From b27cf2b03fb0b430924df2dd1ffaa17c73f04fd0 Mon Sep 17 00:00:00 2001
From: Sergey <83376337+freak12techno@users.noreply.github.com>
Date: Sun, 4 Jun 2023 12:04:34 +0300
Subject: [PATCH] feat: parse v1 proposals (#48)
* feat: parse v1 proposals
* feat: fix defaults
---
config.example.toml | 10 ++++
go.mod | 4 +-
go.sum | 4 +-
pkg/config/config.go | 30 ++++++-----
pkg/config/config_test.go | 31 +++++++++---
pkg/config/types/types.go | 17 +++++--
pkg/logger/logger.go | 5 +-
pkg/report/generator.go | 4 +-
pkg/report/generator_test.go | 26 ++++------
pkg/reporters/pagerduty/pagerduty.go | 14 +++---
pkg/reporters/telegram/telegram.go | 4 +-
pkg/state/generator.go | 12 ++---
pkg/state/state.go | 6 +--
pkg/state/state_test.go | 2 +-
pkg/tendermint/tendermint.go | 64 +++++++++++++++++++++---
pkg/types/responses.go | 63 +++++++++++++++++++----
pkg/types/types.go | 17 +++++++
pkg/utils/utils.go | 10 ++++
templates/telegram/not_voted.html | 6 +--
templates/telegram/proposals.html | 2 +-
templates/telegram/revoted.html | 6 +--
templates/telegram/vote_query_error.html | 2 +-
templates/telegram/voted.html | 6 +--
23 files changed, 248 insertions(+), 97 deletions(-)
diff --git a/config.example.toml b/config.example.toml
index 3b79220..3dd0632 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -37,6 +37,16 @@ wallets = [
{ address = "bitsong14rvn7anf22e00vj5x3al4w50ns78s7n4t8yxcy", alias = "Validator wallet" },
{ address = "bitsong125hdkukw4pu2urhj4nv366q0avdqv24t0vprxs" },
]
+# Some chains have a new proposals structure (v1) compared to an older one (v1beta1),
+# when there are 2 or more actual proposals inside a single one (namely, Quicksilver).
+# On such chains, querying proposals with an older endpoint when there are proposals
+# with 2 or more proposals inside causes an error like this:
+# "codespace sdk code 29: invalid type: can't convert a gov/v1 Proposal to gov/v1beta1 Proposal
+# when amount of proposal messages is more than one". If you see this error, consider switching to
+# a newer endpoint. Keep in mind that some chains do not implement the newer format.
+# The possible values are: "v1" (newer format), "v1beta1" (older format).
+# Defaults to "v1beta1"
+proposals-type = "v1beta1"
# Custom explorer links patterns. They are overridden if mintscan-prefix is specified.
[chains.explorer]
# A pattern for proposal link for explorer, if there's no Mintscan support
diff --git a/go.mod b/go.mod
index 78ff6fa..5ad2fff 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.18
require (
github.com/BurntSushi/toml v1.1.0
- github.com/mcuadros/go-defaults v1.2.0
+ github.com/creasty/defaults v1.7.0
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.26.1
github.com/spf13/cobra v1.4.0
@@ -15,7 +15,9 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
+ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
diff --git a/go.sum b/go.sum
index a8d32b4..634b7d3 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
+github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
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=
@@ -24,8 +26,6 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
-github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 22cdcae..daaadf3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -2,22 +2,23 @@ package config
import (
"fmt"
+ "main/pkg/logger"
"os"
- "main/pkg/config/types"
+ configTypes "main/pkg/config/types"
"github.com/BurntSushi/toml"
- "github.com/mcuadros/go-defaults"
+ "github.com/creasty/defaults"
)
type Config struct {
- PagerDutyConfig PagerDutyConfig `toml:"pagerduty"`
- TelegramConfig TelegramConfig `toml:"telegram"`
- LogConfig LogConfig `toml:"log"`
- StatePath string `toml:"state-path"`
- MutesPath string `toml:"mutes-path"`
- Chains types.Chains `toml:"chains"`
- Interval string `toml:"interval" default:"* * * * *"`
+ PagerDutyConfig PagerDutyConfig `toml:"pagerduty"`
+ TelegramConfig TelegramConfig `toml:"telegram"`
+ LogConfig configTypes.LogConfig `toml:"log"`
+ StatePath string `toml:"state-path"`
+ MutesPath string `toml:"mutes-path"`
+ Chains configTypes.Chains `toml:"chains"`
+ Interval string `toml:"interval" default:"* * * * *"`
}
type PagerDutyConfig struct {
@@ -30,11 +31,6 @@ type TelegramConfig struct {
TelegramToken string `toml:"token"`
}
-type LogConfig struct {
- LogLevel string `toml:"level" default:"info"`
- JSONOutput bool `toml:"json" default:"false"`
-}
-
func (c *Config) Validate() error {
if len(c.Chains) == 0 {
return fmt.Errorf("no chains provided")
@@ -62,11 +58,13 @@ func GetConfig(path string) (*Config, error) {
return nil, err
}
- defaults.SetDefaults(configStruct)
+ if err := defaults.Set(configStruct); err != nil {
+ logger.GetDefaultLogger().Fatal().Err(err).Msg("Error setting default config values")
+ }
for _, chain := range configStruct.Chains {
if chain.MintscanPrefix != "" {
- chain.Explorer = &types.Explorer{
+ chain.Explorer = &configTypes.Explorer{
ProposalLinkPattern: fmt.Sprintf("https://mintscan.io/%s/proposals/%%s", chain.MintscanPrefix),
WalletLinkPattern: fmt.Sprintf("https://mintscan.io/%s/account/%%s", chain.MintscanPrefix),
}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 15b8cde..adab1aa 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -48,9 +48,10 @@ func TestValidateChainWithValidConfig(t *testing.T) {
t.Parallel()
chain := configTypes.Chain{
- Name: "chain",
- LCDEndpoints: []string{"endpoint"},
- Wallets: []*configTypes.Wallet{{Address: "wallet"}},
+ Name: "chain",
+ LCDEndpoints: []string{"endpoint"},
+ Wallets: []*configTypes.Wallet{{Address: "wallet"}},
+ ProposalsType: "v1",
}
err := chain.Validate()
@@ -105,15 +106,33 @@ func TestValidateConfigInvalidChain(t *testing.T) {
assert.NotEqual(t, err, nil, "Error should be presented!")
}
+func TestValidateConfigWrongProposalType(t *testing.T) {
+ t.Parallel()
+
+ config := Config{
+ Chains: []*configTypes.Chain{
+ {
+ Name: "chain",
+ LCDEndpoints: []string{"endpoint"},
+ Wallets: []*configTypes.Wallet{{Address: "wallet"}},
+ ProposalsType: "test",
+ },
+ },
+ }
+ err := config.Validate()
+ assert.NotEqual(t, err, nil, "Error should be presented!")
+}
+
func TestValidateConfigValidChain(t *testing.T) {
t.Parallel()
config := Config{
Chains: []*configTypes.Chain{
{
- Name: "chain",
- LCDEndpoints: []string{"endpoint"},
- Wallets: []*configTypes.Wallet{{Address: "wallet"}},
+ Name: "chain",
+ LCDEndpoints: []string{"endpoint"},
+ Wallets: []*configTypes.Wallet{{Address: "wallet"}},
+ ProposalsType: "v1",
},
},
}
diff --git a/pkg/config/types/types.go b/pkg/config/types/types.go
index 7fe4451..b6d6da9 100644
--- a/pkg/config/types/types.go
+++ b/pkg/config/types/types.go
@@ -3,6 +3,7 @@ package types
import (
"fmt"
"main/pkg/types"
+ "main/pkg/utils"
)
type Explorer struct {
@@ -28,6 +29,7 @@ type Chain struct {
PrettyName string `toml:"pretty-name"`
KeplrName string `toml:"keplr-name"`
LCDEndpoints []string `toml:"lcd-endpoints"`
+ ProposalsType string `toml:"proposals-type" default:"v1beta1"`
Wallets []*Wallet `toml:"wallets"`
MintscanPrefix string `toml:"mintscan-prefix"`
Explorer *Explorer `toml:"explorer"`
@@ -46,6 +48,10 @@ func (c *Chain) Validate() error {
return fmt.Errorf("no wallets provided")
}
+ if !utils.Contains([]string{"v1beta1", "v1"}, c.ProposalsType) {
+ return fmt.Errorf("wrong proposals type: expected one of 'v1beta1', 'v1', but got %s", c.ProposalsType)
+ }
+
for index, wallet := range c.Wallets {
if wallet.Address == "" {
return fmt.Errorf("wallet #%d: address is empty", index)
@@ -85,12 +91,12 @@ func (c Chain) GetExplorerProposalsLinks(proposalID string) []types.Link {
func (c Chain) GetProposalLink(proposal types.Proposal) types.Link {
if c.Explorer == nil || c.Explorer.ProposalLinkPattern == "" {
- return types.Link{Name: proposal.Content.Title}
+ return types.Link{Name: proposal.Title}
}
return types.Link{
- Name: proposal.Content.Title,
- Href: fmt.Sprintf(c.Explorer.ProposalLinkPattern, proposal.ProposalID),
+ Name: proposal.Title,
+ Href: fmt.Sprintf(c.Explorer.ProposalLinkPattern, proposal.ID),
}
}
@@ -122,3 +128,8 @@ func (c Chains) FindByName(name string) *Chain {
return nil
}
+
+type LogConfig struct {
+ LogLevel string `toml:"level" default:"info"`
+ JSONOutput bool `toml:"json" default:"false"`
+}
diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go
index 3a4dfcc..9c487dd 100644
--- a/pkg/logger/logger.go
+++ b/pkg/logger/logger.go
@@ -1,10 +1,9 @@
package logger
import (
+ configTypes "main/pkg/config/types"
"os"
- "main/pkg/config"
-
"github.com/rs/zerolog"
)
@@ -13,7 +12,7 @@ func GetDefaultLogger() *zerolog.Logger {
return &log
}
-func GetLogger(config config.LogConfig) *zerolog.Logger {
+func GetLogger(config configTypes.LogConfig) *zerolog.Logger {
log := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger()
if config.JSONOutput {
diff --git a/pkg/report/generator.go b/pkg/report/generator.go
index e16d14b..830e613 100644
--- a/pkg/report/generator.go
+++ b/pkg/report/generator.go
@@ -91,7 +91,7 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
if newVote.HasVoted() && !oldVote.HasVoted() {
g.Logger.Debug().
Str("chain", chainName).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Str("wallet", wallet).
Msg("Wallet hasn't voted before but voted now - closing an alert")
@@ -108,7 +108,7 @@ func (g *Generator) GenerateReport(oldState, newState state.State) reporters.Rep
if newVote.HasVoted() && oldVote.HasVoted() && newVote.Vote.Option != oldVote.Vote.Option {
g.Logger.Debug().
Str("chain", chainName).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Str("wallet", wallet).
Msg("Wallet changed its vote - sending an alert")
diff --git a/pkg/report/generator_test.go b/pkg/report/generator_test.go
index 0d5c408..bb79634 100644
--- a/pkg/report/generator_test.go
+++ b/pkg/report/generator_test.go
@@ -50,8 +50,7 @@ func TestReportGeneratorWithVoteError(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -73,7 +72,7 @@ func TestReportGeneratorWithVoteError(t *testing.T) {
entry, ok := report.Entries[0].(events.VoteQueryError)
assert.True(t, ok, "Expected to have a vote query error!")
- assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
+ assert.Equal(t, entry.Proposal.ID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithNotVoted(t *testing.T) {
@@ -88,8 +87,7 @@ func TestReportGeneratorWithNotVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {},
@@ -109,7 +107,7 @@ func TestReportGeneratorWithNotVoted(t *testing.T) {
entry, ok := report.Entries[0].(events.NotVotedEvent)
assert.True(t, ok, "Expected to have not voted type!")
- assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
+ assert.Equal(t, entry.Proposal.ID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithVoted(t *testing.T) {
@@ -123,8 +121,7 @@ func TestReportGeneratorWithVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {},
@@ -140,8 +137,7 @@ func TestReportGeneratorWithVoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -165,7 +161,7 @@ func TestReportGeneratorWithVoted(t *testing.T) {
entry, ok := report.Entries[0].(events.VotedEvent)
assert.True(t, ok, "Expected to have voted type!")
- assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
+ assert.Equal(t, entry.Proposal.ID, "proposal", "Proposal ID mismatch!")
}
func TestReportGeneratorWithRevoted(t *testing.T) {
@@ -179,8 +175,7 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -200,8 +195,7 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
ProposalVotes: map[string]state.WalletVotes{
"proposal": {
Proposal: types.Proposal{
- ProposalID: "proposal",
- Content: &types.ProposalContent{},
+ ID: "proposal",
},
Votes: map[string]state.ProposalVote{
"wallet": {
@@ -225,5 +219,5 @@ func TestReportGeneratorWithRevoted(t *testing.T) {
entry, ok := report.Entries[0].(events.RevotedEvent)
assert.True(t, ok, "Expected to have revoted type!")
- assert.Equal(t, entry.Proposal.ProposalID, "proposal", "Proposal ID mismatch!")
+ assert.Equal(t, entry.Proposal.ID, "proposal", "Proposal ID mismatch!")
}
diff --git a/pkg/reporters/pagerduty/pagerduty.go b/pkg/reporters/pagerduty/pagerduty.go
index 0b64a5f..240af53 100644
--- a/pkg/reporters/pagerduty/pagerduty.go
+++ b/pkg/reporters/pagerduty/pagerduty.go
@@ -65,7 +65,7 @@ func (r *Reporter) NewAlertFromReportEntry(eventRaw entry.ReportEntry) (Alert, e
dedupKey := fmt.Sprintf(
"cosmos-proposals-checker alert chain=%s proposal=%s wallet=%s",
event.GetChain().Name,
- event.GetProposal().ProposalID,
+ event.GetProposal().ID,
event.GetWallet().AddressOrAlias(),
)
@@ -75,7 +75,7 @@ func (r *Reporter) NewAlertFromReportEntry(eventRaw entry.ReportEntry) (Alert, e
}
links := []Link{}
- explorerLinks := event.GetChain().GetExplorerProposalsLinks(event.GetProposal().ProposalID)
+ explorerLinks := event.GetChain().GetExplorerProposalsLinks(event.GetProposal().ID)
for _, link := range explorerLinks {
links = append(links, Link{
Href: link.Href,
@@ -88,9 +88,9 @@ func (r *Reporter) NewAlertFromReportEntry(eventRaw entry.ReportEntry) (Alert, e
Summary: fmt.Sprintf(
"Wallet %s hasn't voted on proposal %s on %s: %s",
event.GetWallet().AddressOrAlias(),
- event.GetProposal().ProposalID,
+ event.GetProposal().ID,
event.GetChain().GetName(),
- event.GetProposal().Content.Title,
+ event.GetProposal().Title,
),
Timestamp: time.Now().Format(time.RFC3339),
Severity: "error",
@@ -98,9 +98,9 @@ func (r *Reporter) NewAlertFromReportEntry(eventRaw entry.ReportEntry) (Alert, e
CustomDetails: map[string]string{
"Wallet": event.GetWallet().AddressOrAlias(),
"Chain": event.GetChain().GetName(),
- "Proposal ID": event.GetProposal().ProposalID,
- "Proposal title": event.GetProposal().Content.Title,
- "Proposal description": event.GetProposal().Content.Description,
+ "Proposal ID": event.GetProposal().ID,
+ "Proposal title": event.GetProposal().Title,
+ "Proposal description": event.GetProposal().Description,
},
},
Links: links,
diff --git a/pkg/reporters/telegram/telegram.go b/pkg/reporters/telegram/telegram.go
index b9fce88..ae0e621 100644
--- a/pkg/reporters/telegram/telegram.go
+++ b/pkg/reporters/telegram/telegram.go
@@ -124,10 +124,10 @@ func (reporter *Reporter) SendReport(report reporters.Report) error {
if entryConverted, ok := reportEntry.(entry.ReportEntryNotError); ok {
chain := entryConverted.GetChain()
proposal := entryConverted.GetProposal()
- if reporter.MutesManager.IsMuted(chain.Name, proposal.ProposalID) {
+ if reporter.MutesManager.IsMuted(chain.Name, proposal.ID) {
reporter.Logger.Debug().
Str("chain", chain.Name).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Msg("Notifications are muted, not sending.")
continue
}
diff --git a/pkg/state/generator.go b/pkg/state/generator.go
index 6e0f62c..46c4003 100644
--- a/pkg/state/generator.go
+++ b/pkg/state/generator.go
@@ -45,7 +45,7 @@ func (g *Generator) ProcessChain(
state State,
oldState State,
) {
- rpc := tendermint.NewRPC(chain.LCDEndpoints, g.Logger)
+ rpc := tendermint.NewRPC(chain, g.Logger)
proposals, err := rpc.GetAllProposals()
if err != nil {
@@ -71,13 +71,13 @@ func (g *Generator) ProcessChain(
for _, proposal := range proposals {
g.Logger.Trace().
Str("name", chain.Name).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Msg("Processing a proposal")
for _, wallet := range chain.Wallets {
g.Logger.Trace().
Str("name", chain.Name).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Str("wallet", wallet.Address).
Msg("Processing wallet vote")
wg.Add(1)
@@ -100,13 +100,13 @@ func (g *Generator) ProcessProposalAndWallet(
state State,
oldState State,
) {
- oldVote, _, found := oldState.GetVoteAndProposal(chain.Name, proposal.ProposalID, wallet.Address)
- voteResponse, err := rpc.GetVote(proposal.ProposalID, wallet.Address)
+ oldVote, _, found := oldState.GetVoteAndProposal(chain.Name, proposal.ID, wallet.Address)
+ voteResponse, err := rpc.GetVote(proposal.ID, wallet.Address)
if found && oldVote.HasVoted() && voteResponse.Vote == nil {
g.Logger.Trace().
Str("chain", chain.Name).
- Str("proposal", proposal.ProposalID).
+ Str("proposal", proposal.ID).
Str("wallet", wallet.Address).
Msg("Wallet has voted and there's no vote in the new state - using old vote")
diff --git a/pkg/state/state.go b/pkg/state/state.go
index 35a3c48..aca921b 100644
--- a/pkg/state/state.go
+++ b/pkg/state/state.go
@@ -52,14 +52,14 @@ func (s *State) SetVote(chain *configTypes.Chain, proposal types.Proposal, walle
}
}
- if _, ok := s.ChainInfos[chain.Name].ProposalVotes[proposal.ProposalID]; !ok {
- s.ChainInfos[chain.Name].ProposalVotes[proposal.ProposalID] = WalletVotes{
+ if _, ok := s.ChainInfos[chain.Name].ProposalVotes[proposal.ID]; !ok {
+ s.ChainInfos[chain.Name].ProposalVotes[proposal.ID] = WalletVotes{
Proposal: proposal,
Votes: make(map[string]ProposalVote),
}
}
- s.ChainInfos[chain.Name].ProposalVotes[proposal.ProposalID].Votes[wallet.Address] = vote
+ s.ChainInfos[chain.Name].ProposalVotes[proposal.ID].Votes[wallet.Address] = vote
}
func (s *State) SetChainProposalsError(chain *configTypes.Chain, err error) {
diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go
index 46370f0..f88fedc 100644
--- a/pkg/state/state_test.go
+++ b/pkg/state/state_test.go
@@ -19,7 +19,7 @@ func TestSetVoteWithoutChainInfo(t *testing.T) {
state.SetVote(
&configTypes.Chain{Name: "chain"},
- types.Proposal{ProposalID: "proposal"},
+ types.Proposal{ID: "proposal"},
&configTypes.Wallet{Address: "wallet"},
ProposalVote{
Vote: &types.Vote{
diff --git a/pkg/tendermint/tendermint.go b/pkg/tendermint/tendermint.go
index cd8bf31..07b83dd 100644
--- a/pkg/tendermint/tendermint.go
+++ b/pkg/tendermint/tendermint.go
@@ -4,6 +4,8 @@ import (
"encoding/json"
"errors"
"fmt"
+ configTypes "main/pkg/config/types"
+ "main/pkg/utils"
"net/http"
"strings"
"time"
@@ -16,18 +18,28 @@ import (
const PaginationLimit = 1000
type RPC struct {
- URLs []string
- Logger zerolog.Logger
+ URLs []string
+ ProposalsType string
+ Logger zerolog.Logger
}
-func NewRPC(urls []string, logger zerolog.Logger) *RPC {
+func NewRPC(chainConfig *configTypes.Chain, logger zerolog.Logger) *RPC {
return &RPC{
- URLs: urls,
- Logger: logger.With().Str("component", "rpc").Logger(),
+ URLs: chainConfig.LCDEndpoints,
+ ProposalsType: chainConfig.ProposalsType,
+ Logger: logger.With().Str("component", "rpc").Logger(),
}
}
func (rpc *RPC) GetAllProposals() ([]types.Proposal, error) {
+ if rpc.ProposalsType == "v1" {
+ return rpc.GetAllV1Proposals()
+ }
+
+ return rpc.GetAllV1beta1Proposals()
+}
+
+func (rpc *RPC) GetAllV1beta1Proposals() ([]types.Proposal, error) {
proposals := []types.Proposal{}
offset := 0
@@ -39,7 +51,42 @@ func (rpc *RPC) GetAllProposals() ([]types.Proposal, error) {
offset,
)
- var batchProposals types.ProposalsRPCResponse
+ var batchProposals types.V1Beta1ProposalsRPCResponse
+ if err := rpc.Get(url, &batchProposals); err != nil {
+ return nil, err
+ }
+
+ if batchProposals.Message != "" {
+ return nil, errors.New(batchProposals.Message)
+ }
+
+ parsedProposals := utils.Map(batchProposals.Proposals, func(p types.V1beta1Proposal) types.Proposal {
+ return p.ToProposal()
+ })
+ proposals = append(proposals, parsedProposals...)
+ if len(batchProposals.Proposals) < PaginationLimit {
+ break
+ }
+
+ offset += PaginationLimit
+ }
+
+ return proposals, nil
+}
+
+func (rpc *RPC) GetAllV1Proposals() ([]types.Proposal, error) {
+ proposals := []types.Proposal{}
+ offset := 0
+
+ for {
+ url := fmt.Sprintf(
+ // 2 is for PROPOSAL_STATUS_VOTING_PERIOD
+ "/cosmos/gov/v1/proposals?pagination.limit=%d&pagination.offset=%d&proposal_status=2",
+ PaginationLimit,
+ offset,
+ )
+
+ var batchProposals types.V1ProposalsRPCResponse
if err := rpc.Get(url, &batchProposals); err != nil {
return nil, err
}
@@ -48,7 +95,10 @@ func (rpc *RPC) GetAllProposals() ([]types.Proposal, error) {
return nil, errors.New(batchProposals.Message)
}
- proposals = append(proposals, batchProposals.Proposals...)
+ parsedProposals := utils.Map(batchProposals.Proposals, func(p types.V1Proposal) types.Proposal {
+ return p.ToProposal()
+ })
+ proposals = append(proposals, parsedProposals...)
if len(batchProposals.Proposals) < PaginationLimit {
break
}
diff --git a/pkg/types/responses.go b/pkg/types/responses.go
index 701710a..60b44da 100644
--- a/pkg/types/responses.go
+++ b/pkg/types/responses.go
@@ -7,19 +7,22 @@ import (
"main/pkg/utils"
)
-type Proposal struct {
+// cosmos/gov/v1beta1/proposals?pagination.limit=1000&pagination.offset=0
+
+type V1beta1Proposal struct {
ProposalID string `json:"proposal_id"`
Status string `json:"status"`
Content *ProposalContent `json:"content"`
VotingEndTime time.Time `json:"voting_end_time"`
}
-func (p Proposal) GetTimeLeft() string {
- return utils.FormatDuration(time.Until(p.VotingEndTime).Round(time.Second))
-}
-
-func (p Proposal) GetProposalTime() string {
- return p.VotingEndTime.Format(time.RFC1123)
+func (p V1beta1Proposal) ToProposal() Proposal {
+ return Proposal{
+ ID: p.ProposalID,
+ Title: p.Content.Title,
+ Description: p.Content.Description,
+ EndTime: p.VotingEndTime,
+ }
}
type ProposalContent struct {
@@ -27,12 +30,50 @@ type ProposalContent struct {
Description string `json:"description"`
}
-type ProposalsRPCResponse struct {
- Code int64 `json:"code"`
- Message string `json:"message"`
- Proposals []Proposal `json:"proposals"`
+type V1Beta1ProposalsRPCResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Proposals []V1beta1Proposal `json:"proposals"`
+}
+
+// cosmos/gov/v1beta1/proposals?pagination.limit=1000&pagination.offset=0
+
+type V1ProposalMessage struct {
+ Content ProposalContent `json:"content"`
+}
+
+type V1Proposal struct {
+ ProposalID string `json:"id"`
+ Status string `json:"status"`
+ VotingEndTime time.Time `json:"voting_end_time"`
+ Messages []V1ProposalMessage `json:"messages"`
}
+func (p V1Proposal) ToProposal() Proposal {
+ titles := utils.Map(p.Messages, func(m V1ProposalMessage) string {
+ return m.Content.Title
+ })
+
+ descriptions := utils.Map(p.Messages, func(m V1ProposalMessage) string {
+ return m.Content.Description
+ })
+
+ return Proposal{
+ ID: p.ProposalID,
+ Title: strings.Join(titles, ", "),
+ Description: strings.Join(descriptions, ", "),
+ EndTime: p.VotingEndTime,
+ }
+}
+
+type V1ProposalsRPCResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Proposals []V1Proposal `json:"proposals"`
+}
+
+// cosmos/gov/v1beta1/proposals/:id/votes/:wallet
+
type Vote struct {
ProposalID string `json:"proposal_id"`
Voter string `json:"voter"`
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 9627c33..b99a5cb 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -2,6 +2,8 @@ package types
import (
"fmt"
+ "main/pkg/utils"
+ "time"
)
type Link struct {
@@ -16,3 +18,18 @@ func (l Link) Serialize() string {
return fmt.Sprintf("%s", l.Href, l.Name)
}
+
+type Proposal struct {
+ ID string
+ Title string
+ Description string
+ EndTime time.Time
+}
+
+func (p Proposal) GetTimeLeft() string {
+ return utils.FormatDuration(time.Until(p.EndTime).Round(time.Second))
+}
+
+func (p Proposal) GetProposalTime() string {
+ return p.EndTime.Format(time.RFC1123)
+}
diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go
index d85429b..8926ed1 100644
--- a/pkg/utils/utils.go
+++ b/pkg/utils/utils.go
@@ -27,6 +27,16 @@ func Map[T, V any](slice []T, f func(T) V) []V {
return result
}
+func Contains[T comparable](slice []T, elt T) bool {
+ for _, value := range slice {
+ if value == elt {
+ return true
+ }
+ }
+
+ return false
+}
+
func ResolveVote(value string) string {
votes := map[string]string{
"VOTE_OPTION_YES": "Yes",
diff --git a/templates/telegram/not_voted.html b/templates/telegram/not_voted.html
index 822f7e1..be3b08b 100644
--- a/templates/telegram/not_voted.html
+++ b/templates/telegram/not_voted.html
@@ -1,9 +1,9 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-🔴 Wallet {{ SerializeLink $walletLink }} hasn't voted on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
-{{ .Proposal.Content.Title }}
+🔴 Wallet {{ SerializeLink $walletLink }} hasn't voted on proposal {{ .Proposal.ID }} on {{ .Chain.GetName }}
+{{ .Proposal.Title }}
Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/proposals.html b/templates/telegram/proposals.html
index f01f07d..9ea5edf 100644
--- a/templates/telegram/proposals.html
+++ b/templates/telegram/proposals.html
@@ -9,7 +9,7 @@
{{- end }}
{{- range .ProposalVotes }}
{{- $proposalLink := $chain.GetProposalLink .Proposal }}
-Proposal #{{ .Proposal.ProposalID }}: {{ SerializeLink $proposalLink }} (voting ends in {{ .Proposal.GetTimeLeft }})
+Proposal #{{ .Proposal.ID }}: {{ SerializeLink $proposalLink }} (voting ends in {{ .Proposal.GetTimeLeft }})
{{- range $wallet, $vote := .Votes }}
{{- $walletLink := $chain.GetWalletLink $vote.Wallet -}}
{{- if $vote.IsError }}
diff --git a/templates/telegram/revoted.html b/templates/telegram/revoted.html
index 3e18eb3..fe12b39 100644
--- a/templates/telegram/revoted.html
+++ b/templates/telegram/revoted.html
@@ -1,11 +1,11 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-↔️ Wallet {{ SerializeLink $walletLink }} has changed its vote on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
-{{ .Proposal.Content.Title }}
+↔️ Wallet {{ SerializeLink $walletLink }} has changed its vote on proposal {{ .Proposal.ID }} on {{ .Chain.GetName }}
+{{ .Proposal.Title }}
Vote: {{ .Vote.ResolveVote }}
Old vote: {{ .OldVote.ResolveVote }}
Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/vote_query_error.html b/templates/telegram/vote_query_error.html
index ffd6dc5..3d5927a 100644
--- a/templates/telegram/vote_query_error.html
+++ b/templates/telegram/vote_query_error.html
@@ -1,5 +1,5 @@
❌ There was an error querying proposal on {{ .Chain.GetName }}
-Proposal ID: {{ .Proposal.ProposalID }}
+Proposal ID: {{ .Proposal.ID }}
Error text: {{ .Error }}
Sent by cosmos-proposals-checker.
\ No newline at end of file
diff --git a/templates/telegram/voted.html b/templates/telegram/voted.html
index 3f2d1cf..9c3cfd7 100644
--- a/templates/telegram/voted.html
+++ b/templates/telegram/voted.html
@@ -1,10 +1,10 @@
{{- $walletLink := .Chain.GetWalletLink .Wallet -}}
-✅ Wallet {{ SerializeLink $walletLink }} has voted on proposal {{ .Proposal.ProposalID }} on {{ .Chain.GetName }}
-{{ .Proposal.Content.Title }}
+✅ Wallet {{ SerializeLink $walletLink }} has voted on proposal {{ .Proposal.ID }} on {{ .Chain.GetName }}
+{{ .Proposal.Title }}
Vote: {{ .Vote.ResolveVote }}
Voting ends at: {{ .Proposal.GetProposalTime }} (in {{ .Proposal.GetTimeLeft }})
-{{ range .Chain.GetExplorerProposalsLinks .Proposal.ProposalID }}{{ SerializeLink .}}
+{{ range .Chain.GetExplorerProposalsLinks .Proposal.ID }}{{ SerializeLink .}}
{{ end }}
Sent by cosmos-proposals-checker.
\ No newline at end of file