Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imp: use variable keys and proposal ID from current chain state #1

Merged
merged 2 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,16 @@ Ref: https://keepachangelog.com/en/1.0.0/

# Changelog

## [v0.2.0](https://github.com/MalteHerrmann/upgrade-local-node-go/releases/tag/v0.2.0) - 2023-08-09

### Improvements

- [#1](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/1) adaptively gets keys and current proposal ID from the local node

## [v0.1.0](https://github.com/MalteHerrmann/upgrade-local-node-go/releases/tag/v0.1.0) - 2023-08-01

### Features

- Gets current block height of local node (at `http://localhost:26657`)
- Submit a software upgrade proposal to a running local Evmos node for the target version
- Vote on the software proposal

8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/MalteHerrmann/upgrade-local-node-go

go 1.20

require github.com/stretchr/testify v1.8.4

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
36 changes: 36 additions & 0 deletions keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"regexp"
)

// getKeys returns the list of keys from the current running local node
func getKeys() ([]string, error) {
out, err := executeShellCommand([]string{"keys", "list"}, evmosdHome, "", false)
if err != nil {
return nil, err
}

return parseKeysFromOut(out)
}

func parseKeysFromOut(out string) ([]string, error) {
// Define the regular expression pattern
pattern := `\s+name:\s*(\w+)`

// Compile the regular expression
re := regexp.MustCompile(pattern)

matches := re.FindAllStringSubmatch(out, -1)
if len(matches) == 0 {
return nil, fmt.Errorf("no keys found in output")
}

var keys []string
for _, match := range matches {
keys = append(keys, match[1])
}

return keys, nil
}
62 changes: 62 additions & 0 deletions keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseKeysFromOut(t *testing.T) {
testcases := []struct {
name string
out string
expKeys []string
expError bool
}{
{
name: "pass",
out: ` - address: evmos19mx9kcksequm4m4xume5h0k9fquwgmea3yvu89
name: dev0
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"AmquZBW+CPcgHKx6D4YRDICzr0MNcRvl9Wm/jJn8wJxs"}'
type: local
- address: evmos18z7xfs864u49jcv6gkgajpteesjl5d7krpple6
name: dev1
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"AtY/rqJrmhKbXrQ02xSxq/t9JGgbP2T7HPGTZJIbuT8I"}'
type: local
- address: evmos12rrt7vcnxvhxad6gzz0vt5psdlnurtldety57n
name: dev2
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"A544btlGjv4zB/qpWT8dQqlAHrcmgZEvrFSgJnp7Yjt4"}'
type: local
- address: evmos1dln2gjtsfd2sny6gwdxzyxcsr0uu8sh5nwajun
name: testKey1
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"Amja5pRiVw+5vPkozo6Eo20AEbYVVBqOKBi5yP7EbxyJ"}'
type: local
- address: evmos1qdxgxz9g2la8g9eyjdq4srlpxgrmuqd6ty88zm
name: testKey2
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"A+ytKfWmkQiW0c6iOCXSL71e4b5njmJVUd1msONsPEnA"}'
type: local
- address: evmos1hduvvhjvu0pqu7m97pajymdsupqx3us3ntey9a
name: testKey3
pubkey: '{"@type":"/ethermint.crypto.v1.ethsecp256k1.PubKey","key":"AsdAPndEVttzhUz5iSm0/FoFxkzB0oZE7DuKf3NjzXkS"}'
type: local`,
expKeys: []string{"dev0", "dev1", "dev2", "testKey1", "testKey2", "testKey3"},
},
{
name: "fail - no keys",
out: "invalid output",
expError: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
keys, err := parseKeysFromOut(tc.out)
if tc.expError {
require.Error(t, err, "expected error parsing keys")
} else {
require.NoError(t, err, "unexpected error parsing keys")
require.Equal(t, tc.expKeys, keys)
}
})
}
}
34 changes: 11 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ const (
defaultFees int = 1e18 // 1 aevmos
// The denomination used for the local node.
denom = "aevmos"
// proposalID is the ID of the proposal that will be created.
proposalID = 1
)

// evmosdHome is the home directory of the local node.
Expand Down Expand Up @@ -67,34 +65,24 @@ func upgradeLocalNode(targetVersion string) {
}

upgradeHeight := currentHeight + deltaHeight
upgradeProposal := buildUpgradeProposalCommand(targetVersion, upgradeHeight)
_, err = executeShellCommand(upgradeProposal, evmosdHome, "dev0", true)
fmt.Println("Submitting upgrade proposal...")
proposalID, err := submitUpgradeProposal(targetVersion, upgradeHeight)
if err != nil {
log.Fatalf("Error executing upgrade proposal: %v", err)
}
fmt.Printf("Scheduled upgrade to %s at height %d.\n", targetVersion, upgradeHeight)

