diff --git a/.gitignore b/.gitignore index 616ddf6..dfd801e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ # DS Store .DS_Store +# Coverage report +coverage.out diff --git a/CHANGELOG.md b/CHANGELOG.md index a4541c4..901df73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +- [#14](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/14) Enable just voting with the binary - [#7](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/7) Add linters plus corresponding refactors - [#6](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/6) Restructuring and refactoring - [#4](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/4) Add GH actions and Makefile for testing diff --git a/Makefile b/Makefile index ebfc680..9c2de72 100644 --- a/Makefile +++ b/Makefile @@ -16,3 +16,6 @@ test: test-unit test-unit: @go test -mod=readonly ./... + +test-unit-cover: + @go test -mod=readonly -coverprofile=coverage.out ./... diff --git a/README.md b/README.md index 3c946ec..0bd42c0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# Upgrade a local Evmos node +# Evmos Dev Utils -This utility helps executing the necessary commands to prepare a -software upgrade proposal, submit it to a running local Evmos node, -and vote on the proposal. +This tool contains several utility functionalities that are useful during +development of the Evmos blockchain. -Note, that this script is designed to work with a local node that was +At the core, all interactions go through the Evmos CLI interface, which is +called from within the Go code. + +Note, that this script is designed to work with a local node that was started by calling the `local_node.sh` script from the Evmos main repository. ## Installation @@ -13,13 +15,25 @@ started by calling the `local_node.sh` script from the Evmos main repository. go install github.com/MalteHerrmann/upgrade-local-node-go@latest ``` -## Usage +## Features + +### Upgrade Local Node -Start a local node by running `local_node.sh` from the Evmos repository. -In order to schedule an upgrade, run: +The tool creates and submits a software upgrade proposal to a running local Evmos node, +and votes on the proposal. To do so, run: ```bash upgrade-local-node-go [TARGET_VERSION] ``` The target version must be specified in the format `vX.Y.Z(-rc*)`, e.g. `v13.0.0-rc2`. + +### Vote on Proposal + +The tool can vote with all keys from the configured keyring, that have delegations +to validators. This can either target the most recent proposal, or a specific one when +passing an ID to the command. + +```bash +upgrade-local-node-go vote [PROPOSAL_ID] +``` diff --git a/go.mod b/go.mod index 6ab7218..f0fa956 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cometbft/cometbft v0.37.2 github.com/cosmos/cosmos-sdk v0.47.4 github.com/evmos/evmos/v14 v14.0.0-rc3 + github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 ) @@ -139,7 +140,6 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect github.com/petermattis/goid v0.0.0-20230518223814-80aa455d8761 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect diff --git a/gov/proposal.go b/gov/proposal.go index 46ab8aa..afe3307 100644 --- a/gov/proposal.go +++ b/gov/proposal.go @@ -2,14 +2,118 @@ package gov import ( "fmt" + "log" "strconv" "strings" "github.com/MalteHerrmann/upgrade-local-node-go/utils" - abcitypes "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/pkg/errors" ) +// buildUpgradeProposalCommand builds the command to submit a software upgrade proposal. +func buildUpgradeProposalCommand(targetVersion string, upgradeHeight int) []string { + return []string{ + "tx", "gov", "submit-legacy-proposal", "software-upgrade", targetVersion, + "--title", fmt.Sprintf("'Upgrade to %s'", targetVersion), + "--description", fmt.Sprintf("'Upgrade to %s'", targetVersion), + "--upgrade-height", fmt.Sprintf("%d", upgradeHeight), + "--deposit", "100000000000000000000aevmos", + "--output", "json", + "--no-validate", + } +} + +// GetProposalIDFromSubmitEvents looks for the proposal submission event in the given transaction events +// and returns the proposal id, if found. +func GetProposalIDFromSubmitEvents(events []sdk.StringEvent) (int, error) { + for _, event := range events { + if event.Type != "submit_proposal" { + continue + } + + for _, attribute := range event.Attributes { + if attribute.Key == "proposal_id" { + proposalID, err := strconv.Atoi(attribute.Value) + if err != nil { + return 0, fmt.Errorf("error parsing proposal id: %w", err) + } + + return proposalID, nil + } + } + } + + return 0, fmt.Errorf("proposal submission event not found") +} + +// QueryLatestProposalID queries the latest proposal ID. +func QueryLatestProposalID(bin *utils.Binary) (int, error) { + out, err := utils.ExecuteBinaryCmd(bin, utils.BinaryCmdArgs{ + Subcommand: []string{"q", "gov", "proposals", "--output=json"}, + Quiet: true, + }) + if err != nil { + if strings.Contains(out, "no proposals found") { + return 0, errors.New("no proposals found") + } + + return 0, errors.Wrap(err, "error querying proposals") + } + + // NOTE: the SDK CLI command uses the x/gov v1 package + // see: https://github.com/cosmos/cosmos-sdk/blob/v0.47.4/x/gov/client/cli/query.go#L151-L159 + var res govv1types.QueryProposalsResponse + + err = bin.Cdc.UnmarshalJSON([]byte(out), &res) + if err != nil { + return 0, errors.Wrap(err, "error unmarshalling proposals") + } + + if len(res.Proposals) == 0 { + return 0, errors.New("no proposals found") + } + + return int(res.Proposals[len(res.Proposals)-1].Id), nil +} + +// SubmitAllVotesForProposal submits a vote for the given proposal ID using all testing accounts. +func SubmitAllVotesForProposal(bin *utils.Binary, proposalID int) error { + accsWithDelegations, err := utils.FilterAccountsWithDelegations(bin) + if err != nil { + return errors.Wrap(err, "Error filtering accounts") + } + + if len(accsWithDelegations) == 0 { + return errors.New("No accounts with delegations found") + } + + utils.Wait(1) + log.Printf("Voting for proposal %d...\n", proposalID) + + var out string + + for _, acc := range accsWithDelegations { + out, err = VoteForProposal(bin, proposalID, acc.Name) + if err != nil { + if strings.Contains(out, fmt.Sprintf("%d: unknown proposal", proposalID)) { + return fmt.Errorf("no proposal with ID %d found", proposalID) + } + + if strings.Contains(out, fmt.Sprintf("%d: inactive proposal", proposalID)) { + return fmt.Errorf("proposal with ID %d is inactive", proposalID) + } + + log.Printf(" - could NOT vote using key: %s\n", acc.Name) + } else { + log.Printf(" - voted using key: %s\n", acc.Name) + } + } + + return nil +} + // SubmitUpgradeProposal submits a software upgrade proposal with the given target version and upgrade height. func SubmitUpgradeProposal(bin *utils.Binary, targetVersion string, upgradeHeight int) (int, error) { upgradeProposal := buildUpgradeProposalCommand(targetVersion, upgradeHeight) @@ -35,53 +139,24 @@ func SubmitUpgradeProposal(bin *utils.Binary, targetVersion string, upgradeHeigh return 0, fmt.Errorf("error getting tx events: %w", err) } - return GetProposalID(events) -} - -// GetProposalID looks for the proposal submission event in the given transaction events -// and returns the proposal id, if found. -func GetProposalID(events []abcitypes.Event) (int, error) { - for _, event := range events { - if event.Type != "submit_proposal" { - continue - } - - for _, attribute := range event.Attributes { - if attribute.Key == "proposal_id" { - proposalID, err := strconv.Atoi(attribute.Value) - if err != nil { - return 0, fmt.Errorf("error parsing proposal id: %w", err) - } - - return proposalID, nil - } - } + if len(events) == 0 { + return 0, fmt.Errorf("no events found in transaction to submit proposal") } - return 0, fmt.Errorf("proposal submission event not found") -} - -// buildUpgradeProposalCommand builds the command to submit a software upgrade proposal. -func buildUpgradeProposalCommand(targetVersion string, upgradeHeight int) []string { - return []string{ - "tx", "gov", "submit-legacy-proposal", "software-upgrade", targetVersion, - "--title", fmt.Sprintf("'Upgrade to %s'", targetVersion), - "--description", fmt.Sprintf("'Upgrade to %s'", targetVersion), - "--upgrade-height", fmt.Sprintf("%d", upgradeHeight), - "--deposit", "100000000000000000000aevmos", - "--output", "json", - "--no-validate", - } + return GetProposalIDFromSubmitEvents(events) } // VoteForProposal votes for the proposal with the given ID using the given account. -func VoteForProposal(bin *utils.Binary, proposalID int, sender string) error { - _, err := utils.ExecuteBinaryCmd(bin, utils.BinaryCmdArgs{ +func VoteForProposal(bin *utils.Binary, proposalID int, sender string) (string, error) { + out, err := utils.ExecuteBinaryCmd(bin, utils.BinaryCmdArgs{ Subcommand: []string{"tx", "gov", "vote", fmt.Sprintf("%d", proposalID), "yes"}, From: sender, UseDefaults: true, Quiet: true, }) + if err != nil { + return out, errors.Wrap(err, fmt.Sprintf("failed to vote for proposal %d", proposalID)) + } - return errors.Wrap(err, fmt.Sprintf("failed to vote for proposal %d", proposalID)) + return out, nil } diff --git a/gov/proposal_test.go b/gov/proposal_test.go index 4c160a9..c1c5505 100644 --- a/gov/proposal_test.go +++ b/gov/proposal_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/MalteHerrmann/upgrade-local-node-go/gov" - abcitypes "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -14,16 +14,16 @@ func TestGetProposalID(t *testing.T) { testcases := []struct { name string - events []abcitypes.Event + events []sdk.StringEvent expID int expError bool errContains string }{ { name: "pass", - events: []abcitypes.Event{{ + events: []sdk.StringEvent{{ Type: "submit_proposal", - Attributes: []abcitypes.EventAttribute{ + Attributes: []sdk.Attribute{ {Key: "proposal_id", Value: "5"}, {Key: "proposal_messages", Value: ",/cosmos.gov.v1.MsgExecLegacyContent"}, }, @@ -32,10 +32,10 @@ func TestGetProposalID(t *testing.T) { }, { name: "pass - multiple events", - events: []abcitypes.Event{ + events: []sdk.StringEvent{ { Type: "message", - Attributes: []abcitypes.EventAttribute{ + Attributes: []sdk.Attribute{ {Key: "action", Value: "/cosmos.gov.v1beta1.MsgSubmitProposal"}, {Key: "sender", Value: "evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2"}, {Key: "module", Value: "gov"}, @@ -43,7 +43,7 @@ func TestGetProposalID(t *testing.T) { }, { Type: "submit_proposal", - Attributes: []abcitypes.EventAttribute{ + Attributes: []sdk.Attribute{ {Key: "proposal_id", Value: "5"}, {Key: "proposal_messages", Value: ",/cosmos.gov.v1.MsgExecLegacyContent"}, }, @@ -53,9 +53,9 @@ func TestGetProposalID(t *testing.T) { }, { name: "fail - no submit proposal event", - events: []abcitypes.Event{{ + events: []sdk.StringEvent{{ Type: "other type", - Attributes: []abcitypes.EventAttribute{ + Attributes: []sdk.Attribute{ {Key: "proposal_id", Value: "4"}, {Key: "proposal_messages", Value: ",/cosmos.gov.v1.MsgExecLegacyContent"}, }, @@ -65,9 +65,9 @@ func TestGetProposalID(t *testing.T) { }, { name: "fail - invalid proposal ID", - events: []abcitypes.Event{{ + events: []sdk.StringEvent{{ Type: "submit_proposal", - Attributes: []abcitypes.EventAttribute{ + Attributes: []sdk.Attribute{ {Key: "proposal_id", Value: "invalid"}, }, }}, @@ -80,7 +80,7 @@ func TestGetProposalID(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - propID, err := gov.GetProposalID(tc.events) + propID, err := gov.GetProposalIDFromSubmitEvents(tc.events) if tc.expError { require.Error(t, err, "expected error parsing proposal ID") require.ErrorContains(t, err, tc.errContains, "expected different error") diff --git a/main.go b/main.go index f00b0b1..d4103a3 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,11 @@ import ( "log" "os" "regexp" - "time" + "strconv" "github.com/MalteHerrmann/upgrade-local-node-go/gov" "github.com/MalteHerrmann/upgrade-local-node-go/utils" + "github.com/pkg/errors" ) // The amount of blocks in the future that the upgrade will be scheduled. @@ -15,30 +16,79 @@ const deltaHeight = 15 func main() { if len(os.Args) < 2 { - log.Println("Usage: upgrade-local-node-go ") + log.Printf( + "Possible usages:\n" + + " upgrade-local-node-go \n" + + " upgrade-local-node-go vote [proposal-id]\n", + ) os.Exit(1) } - targetVersion := os.Args[1] - if matched, _ := regexp.MatchString(`v\d+\.\d+\.\d(-rc\d+)?`, targetVersion); !matched { - log.Println("Invalid target version. Please use the format vX.Y.Z(-rc*).") - os.Exit(2) - } - bin, err := utils.NewEvmosTestingBinary() if err != nil { log.Fatalf("Error creating binary: %v", err) } - upgradeLocalNode(bin, targetVersion) + err = bin.GetAccounts() + if err != nil { + log.Fatalf("Error getting accounts: %v", err) + } + + //nolint:nestif // nesting complexity is fine here, will be reworked with Cobra commands anyway + if os.Args[1] == "vote" { + proposalID, err := getProposalIDForVoting(bin, os.Args) + if err != nil { + log.Fatalf("Error getting proposal ID: %v", err) + } + + err = gov.SubmitAllVotesForProposal(bin, proposalID) + if err != nil { + log.Fatalf("Error submitting votes for proposal %d: %v", proposalID, err) + } + } else { + targetVersion := os.Args[1] + if matched, _ := regexp.MatchString(`v\d+\.\d+\.\d(-rc\d+)?`, targetVersion); !matched { + log.Fatalf("Invalid target version: %s. Please use the format vX.Y.Z(-rc*).\n", targetVersion) + } + + err := upgradeLocalNode(bin, targetVersion) + if err != nil { + log.Fatalf("Error upgrading local node: %v", err) + } + } +} + +// getProposalIDForVoting gets the proposal ID from the command line arguments. +func getProposalIDForVoting(bin *utils.Binary, args []string) (int, error) { + var ( + err error + proposalID int + ) + + switch len(args) { + case 2: + proposalID, err = gov.QueryLatestProposalID(bin) + if err != nil { + return 0, errors.Wrap(err, "Error querying latest proposal ID") + } + case 3: + proposalID, err = strconv.Atoi(args[2]) + if err != nil { + return 0, errors.Wrapf(err, "Error converting proposal ID %s to integer", args[2]) + } + default: + return 0, errors.New("Invalid number of arguments") + } + + return proposalID, nil } // upgradeLocalNode prepares upgrading the local node to the target version // by submitting the upgrade proposal and voting on it using all testing accounts. -func upgradeLocalNode(bin *utils.Binary, targetVersion string) { +func upgradeLocalNode(bin *utils.Binary, targetVersion string) error { currentHeight, err := utils.GetCurrentHeight(bin) if err != nil { - log.Fatalf("Error getting current height: %v", err) + return errors.Wrap(err, "Error getting current height") } upgradeHeight := currentHeight + deltaHeight @@ -47,34 +97,15 @@ func upgradeLocalNode(bin *utils.Binary, targetVersion string) { proposalID, err := gov.SubmitUpgradeProposal(bin, targetVersion, upgradeHeight) if err != nil { - log.Fatalf("Error executing upgrade proposal: %v", err) + return errors.Wrap(err, "Error executing upgrade proposal") } log.Printf("Scheduled upgrade to %s at height %d.\n", targetVersion, upgradeHeight) - availableAccounts, err := utils.GetAccounts(bin) + err = gov.SubmitAllVotesForProposal(bin, proposalID) if err != nil { - log.Fatalf("Error getting available keys: %v", err) + return errors.Wrapf(err, "Error submitting votes for proposal %d", proposalID) } - accsWithDelegations, err := utils.FilterAccountsWithDelegations(bin, availableAccounts) - if err != nil { - log.Fatalf("Error filtering accounts: %v", err) - } - - wait(1) - log.Println("Voting for upgrade...") - - for _, acc := range accsWithDelegations { - if err = gov.VoteForProposal(bin, proposalID, acc.Name); err != nil { - log.Printf(" - could NOT vote using key: %s\n", acc.Name) - } else { - log.Printf(" - voted using key: %s\n", acc.Name) - } - } -} - -// wait waits for the specified amount of seconds. -func wait(seconds int) { - time.Sleep(time.Duration(seconds) * time.Second) + return nil } diff --git a/utils/binary.go b/utils/binary.go index 54ba050..c32563e 100644 --- a/utils/binary.go +++ b/utils/binary.go @@ -21,6 +21,8 @@ type Binary struct { Home string // Appd is the name of the binary to be executed, e.g. "evmosd". Appd string + // Accounts are the accounts stored in the local keyring. + Accounts []Account } // NewBinary returns a new Binary instance. diff --git a/utils/keys.go b/utils/keys.go index 7c8cdde..e403a1a 100644 --- a/utils/keys.go +++ b/utils/keys.go @@ -18,28 +18,35 @@ type Account struct { Delegations []stakingtypes.Delegation `json:"delegations"` } -// GetAccounts returns the list of keys from the current running local node. -func GetAccounts(bin *Binary) ([]Account, error) { +// GetAccounts is a method to retrieve the binaries keys from the configured +// keyring backend and stores it in the Binary struct. +func (bin *Binary) GetAccounts() error { out, err := ExecuteBinaryCmd(bin, BinaryCmdArgs{ Subcommand: []string{"keys", "list", "--output=json"}, }) if err != nil { - return nil, err + return err } accounts, err := ParseAccountsFromOut(out) if err != nil { - return nil, err + return err } - return accounts, nil + bin.Accounts = accounts + + return nil } // FilterAccountsWithDelegations filters the given list of accounts for those, which are used for staking. -func FilterAccountsWithDelegations(bin *Binary, accounts []Account) ([]Account, error) { +func FilterAccountsWithDelegations(bin *Binary) ([]Account, error) { var stakingAccs []Account - for _, acc := range accounts { + if len(bin.Accounts) == 0 { + return nil, fmt.Errorf("no accounts found") + } + + for _, acc := range bin.Accounts { out, err := ExecuteBinaryCmd(bin, BinaryCmdArgs{ Subcommand: []string{"query", "staking", "delegations", acc.Address, "--output=json"}, }) diff --git a/utils/utils.go b/utils/utils.go index 085e36e..5164097 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -9,7 +9,6 @@ import ( "strings" "time" - abcitypes "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" evmosutils "github.com/evmos/evmos/v14/utils" @@ -100,7 +99,7 @@ func GetCurrentHeight(bin *Binary) (int, error) { // It tries to get the transaction hash from the output // and then waits for the transaction to be included in a block. // It then returns the transaction events. -func GetTxEvents(bin *Binary, out string) ([]abcitypes.Event, error) { +func GetTxEvents(bin *Binary, out string) ([]sdk.StringEvent, error) { txHash, err := GetTxHashFromTxResponse(bin.Cdc, out) if err != nil { return nil, err @@ -138,7 +137,7 @@ func GetTxEvents(bin *Binary, out string) ([]abcitypes.Event, error) { // GetEventsFromTxResponse unpacks the transaction response into the corresponding // SDK type and returns the events. -func GetEventsFromTxResponse(cdc *codec.ProtoCodec, out string) ([]abcitypes.Event, error) { +func GetEventsFromTxResponse(cdc *codec.ProtoCodec, out string) ([]sdk.StringEvent, error) { var txRes sdk.TxResponse err := cdc.UnmarshalJSON([]byte(out), &txRes) @@ -146,7 +145,20 @@ func GetEventsFromTxResponse(cdc *codec.ProtoCodec, out string) ([]abcitypes.Eve return nil, fmt.Errorf("error unmarshalling transaction response: %w\n\nresponse: %s", err, out) } - return txRes.Events, nil + logs := txRes.Logs + if len(logs) == 0 { + return nil, fmt.Errorf("no logs found in transaction response: %s", out) + } + + var events []sdk.StringEvent + + for _, msgLog := range logs { + for _, event := range msgLog.Events { + events = append(events, event) + } + } + + return events, nil } // GetTxHashFromTxResponse parses the transaction hash from the given response. @@ -164,3 +176,8 @@ func GetTxHashFromTxResponse(cdc *codec.ProtoCodec, out string) (string, error) return txHash.TxHash, nil } + +// Wait waits for the specified amount of seconds. +func Wait(seconds int) { + time.Sleep(time.Duration(seconds) * time.Second) +} diff --git a/utils/utils_test.go b/utils/utils_test.go index df67e7c..5985525 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/MalteHerrmann/upgrade-local-node-go/utils" - abcitypes "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -67,7 +67,7 @@ func TestGetEventsFromTxResponse(t *testing.T) { testcases := []struct { name string out string - expEvents []abcitypes.Event + expEvents []sdk.StringEvent expError bool errContains string }{ @@ -75,11 +75,11 @@ func TestGetEventsFromTxResponse(t *testing.T) { name: "pass", //nolint:lll // line length is okay here out: `{"height":"138","txhash":"FE14C1BF8BBA55A314D7040ACA404A97D2172126ABF81C0C90D0B5C9B0CADEE6","codespace":"","code":0,"data":"12330A2D2F636F736D6F732E676F762E763162657461312E4D73675375626D697450726F706F73616C526573706F6E736512020805","raw_log":"[{\"msg_index\":0,\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.gov.v1beta1.MsgSubmitProposal\"},{\"key\":\"sender\",\"value\":\"evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2\"},{\"key\":\"module\",\"value\":\"gov\"}]},{\"type\":\"submit_proposal\",\"attributes\":[{\"key\":\"proposal_id\",\"value\":\"5\"},{\"key\":\"proposal_messages\",\"value\":\",/cosmos.gov.v1.MsgExecLegacyContent\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2\"},{\"key\":\"amount\",\"value\":\"100000000000000000000aevmos\"}]},{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"evmos10d07y265gmmuvt4z0w9aw880jnsr700jcrztvm\"},{\"key\":\"amount\",\"value\":\"100000000000000000000aevmos\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"evmos10d07y265gmmuvt4z0w9aw880jnsr700jcrztvm\"},{\"key\":\"sender\",\"value\":\"evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2\"},{\"key\":\"amount\",\"value\":\"100000000000000000000aevmos\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"sender\",\"value\":\"evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2\"}]},{\"type\":\"proposal_deposit\",\"attributes\":[{\"key\":\"amount\",\"value\":\"100000000000000000000aevmos\"},{\"key\":\"proposal_id\",\"value\":\"5\"}]},{\"type\":\"submit_proposal\",\"attributes\":[{\"key\":\"voting_period_start\",\"value\":\"5\"}]}]}]","logs":[{"msg_index":0,"log":"","events":[{"type":"submit_proposal","attributes":[{"key":"proposal_id","value":"5"},{"key":"proposal_messages","value":",/cosmos.gov.v1.MsgExecLegacyContent"}]}]}],"info":"","gas_wanted":"270887","gas_used":"209242","tx":{"@type":"/cosmos.tx.v1beta1.Tx","body":{"messages":[{"@type":"/cosmos.gov.v1beta1.MsgSubmitProposal","content":{"@type":"/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal","title":"'Upgrade to v14.0.0-rc4'","description":"'Upgrade to v14.0.0-rc4'","plan":{"name":"v14.0.0-rc4","time":"0001-01-01T00:00:00Z","height":"151","info":"","upgraded_client_state":null}},"initial_deposit":[{"denom":"aevmos","amount":"100000000000000000000"}],"proposer":"evmos1vv6hqcxp0w5we5rzdvf4ddhsas5gx0dep8vmv2"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"A9i4S3tyqlhjlLyOwVu9PNYWZNLL29RR643ae7K63VJ1"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"5"}],"fee":{"amount":[{"denom":"aevmos","amount":"1000000000000000000"}],"gas_limit":"270887","payer":"","granter":""},"tip":null},"signatures":["cOwD3//F3MuL1rBmlCRTySjJidZGJygW2u96nXL5DR8a1GkmjyfToUUUpfs2HtYZohIGOsjG8werUSxllSa6LgA="]},"timestamp":"2023-08-23T21:16:24Z","events":[{"type":"submit_proposal","attributes":[{"key":"proposal_id","value":"5","index":true},{"key":"proposal_messages","value":",/cosmos.gov.v1.MsgExecLegacyContent","index":true}]}]}`, - expEvents: []abcitypes.Event{{ + expEvents: []sdk.StringEvent{{ Type: "submit_proposal", - Attributes: []abcitypes.EventAttribute{ - {Key: "proposal_id", Value: "5", Index: true}, - {Key: "proposal_messages", Value: ",/cosmos.gov.v1.MsgExecLegacyContent", Index: true}, + Attributes: []sdk.Attribute{ + {Key: "proposal_id", Value: "5"}, + {Key: "proposal_messages", Value: ",/cosmos.gov.v1.MsgExecLegacyContent"}, }, }}, },