diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5eba45c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,8 @@ + +# CODEOWNERS: https://help.github.com/articles/about-codeowners/ + +# NOTE: Order is important; the last matching pattern takes the +# most precedence. + +# Primary repo maintainers +* @ojo-network/core-devs \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f60de2a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: Release contract monitor + +on: + push: + branches: + - main + + # Sequence of patterns matched against refs/tags + tags: + - "v[0-9]+\\.[0-9]+\\.[0-9]+" # Official release version tags e.g. v2.0.5 + - "v[0-9]+\\.[0-9]+\\.[0-9]+-rc[0-9]+" # Release candidate tags e.g. v1.0.3-rc4 + - "v[0-9]+\\.[0-9]+\\.[0-9]+-alpha[0-9]+" # Alpha release testing tags e.g. v0.0.3-alpha1 + +permissions: + contents: write + id-token: write + +jobs: + goreleaser: + strategy: + matrix: + os: [ubuntu-latest, ubuntu-20.04] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-go@v4 + with: + go-version: 1.19 + cache: true + cache-dependency-path: go.sum + + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/*}" >> $GITHUB_ENV + + - name: Check GLIBC version + if: startsWith(matrix.os, 'ubuntu-') + run: | + echo "GLIBC_VERSION=$(ldd --version | grep ldd | awk '{print $NF}')" >> $GITHUB_ENV + + # Ref: https://goreleaser.com/limitations/semver + - name: Tag without prefix locally to avoid error in goreleaser + run: |- + git tag -d ${{ env.RELEASE_VERSION }} || echo "No such a tag exists before" + git tag ${{ env.RELEASE_VERSION }} HEAD + + - name: Build + uses: goreleaser/goreleaser-action@v4 + if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'Enable:ReleaseBuild') + with: + version: latest + args: build --rm-dist --skip-validate # skip validate skips initial sanity checks in order to be able to fully run + env: + GORELEASER_CURRENT_TAG: ${{ env.RELEASE_VERSION }} + GLIBC_VERSION: ${{ env.GLIBC_VERSION }} + + - name: release + if: startsWith(github.ref, 'refs/tags/') + uses: goreleaser/goreleaser-action@v4 + with: + # Note, we have to pin to v0.179.0 due to newer releases enforcing + # correct semantic versioning even when '--skip-validate' is provided. + # + # Ref: https://github.com/goreleaser/goreleaser/pull/2503 + version: v0.179.0 + args: release --rm-dist --skip-validate + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GORELEASER_CURRENT_TAG: ${{ env.RELEASE_VERSION }} + GLIBC_VERSION: ${{ env.GLIBC_VERSION }} diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..3030e96 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,48 @@ +project_name: contractMonitor + +env: + - CGO_ENABLED=1 + +before: + hooks: + - go mod download + +builds: + - main: ./ + id: "contractMonitor" + binary: contractMonitor + mod_timestamp: "{{ .CommitTimestamp }}" + flags: + - -trimpath + ldflags: + - -s -w -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} + goos: + - linux + goarch: + - amd64 + +archives: + - id: bin + format: binary + name_template: "{{ .Binary }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}-glibc-{{.Env.GLIBC_VERSION}}" + + - id: tarball + format: tar.gz + wrap_in_directory: true + format_overrides: + - goos: windows + format: zip + name_template: '{{ .Binary }}-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}-glibc-{{.Env.GLIBC_VERSION}}' + +release: + disable: false + +snapshot: + name_template: SNAPSHOT-{{ .Commit }} + +checksum: + name_template: 'SHA256SUMS-{{ replace .Version "version/" "cw-relayer-version-" }}-glibc-{{.Env.GLIBC_VERSION}}.txt' + algorithm: sha256 + +changelog: + skip: false \ No newline at end of file diff --git a/Makefile b/Makefile index a3eb177..bd7749c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ -BUILD_DIR ?= $(CURDIR)/build +build: + go build -o ./build/ ./... -build: go.sum - CGO_ENABLED=0 go build -mod=readonly -o $(BUILD_DIR)/monitor ./... \ No newline at end of file +start: + ${MAKE} build + ./contractMonitor ./config.toml + +.PHONY: build start \ No newline at end of file diff --git a/config.toml b/config.toml index 9c8bc88..a152077 100644 --- a/config.toml +++ b/config.toml @@ -1,11 +1,13 @@ -cron_interval="30s" +cron_interval="50s" -[address_map.juno-testnet] +[address_map.juno] contract_address = "juno1yqm8q56hjv8sd4r37wdhkkdt3wu45gc5ptrjmd9k0nhvavl0354qwcf249" relayer_address = "juno1rkhrfuq7k2k68k0hctrmv8efyxul6tgn8hny6y" denom="ujuno" -warning_threshold=5000000 +warning_threshold=500000000000000000 threshold=1000000 +report_median = true +report_deviation = true [network_rpc] -juno-testnet = "https://juno-api.polkachu.com" \ No newline at end of file +juno = "https://juno-api.polkachu.com" \ No newline at end of file diff --git a/config/config.go b/config/config.go index 64fe518..4562f32 100644 --- a/config/config.go +++ b/config/config.go @@ -15,11 +15,16 @@ type ( } Relayer struct { - ContractAddress string `mapstructure:"contract_address"` - RelayerAddress string `mapstructure:"relayer_address"` - Denom string `mapstructure:"denom"` - WarningThreshold int64 `mapstructure:"warning_threshold"` - Threshold int64 `mapstructure:"threshold"` + ContractAddress string `mapstructure:"contract_address" validate:"required"` + RelayerAddress string `mapstructure:"relayer_address" validate:"required"` + Denom string `mapstructure:"denom" validate:"required"` + + // threshold is less than warning threshold + WarningThreshold int64 `mapstructure:"warning_threshold" validate:"required"` + Threshold int64 `mapstructure:"threshold" validate:"required"` + + ReportMedian bool `mapstructure:"report_median" validate:"required"` + ReportDeviation bool `mapstructure:"report_deviation" validate:"required"` } AccessToken struct { diff --git a/go.mod b/go.mod index fe43170..ebed022 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/slack-go/slack v0.12.2 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 + golang.org/x/sync v0.1.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -16,8 +18,8 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/spf13/afero v1.9.5 // indirect @@ -28,5 +30,4 @@ require ( golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 14e43a3..8b54328 100644 --- a/go.sum +++ b/go.sum @@ -145,10 +145,12 @@ 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= @@ -292,6 +294,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -329,6 +333,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/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= diff --git a/monitor/execute.go b/monitor/execute.go index 3aaa465..031e2b6 100644 --- a/monitor/execute.go +++ b/monitor/execute.go @@ -16,21 +16,36 @@ import ( "github.com/ojo-network/contractMonitor/config" ) +// base64 encoding for queries const ( + // {"get_deviation_ref": {"symbol": "ATOM"}} deviation = "eyJnZXRfZGV2aWF0aW9uX3JlZiI6IHsic3ltYm9sIjogIkFUT00ifX0=" + + // {"get_median_ref": {"symbol": "ATOM"}} + median = "eyJnZXRfbWVkaWFuX3JlZiI6IHsic3ltYm9sIjogIkFUT00ifX0=" + + // {"get_ref": {"symbol": "ATOM"}} + rate = "eyJnZXRfcmVmIjogeyJzeW1ib2wiOiAiQVRPTSJ9fQ==" ) -var ( - slackchan chan slack.Attachment - errchan chan error - check sync.Mutex - globallist map[string]int64 +type IDS struct { + requestID int64 + medianID int64 + deviationID int64 +} - LowBalance = "Low Balance" - StaleRequestID = "No New Request id" +var ( + slackChan chan slack.Attachment + wg sync.WaitGroup ) -const RELAYER = "cw-relayer" +const ( + StaleRateRequestID = "No New Request id" + StaleMedianRequestID = "No New Median id" + StaleDeviationRequestID = "No New Deviation id" + LowBalance = "Low Balance" + Relayer = "Relayer" +) var rootCmd = &cobra.Command{ Use: "monitor [config-file]", @@ -44,6 +59,8 @@ func Execute() { fmt.Println(err) os.Exit(1) } + + rootCmd.AddCommand(getVersionCmd()) } func cwRelayerCmdHandler(cmd *cobra.Command, args []string) error { @@ -53,57 +70,46 @@ func cwRelayerCmdHandler(cmd *cobra.Command, args []string) error { } client := slack.New(accessToken.SlackToken, slack.OptionDebug(false)) - slackchan = make(chan slack.Attachment, len(config.AddressMap)) - - errchan = make(chan error, len(config.AddressMap)) - globallist = make(map[string]int64) + slackChan = make(chan slack.Attachment, len(config.AddressMap)) logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() - cronDuration, err := time.ParseDuration(config.CronInterval) if err != nil { return err } + logger.Info().Msg("Contract monitor starting...") + ctx, cancel := context.WithCancel(cmd.Context()) - var wg sync.WaitGroup for network, asset := range config.AddressMap { - globallist[asset.ContractAddress] = 0 rpc := config.NetworkRpc[network] wg.Add(1) - go func(ctx context.Context, threshold, warning int64, network, denom, rpc, relayer, contractAddress string) { - defer wg.Done() - for { - select { - case <-ctx.Done(): - return - default: - if err := checkBalance(threshold, warning, network, denom, rpc, relayer); err != nil { - errchan <- err - } - - err := checkQuery(network, rpc, contractAddress) - if err != nil { - errchan <- err - } - - time.Sleep(cronDuration) - } - } - }(ctx, asset.Threshold, asset.WarningThreshold, network, asset.Denom, rpc, asset.RelayerAddress, asset.ContractAddress) + newCosmwasmChecker( + ctx, + cronDuration, + asset.Threshold, + asset.WarningThreshold, + network, + asset.Denom, + rpc, + asset.ContractAddress, + asset.RelayerAddress, + asset.ReportMedian, + asset.ReportDeviation, + logger, + ) + + logger.Info().Str("network", network). + Str("relayer address", asset.RelayerAddress). + Str("contract address", asset.ContractAddress). + Msg("monitoring") } go func() { - for err := range errchan { - logger.Err(err).Msg("Error in monitoring") - } - }() - - go func() { - for attachment := range slackchan { + for attachment := range slackChan { _, timestamp, err := client.PostMessage(accessToken.SlackChannel, slack.MsgOptionAttachments(attachment)) if err != nil { - errchan <- err + logger.Err(err).Msg("error posting slack message") } logger.Info().Str("Posted at timestamp", timestamp).Msg("slack message posted") @@ -114,11 +120,10 @@ func cwRelayerCmdHandler(cmd *cobra.Command, args []string) error { for { select { case <-ctx.Done(): - logger.Info().Msg("closing monitor, waiting for all routines to exit") + logger.Info().Msg("closing monitor, waiting for all monitors to exit") - wg.Wait() // waiting for all goroutines to exit - close(errchan) - close(slackchan) + wg.Wait() + close(slackChan) return nil } } diff --git a/monitor/query.go b/monitor/query.go index 2a3ec9c..e153750 100644 --- a/monitor/query.go +++ b/monitor/query.go @@ -1,6 +1,7 @@ package monitor import ( + "context" "encoding/json" "fmt" "io" @@ -8,34 +9,112 @@ import ( "strconv" "time" - "github.com/slack-go/slack" + "github.com/rs/zerolog" + "golang.org/x/sync/errgroup" ) -type Response struct { - Data struct { - Rate string `json:"rate"` - ResolveTime string `json:"resolve_time"` - RequestID string `json:"request_id"` - } `json:"data"` -} +type ( + Response struct { + Data struct { + RequestID string `json:"request_id"` + } `json:"data"` + } + + Balance struct { + Denom string `json:"denom"` + Amount string `json:"amount"` + } -type Balance struct { - Denom string `json:"denom"` - Amount string `json:"amount"` + Pagination struct { + NextKey *string `json:"next_key"` + Total string `json:"total"` + } + + BalResponse struct { + Balances []Balance `json:"balances"` + Pagination Pagination `json:"pagination"` + } +) + +type cosmwasmChecker struct { + threshold int64 + warning int64 + network string + denom string + rpc string + contractAddress string + relayerAddress string + reportMedian bool + reportDeviation bool + requestID int64 + deviationID int64 + medianID int64 + logger zerolog.Logger } -type Pagination struct { - NextKey *string `json:"next_key"` - Total string `json:"total"` +func newCosmwasmChecker( + ctx context.Context, + duration time.Duration, + threshold int64, + warning int64, + network string, + denom string, + rpc string, + contractAddress string, + relayerAddress string, + reportMedian bool, + reportDeviation bool, + logger zerolog.Logger, +) { + checker := &cosmwasmChecker{ + threshold: threshold, + warning: warning, + network: network, + denom: denom, + rpc: rpc, + contractAddress: contractAddress, + relayerAddress: relayerAddress, + reportMedian: reportMedian, + reportDeviation: reportDeviation, + logger: logger, + } + + go checker.startCron(ctx, duration) } -type BalResponse struct { - Balances []Balance `json:"balances"` - Pagination Pagination `json:"pagination"` +func (c *cosmwasmChecker) startCron(ctx context.Context, duration time.Duration) { + for { + select { + case <-ctx.Done(): + wg.Done() + return + + default: + err := c.checkBalance() + if err != nil { + c.logger.Err(err). + Str("relayer", c.relayerAddress). + Str("contract", c.contractAddress). + Str("network", c.network). + Msg("Error in querying balance") + } + + err = c.checkQuery(ctx) + if err != nil { + c.logger.Err(err). + Str("relayer", c.relayerAddress). + Str("contract", c.contractAddress). + Str("network", c.network). + Msg("Error in querying request ids") + } + + time.Sleep(duration) + } + } } -func checkBalance(threshold, warning int64, network, denom, rpc, relayerAddress string) error { - bal := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s", rpc, relayerAddress) +func (c *cosmwasmChecker) checkBalance() error { + bal := fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s", c.rpc, c.relayerAddress) balResp, err := http.Get(bal) if err != nil { return err @@ -52,7 +131,7 @@ func checkBalance(threshold, warning int64, network, denom, rpc, relayerAddress return err } for _, balance := range balResponse.Balances { - if balance.Denom != denom { + if balance.Denom != c.denom { continue } @@ -61,112 +140,118 @@ func checkBalance(threshold, warning int64, network, denom, rpc, relayerAddress return err } - if amount <= warning { - slackchan <- createLowBalanceAttachment((amount <= warning) && (amount > threshold), balance.Amount, denom, relayerAddress, network) + if amount <= c.warning { + slackChan <- createLowBalanceAttachment( + (amount <= c.warning) && (amount > c.threshold), + balance.Amount, + c.denom, + c.relayerAddress, + c.network, + ) } } return nil } -func checkQuery(network, rpc, address string) error { - url := fmt.Sprintf("%s/cosmwasm/wasm/v1/contract/%s/smart/%s", rpc, address, deviation) - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() +func (c *cosmwasmChecker) checkQuery(ctx context.Context) error { + g, _ := errgroup.WithContext(ctx) - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return err - } + g.Go( + func() error { + num, err := c.returnLatestID(rate) + if err != nil { + return err + } - var response Response - if err := json.Unmarshal(responseBody, &response); err != nil { - return err - } + if num <= c.requestID { + slackChan <- createStaleRequestIDAttachment( + StaleRateRequestID, + c.contractAddress, + c.network, + c.requestID, + num, + ) + return nil + } - num, err := strconv.ParseInt(response.Data.RequestID, 10, 64) - if err != nil { - return err - } + c.requestID = num + return nil + }, + ) - check.Lock() - defer check.Unlock() + if c.reportDeviation { + g.Go( + func() error { + num, err := c.returnLatestID(deviation) + if err != nil { + return err + } - requestID := globallist[address] - globallist[address] = num - if num <= requestID { - slackchan <- createStaleRequestIDAttachment(num, response.Data.RequestID, address, network) + if num <= c.deviationID { + slackChan <- createStaleRequestIDAttachment( + StaleDeviationRequestID, + c.contractAddress, + c.network, + c.deviationID, + num, + ) + return nil + } + + // update to latest id + c.deviationID = num + return nil + }, + ) } - return nil -} + if c.reportMedian { + g.Go( + func() error { + num, err := c.returnLatestID(median) + if err != nil { + return err + } -func createLowBalanceAttachment(warning bool, balance, denom, relayerAddress, network string) slack.Attachment { - attachment := slack.Attachment{ - Pretext: fmt.Sprintf("*Network*: %s\n*Relayer*: %s", network, RELAYER), - Title: fmt.Sprintf(":exclamation: %s", LowBalance), - Color: "danger", - Fields: []slack.AttachmentField{ - { - Title: "Relayer Address", - Value: fmt.Sprintf("```%s```", relayerAddress), - Short: false, - }, - { - Title: "Current balance", - Value: fmt.Sprintf("```%s%s```", balance, denom), - Short: true, - }, - { - Title: "Network", - Value: fmt.Sprintf("```%s```", network), - Short: true, + if num <= c.medianID { + slackChan <- createStaleRequestIDAttachment( + StaleMedianRequestID, + c.contractAddress, + c.network, + c.medianID, + num, + ) + return nil + } + + // update to latest id + c.medianID = num + return nil }, - }, - Footer: "Monitor Bot", - Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), + ) } - if warning { - attachment.Color = "ff9966" + return g.Wait() +} + +func (c *cosmwasmChecker) returnLatestID(request string) (int64, error) { + url := fmt.Sprintf("%s/cosmwasm/wasm/v1/contract/%s/smart/%s", c.rpc, c.contractAddress, request) + resp, err := http.Get(url) + if err != nil { + return -1, err } + defer resp.Body.Close() - return attachment -} + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return -1, err + } -func createStaleRequestIDAttachment(oldRequestID int64, currentRequestID string, contractAddress, network string) slack.Attachment { - attachment := slack.Attachment{ - Pretext: fmt.Sprintf("*Network*: %s\n*Relayer*: %s", network, RELAYER), - Title: fmt.Sprintf(":exclamation: %s", StaleRequestID), - Color: "danger", - Fields: []slack.AttachmentField{ - { - Title: "Contract Address", - Value: fmt.Sprintf("```%s```", contractAddress), - Short: false, - }, - { - Title: "Current Request ID", - Value: fmt.Sprintf("```%s```", currentRequestID), - Short: true, - }, - { - Title: "Old Request ID", - Value: fmt.Sprintf("```%s```", strconv.FormatInt(oldRequestID, 10)), - Short: true, - }, - { - Title: "Network", - Value: fmt.Sprintf("```%s```", network), - Short: false, - }, - }, - Footer: "Monitor Bot", - Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), + var response Response + if err := json.Unmarshal(responseBody, &response); err != nil { + return -1, err } - return attachment + return strconv.ParseInt(response.Data.RequestID, 10, 64) } diff --git a/monitor/slack.go b/monitor/slack.go new file mode 100644 index 0000000..c35abb2 --- /dev/null +++ b/monitor/slack.go @@ -0,0 +1,77 @@ +package monitor + +import ( + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/slack-go/slack" +) + +func createLowBalanceAttachment(warning bool, balance, denom, relayerAddress, network string) slack.Attachment { + attachment := slack.Attachment{ + Pretext: fmt.Sprintf("*Network*: %s\n*Relayer*: %s", network, Relayer), + Title: fmt.Sprintf(":exclamation: %s", LowBalance), + Color: "danger", + Fields: []slack.AttachmentField{ + { + Title: "Relayer Address", + Value: fmt.Sprintf("```%s```", relayerAddress), + Short: false, + }, + { + Title: "Current balance", + Value: fmt.Sprintf("```%s%s```", balance, denom), + Short: true, + }, + { + Title: "Network", + Value: fmt.Sprintf("```%s```", network), + Short: true, + }, + }, + Footer: "Monitor Bot", + Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), + } + + if warning { + attachment.Color = "ff9966" + } + + return attachment +} + +func createStaleRequestIDAttachment(requestTitle, contractAddress, network string, oldRequestID, currentRequestID int64) slack.Attachment { + attachment := slack.Attachment{ + Pretext: fmt.Sprintf("*Network*: %s\n*Relayer*: %s", network, Relayer), + Title: fmt.Sprintf(":exclamation: %s", requestTitle), + Color: "danger", + Fields: []slack.AttachmentField{ + { + Title: "Contract Address", + Value: fmt.Sprintf("```%s```", contractAddress), + Short: false, + }, + { + Title: "Current Request ID", + Value: fmt.Sprintf("```%d```", currentRequestID), + Short: true, + }, + { + Title: "Old Request ID", + Value: fmt.Sprintf("```%d```", oldRequestID), + Short: true, + }, + { + Title: "Network", + Value: fmt.Sprintf("```%s```", network), + Short: false, + }, + }, + Footer: "Monitor Bot", + Ts: json.Number(strconv.FormatInt(time.Now().Unix(), 10)), + } + + return attachment +} diff --git a/monitor/version.go b/monitor/version.go new file mode 100644 index 0000000..d1c9d35 --- /dev/null +++ b/monitor/version.go @@ -0,0 +1,70 @@ +package monitor + +import ( + "encoding/json" + "fmt" + "runtime" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +const ( + flagFormat = "format" +) + +var ( + // Version defines the application version (defined at compile time) + Version = "" + + // Commit defines the application commit hash (defined at compile time) + Commit = "" + + versionFormat string +) + +type versionInfo struct { + Version string `json:"version" yaml:"version"` + Commit string `json:"commit" yaml:"commit"` + Go string `json:"go" yaml:"go"` +} + +func getVersionCmd() *cobra.Command { + versionCmd := &cobra.Command{ + Use: "version", + Short: "Print binary version information", + RunE: func(cmd *cobra.Command, args []string) error { + verInfo := versionInfo{ + Version: Version, + Commit: Commit, + Go: fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH), + } + + var bz []byte + + var err error + switch versionFormat { + case "json": + bz, err = json.Marshal(verInfo) + + default: + bz, err = yaml.Marshal(&verInfo) + } + if err != nil { + return err + } + + _, err = fmt.Println(string(bz)) + return err + }, + } + + versionCmd.Flags().StringVar( + &versionFormat, + flagFormat, + "text", + "Print the version in the given format (text|json)", + ) + + return versionCmd +}