wait(2)
if err = voteForProposal(proposalID, "dev0"); err != nil {
log.Fatalf("Error voting for upgrade: %v", err)
}

wait(2)
if err = voteForProposal(proposalID, "dev1"); err != nil {
log.Fatalf("Error voting for upgrade: %v", err)
availableKeys, err := getKeys()
if err != nil {
log.Fatalf("Error getting available keys: %v", err)
}

wait(2)
if err = voteForProposal(proposalID, "dev2"); err != nil {
log.Fatalf("Error voting for upgrade: %v", err)
wait(1)
for _, key := range availableKeys {
if err = voteForProposal(proposalID, key); err != nil {
log.Fatalf("Error voting for upgrade: %v", err)
}
}
fmt.Printf("Cast all votes for proposal %d.\n", proposalID)
}

// voteForProposal votes for the proposal with the given ID using the given account.
func voteForProposal(proposalID int, sender string) error {
_, err := executeShellCommand([]string{"tx", "gov", "vote", fmt.Sprintf("%d", proposalID), "yes"}, evmosdHome, sender, true)
return err
fmt.Printf("Cast all %d 'yes' votes for proposal %d.\n", len(availableKeys), proposalID)
}

// wait waits for the specified amount of seconds.
Expand Down
39 changes: 38 additions & 1 deletion proposal.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
package main

import "fmt"
import (
"fmt"
"regexp"
"strconv"
)

// submitUpgradeProposal submits a software upgrade proposal with the given target version and upgrade height.
func submitUpgradeProposal(targetVersion string, upgradeHeight int) (int, error) {
upgradeProposal := buildUpgradeProposalCommand(targetVersion, upgradeHeight)
out, err := executeShellCommand(upgradeProposal, evmosdHome, "dev0", true)
if err != nil {
return 0, err
}

return getProposalID(out)
}

// getProposalID parses the proposal ID from the given output from submitting an upgrade proposal.
func getProposalID(out string) (int, error) {
// Define the regular expression pattern
pattern := `- key:\s*proposal_id\s*\n\s*value:\s*"([^"]+)"`

// Compile the regular expression
re := regexp.MustCompile(pattern)

match := re.FindStringSubmatch(out)
if len(match) != 2 {
return 0, fmt.Errorf("proposal ID not found in output")
}

return strconv.Atoi(match[1])
}

// buildUpgradeProposalCommand builds the command to submit a software upgrade proposal.
func buildUpgradeProposalCommand(targetVersion string, upgradeHeight int) []string {
Expand All @@ -13,3 +44,9 @@ func buildUpgradeProposalCommand(targetVersion string, upgradeHeight int) []stri
"--no-validate",
}
}

// voteForProposal votes for the proposal with the given ID using the given account.
func voteForProposal(proposalID int, sender string) error {
_, err := executeShellCommand([]string{"tx", "gov", "vote", fmt.Sprintf("%d", proposalID), "yes"}, evmosdHome, sender, true)
return err
}
65 changes: 65 additions & 0 deletions proposal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestGetProposalID(t *testing.T) {
testcases := []struct {
name string
out string
expID int
expError bool
}{
{
name: "pass",
out: `gas estimate: 850456
code: 0
codespace: ""
data: 12330A2D2F636F736D6F732E676F762E763162657461312E4D73675375626D697450726F706F73616C526573706F6E736512020804
events:
logs:
- events:
- attributes:
- key: amount
value: 1000000000000aevmos
- key: proposal_id
value: "4"
type: proposal_deposit
- attributes:
- key: proposal_id
value: "4"
- key: proposal_messages
value: ',/cosmos.gov.v1.MsgExecLegacyContent'
- key: voting_period_start
value: "4"
type: submit_proposal
type: transfer
log: ""
msg_index: 0
timestamp: ""
tx: null
txhash: A505158FF9EFB4E939CD4A9A94F731E0E34AEEF50C7E53A723226EEF33A1A89B`,
expID: 4,
},
{
name: "fail - no proposal ID",
out: "invalid output",
expError: true,
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
id, err := getProposalID(tc.out)
if tc.expError {
require.Error(t, err, "expected error parsing proposal ID")
} else {
require.NoError(t, err, "unexpected error parsing proposal ID")
require.Equal(t, tc.expID, id, "expected different proposal ID")
}
})
}
}