diff --git a/go.mod b/go.mod index 1448f82..ee0a05d 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,19 @@ module github.com/firstset/tenderduty/v2 -go 1.18 +go 1.21 require ( github.com/PagerDuty/go-pagerduty v1.5.1 + github.com/cosmos/btcutil v1.0.4 github.com/cosmos/cosmos-sdk v0.45.11 github.com/go-passwd/validator v0.0.0-20180902184246-0b4c967e436b github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/go-yaml/yaml v2.1.0+incompatible github.com/gorilla/websocket v1.5.0 + github.com/near/borsh-go v0.3.1 github.com/prometheus/client_golang v1.12.2 github.com/tendermint/tendermint v0.34.24 github.com/textileio/go-threads v1.1.5 - github.com/near/borsh-go v0.3.1 golang.org/x/crypto v0.1.0 golang.org/x/term v0.1.0 ) @@ -29,7 +30,6 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/confio/ics23/go v0.7.0 // indirect - github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/iavl v0.19.4 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect diff --git a/go.sum b/go.sum index c3f6a15..24948fb 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,7 @@ github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI 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/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= +github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= 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/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= @@ -114,12 +115,14 @@ github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MR github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= 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/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -155,6 +158,7 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coinbase/rosetta-sdk-go v0.7.0 h1:lmTO/JEpCvZgpbkOITL95rA80CPKb5CtMzLaqF2mCNg= +github.com/coinbase/rosetta-sdk-go v0.7.0/go.mod h1:7nD3oBPIiHqhRprqvMgPoGxe/nyq3yftRmpsy29coWE= github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -175,7 +179,6 @@ 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/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= @@ -185,6 +188,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= +github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= 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/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= @@ -240,19 +244,25 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= 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.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 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.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -325,6 +335,7 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= +github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.3.1/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= @@ -385,6 +396,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -398,6 +410,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 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= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -423,6 +436,7 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/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/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= @@ -650,6 +664,7 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 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= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -662,12 +677,15 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/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/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-addr-util v0.1.0/go.mod h1:6I3ZYuFr2O/9D+SoyM0zEw0EF3YkldtTX406BpdQMqw= @@ -952,6 +970,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -979,6 +998,7 @@ github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0Em github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= 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/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= @@ -1110,6 +1130,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/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= +github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1187,6 +1208,7 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= 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= 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= @@ -1200,9 +1222,12 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= 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= @@ -1455,8 +1480,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 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= @@ -1558,8 +1581,6 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1680,15 +1701,11 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= 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= @@ -1700,8 +1717,6 @@ 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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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= @@ -1904,8 +1919,6 @@ 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.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= diff --git a/main.go b/main.go index 0334f3c..1aae37d 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,7 @@ import ( _ "embed" "flag" "fmt" - "log" + "log/slog" "os" "syscall" @@ -48,7 +48,8 @@ func main() { fmt.Print("Please enter the encryption password: ") pass, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { - log.Fatal(err) + slog.Error("failed to read password", "err", err) + os.Exit(1) } fmt.Println("") password = string(pass) @@ -62,13 +63,14 @@ func main() { e = td2.EncryptedConfig(configFile, encryptedFile, password, true) } if e != nil { - log.Fatalln(e) + slog.Error("failed to process encrypted config", "err", e) + os.Exit(1) } os.Exit(0) } err := td2.Run(configFile, stateFile, chainConfigDirectory, &password, devMode) if err != nil { - log.Println(err.Error(), "... exiting.") + slog.Error("tenderduty exiting", "err", err) } } diff --git a/td2/alert.go b/td2/alert.go index 74e23ee..b3a412b 100644 --- a/td2/alert.go +++ b/td2/alert.go @@ -5,7 +5,7 @@ import ( "context" "encoding/json" "fmt" - "log" + "log/slog" "math" "net/http" "slices" @@ -184,7 +184,7 @@ func shouldNotify(msg *alertMsg, dest notifyDest) bool { if strings.HasPrefix(msg.uniqueId, "UnvotedGovernanceProposal") { // Check if it has been 6 hours since the last (re-)send if whichMap[msg.uniqueId].SentTime.Before(time.Now().Add(-1 * time.Duration(td.GovernanceAlertsReminderInterval) * time.Hour)) { - l(fmt.Sprintf("🔄 RE-SENDING ALERT on %s (%s) - notifying %s", msg.chain, msg.message, service)) + l(slog.LevelInfo, fmt.Sprintf("🔄 RE-SENDING ALERT on %s (%s) - notifying %s", msg.chain, msg.message, service)) cache := alertMsgCache{ Message: msg.message, SentTime: time.Now(), @@ -197,11 +197,11 @@ func shouldNotify(msg *alertMsg, dest notifyDest) bool { case !whichMap[msg.uniqueId].SentTime.IsZero() && msg.resolved: // alarm is cleared delete(whichMap, msg.uniqueId) - l(fmt.Sprintf("💜 Resolved alarm on %s (%s) - notifying %s", msg.chain, msg.message, service)) + l(slog.LevelInfo, fmt.Sprintf("💜 Resolved alarm on %s (%s) - notifying %s", msg.chain, msg.message, service)) return true case msg.resolved: // it looks like we got a duplicate resolution or suppressed it. Note it and move on: - l(fmt.Sprintf("😕 Not clearing alarm on %s (%s) - no corresponding alert %s", msg.chain, msg.message, service)) + l(slog.LevelWarn, fmt.Sprintf("😕 Not clearing alarm on %s (%s) - no corresponding alert %s", msg.chain, msg.message, service)) return false } @@ -212,7 +212,7 @@ func shouldNotify(msg *alertMsg, dest notifyDest) bool { // for pagerduty we perform some basic flap detection if dest == pd && msg.pd && alarms.flappingAlarms[msg.chain][msg.uniqueId].SentTime.After(time.Now().Add(-5*time.Minute)) { - l("🛑 flapping detected - suppressing pagerduty notification:", msg.chain, msg.message) + l(slog.LevelWarn, "🛑 flapping detected - suppressing pagerduty notification:", msg.chain, msg.message) return false } else if dest == pd && msg.pd { cache := alertMsgCache{ @@ -222,7 +222,7 @@ func shouldNotify(msg *alertMsg, dest notifyDest) bool { alarms.flappingAlarms[msg.chain][msg.uniqueId] = cache } - l(fmt.Sprintf("🚨 ALERT new alarm on %s (%s) - notifying %s", msg.chain, msg.message, service)) + l(slog.LevelInfo, fmt.Sprintf("🚨 ALERT new alarm on %s (%s) - notifying %s", msg.chain, msg.message, service)) cache := alertMsgCache{ Message: msg.message, SentTime: time.Now(), @@ -301,27 +301,27 @@ func notifyDiscord(msg *alertMsg) (err error) { client := &http.Client{} data, err := json.MarshalIndent(discPost, "", " ") if err != nil { - l("âš ī¸ Could not notify discord!", err) + l(slog.LevelWarn, "âš ī¸ Could not notify discord!", err) return err } req, err := http.NewRequest("POST", msg.discHook, bytes.NewBuffer(data)) if err != nil { - l("âš ī¸ Could not notify discord!", err) + l(slog.LevelWarn, "âš ī¸ Could not notify discord!", err) return err } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { - l("âš ī¸ Could not notify discord!", err) + l(slog.LevelWarn, "âš ī¸ Could not notify discord!", err) return err } _ = resp.Body.Close() if resp.StatusCode != 204 { - log.Println(resp) - l("âš ī¸ Could not notify discord! Returned", resp.StatusCode) + slog.Warn("discord webhook returned non-success response", "status", resp.StatusCode) + l(slog.LevelWarn, "âš ī¸ Could not notify discord! Returned", resp.StatusCode) return err } return nil @@ -364,7 +364,7 @@ func notifyTg(msg *alertMsg) (err error) { } bot, err := tgbotapi.NewBotAPI(msg.tgKey) if err != nil { - l("notify telegram:", err) + l(slog.LevelWarn, "notify telegram:", err) return } @@ -376,7 +376,7 @@ func notifyTg(msg *alertMsg) (err error) { mc := tgbotapi.NewMessageToChannel(msg.tgChannel, fmt.Sprintf("%s: %s: %s", msg.chain, prefix, msg.message)) _, err = bot.Send(mc) if err != nil { - l("telegram send:", err) + l(slog.LevelWarn, "telegram send:", err) } return err } @@ -390,7 +390,7 @@ func notifyPagerduty(msg *alertMsg) (err error) { } // key from the example, don't spam their api if msg.key == "aaaaaaaaaaaabbbbbbbbbbbbbcccccccccccc" { - l("invalid pagerduty key") + l(slog.LevelWarn, "invalid pagerduty key") return } action := "trigger" @@ -485,13 +485,13 @@ func notifyWebhook(msg *alertMsg) (err error) { data, err := json.Marshal(payload) if err != nil { - l("âš ī¸ Could not marshal webhook payload!", err) + l(slog.LevelWarn, "âš ī¸ Could not marshal webhook payload!", err) return err } req, err := http.NewRequest("POST", msg.whURL, bytes.NewBuffer(data)) if err != nil { - l("âš ī¸ Could not create webhook request!", err) + l(slog.LevelWarn, "âš ī¸ Could not create webhook request!", err) return err } req.Header.Set("Content-Type", "application/json") @@ -499,13 +499,13 @@ func notifyWebhook(msg *alertMsg) (err error) { client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { - l("âš ī¸ Could not send webhook!", err) + l(slog.LevelWarn, "âš ī¸ Could not send webhook!", err) return err } _ = resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { - l("âš ī¸ Webhook returned non-success status:", resp.StatusCode) + l(slog.LevelWarn, "âš ī¸ Webhook returned non-success status:", resp.StatusCode) return fmt.Errorf("webhook returned status %d for %s", resp.StatusCode, msg.chain) } @@ -660,7 +660,7 @@ func evaluateNoRPCEndpointsAlert(cc *ChainConfig, noNodesSec *int) (bool, bool) *noNodesSec += 2 if *noNodesSec <= 60*td.NodeDownMin { if *noNodesSec%20 == 0 { - l(fmt.Sprintf("no nodes available on %s for %d seconds, deferring alarm", cc.ChainId, *noNodesSec)) + l(slog.LevelInfo, fmt.Sprintf("no nodes available on %s for %d seconds, deferring alarm", cc.ChainId, *noNodesSec)) } } else { if !alarms.exist(cc.name, alertID) { @@ -1006,12 +1006,14 @@ func evaluateUnclaimedRewardsAlert(cc *ChainConfig) (bool, bool) { return alert, resolved } + convertedToDisplay := false if cc.denomMetadata != nil { convertedCoins, err := utils.ConvertDecCoinToDisplayUnit(nativeCoins, *cc.denomMetadata) if err == nil { nativeCoins = *convertedCoins + convertedToDisplay = true } else { - l(fmt.Errorf("cannot convert rewards/commission to display unit for %s, err: %w", cc.name, err)) + l(slog.LevelDebug, fmt.Errorf("cannot convert rewards/commission to display unit for %s, err: %w", cc.name, err)) } } @@ -1024,26 +1026,17 @@ func evaluateUnclaimedRewardsAlert(cc *ChainConfig) (bool, bool) { return alert, resolved } - if err := github_com_cosmos_cosmos_sdk_types.ValidateDenom(targetDenom); err != nil { - fallback := "" - if len(nativeCoins) > 0 { - if errFallback := github_com_cosmos_cosmos_sdk_types.ValidateDenom(nativeCoins[0].Denom); errFallback == nil { - fallback = nativeCoins[0].Denom - } - } - if fallback == "" { - l(fmt.Errorf("invalid target denom %q for %s: %w", targetDenom, cc.name, err)) - return alert, resolved - } - l(fmt.Errorf("invalid target denom %q for %s: %w; falling back to %q", targetDenom, cc.name, err, fallback)) - targetDenom = fallback + // coinPrice is for the chain display token. If metadata exists but conversion to display + // failed, the amount may still be in base units (e.g. uom), which would misprice by 10^exp. + // Skip this alert evaluation rather than raising a false amount-based alert. + if cc.denomMetadata != nil && !convertedToDisplay { + l(slog.LevelDebug, fmt.Errorf("skipping unclaimed rewards pricing for %s because rewards are not confirmed in display units", cc.name)) + return alert, resolved } - totalRewards := github_com_cosmos_cosmos_sdk_types.NewDecCoinFromDec(targetDenom, totalAmount) - coinPrice, err := td.coinMarketCapClient.GetPrice(td.ctx, cc.Slug) - if err == nil { - totalRewardsConverted := totalRewards.Amount.MustFloat64() * coinPrice.Price + if err == nil && convertedToDisplay { + totalRewardsConverted := totalAmount.MustFloat64() * coinPrice.Price threshold := floatVal(cc.Alerts.UnclaimedRewardsThreshold) alertID := fmt.Sprintf("UnclaimedRewards_%s", cc.ValAddress) diff --git a/td2/dashboard/server.go b/td2/dashboard/server.go index 4d1801b..3d82d9f 100644 --- a/td2/dashboard/server.go +++ b/td2/dashboard/server.go @@ -4,8 +4,9 @@ import ( "embed" "encoding/json" "io/fs" - "log" + "log/slog" "net/http" + "os" "regexp" "sort" "sync" @@ -27,7 +28,8 @@ func Serve(port string, updates chan *ChainStatus, logs chan LogMessage, hideLog var err error rootDir, err = fs.Sub(Content, "static") if err != nil { - log.Fatalln(err) + slog.Error("failed to load embedded static content", "err", err) + os.Exit(1) } var cast broadcast.Broadcaster @@ -150,7 +152,8 @@ func Serve(port string, updates chan *ChainStatus, logs chan LogMessage, hideLog } err = server.ListenAndServe() cast.Discard() - log.Fatal("tenderduty dashboard server failed", err) + slog.Error("tenderduty dashboard server failed", "err", err) + os.Exit(1) } // CacheHandler implements the Handler interface with a Cache-Control set on responses diff --git a/td2/encryption.go b/td2/encryption.go index 6d40efd..e7cbc49 100644 --- a/td2/encryption.go +++ b/td2/encryption.go @@ -11,7 +11,7 @@ import ( "errors" "fmt" "io" - "log" + "log/slog" "os" "github.com/go-passwd/validator" @@ -227,6 +227,6 @@ func EncryptedConfig(plaintext, ciphertext, pass string, decrypting bool) error if decrypting { fileType = "decrypted" } - log.Printf("wrote %d bytes to %s file %s\n", size, fileType, outfile) + slog.Info("wrote file", "bytes", size, "type", fileType, "file", outfile) return nil } diff --git a/td2/init.go b/td2/init.go index d9b81e6..cd1405c 100644 --- a/td2/init.go +++ b/td2/init.go @@ -1,9 +1,10 @@ package tenderduty import ( + "context" "embed" "fmt" - "log" + "log/slog" "os" "strings" "time" @@ -15,21 +16,50 @@ import ( var content embed.FS func init() { - log.SetFlags(log.LstdFlags) - log.SetOutput(os.Stderr) + level := slog.LevelInfo + if envLevel := strings.TrimSpace(strings.ToLower(os.Getenv("LOG_LEVEL"))); envLevel != "" { + switch envLevel { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn", "warning": + level = slog.LevelWarn + case "error": + level = slog.LevelError + } + } + handler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: level, + }) + slog.SetDefault(slog.New(handler)) + slog.Info("logging configured", "level", level.String(), "hint", "set LOG_LEVEL to debug|info|warn|error") dash.Content = content // use a channel for logging, two reasons: several logs could hit at once (formatting,) and to broadcast // messages to the monitoring dashboard go func() { for msg := range logs { - msg = strings.TrimRight(strings.TrimLeft(fmt.Sprint(msg), "["), "]") - log.Println("tenderduty | ", msg) + parts, ok := msg.([]any) + if !ok || len(parts) == 0 { + continue + } + level, ok := parts[0].(slog.Level) + if !ok { + level = slog.LevelInfo + } else { + parts = parts[1:] + } + if len(parts) == 0 { + continue + } + msgStr := strings.TrimRight(strings.TrimLeft(fmt.Sprint(parts...), "["), "]") + slog.Log(context.Background(), level, "tenderduty | "+msgStr) if td.EnableDash && !td.HideLogs && td.logChan != nil { td.logChan <- dash.LogMessage{ MsgType: "log", Ts: time.Now().UTC().Unix(), - Msg: msg.(string), + Msg: msgStr, } } } @@ -39,5 +69,20 @@ func init() { var logs = make(chan any) func l(v ...any) { - logs <- v + if len(v) == 0 { + return + } + level := slog.LevelInfo + switch t := v[0].(type) { + case slog.Level: + level = t + v = v[1:] + case slog.Leveler: + level = t.Level() + v = v[1:] + } + if len(v) == 0 { + return + } + logs <- append([]any{level}, v...) } diff --git a/td2/prometheus.go b/td2/prometheus.go index 84746ec..44515ec 100644 --- a/td2/prometheus.go +++ b/td2/prometheus.go @@ -3,8 +3,9 @@ package tenderduty import ( "context" "fmt" - "log" + "log/slog" "net/http" + "os" "sync" "time" @@ -176,7 +177,7 @@ func prometheusExporter(ctx context.Context, updates chan *promUpdate) { promMux := http.NewServeMux() - l(fmt.Sprintf("📊 Serving prometheus metrics at 0.0.0.0:%d/metrics", td.PrometheusListenPort)) + l(slog.LevelInfo, fmt.Sprintf("📊 Serving prometheus metrics at 0.0.0.0:%d/metrics", td.PrometheusListenPort)) promMux.Handle("/metrics", promhttp.Handler()) promSrv := &http.Server{ Addr: fmt.Sprintf(":%d", td.PrometheusListenPort), @@ -186,5 +187,8 @@ func prometheusExporter(ctx context.Context, updates chan *promUpdate) { IdleTimeout: 120 * time.Second, ReadHeaderTimeout: 20 * time.Second, } - log.Fatal(promSrv.ListenAndServe()) + if err := promSrv.ListenAndServe(); err != nil { + slog.Error("prometheus server failed", "err", err) + os.Exit(1) + } } diff --git a/td2/provider-default.go b/td2/provider-default.go index dc7f55a..f5aa05a 100644 --- a/td2/provider-default.go +++ b/td2/provider-default.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -149,13 +150,13 @@ func (d *DefaultProvider) QueryUnvotedOpenProposals(ctx context.Context) ([]gov. // For each proposal, check if the validator has voted accAddress, err := ConvertValopertToAccAddress(d.ChainConfig.ValAddress) if err != nil { - l(fmt.Sprintf("âš ī¸ Cannot convert valoper to account address: %v", err)) + l(slog.LevelWarn, fmt.Sprintf("âš ī¸ Cannot convert valoper to account address: %v", err)) continue } hasVoted, err := d.CheckIfValidatorVoted(ctx, proposal.ProposalId, accAddress) if err != nil { - l(fmt.Sprintf("âš ī¸ Error checking if validator voted: %v", err)) + l(slog.LevelWarn, fmt.Sprintf("âš ī¸ Error checking if validator voted: %v", err)) } if !hasVoted { diff --git a/td2/provider-namada.go b/td2/provider-namada.go index 12132cb..c102df8 100644 --- a/td2/provider-namada.go +++ b/td2/provider-namada.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "net/url" "slices" @@ -74,7 +75,7 @@ func getVotingPeriodProposals(httpClient *http.Client, indexers []string) ([]gov govProposal, err := namadaProposal.ToGovProposal() if err != nil { // Log error but continue with other proposals - l(fmt.Sprintf("Failed to convert proposal %s: %v", namadaProposal.ID, err)) + l(slog.LevelWarn, fmt.Sprintf("Failed to convert proposal %s: %v", namadaProposal.ID, err)) continue } if !slices.Contains(votingPeriodProposalIds, namadaProposal.ID) { diff --git a/td2/rpc.go b/td2/rpc.go index c1a758d..df89131 100644 --- a/td2/rpc.go +++ b/td2/rpc.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "regexp" @@ -23,9 +24,9 @@ func (cc *ChainConfig) newRpc() error { // This is done early so we can use it for RPC fallback and chain params if cc.cosmosDirectoryData == nil { if err := cc.loadCosmosDirectoryData(); err != nil { - l("â„šī¸ cosmos.directory data not available for", cc.name, "(chain_name: "+cc.getEffectiveChainName()+", chain_id: "+cc.ChainId+")", "-", err) + l(slog.LevelWarn, "â„šī¸ cosmos.directory data not available for", cc.name, "(chain_name: "+cc.getEffectiveChainName()+", chain_id: "+cc.ChainId+")", "-", err) } else { - l("✅ loaded cosmos.directory data for", cc.name, "(chain_name: "+cc.getEffectiveChainName()+", chain_id: "+cc.ChainId+")") + l(slog.LevelInfo, "✅ loaded cosmos.directory data for", cc.name, "(chain_name: "+cc.getEffectiveChainName()+", chain_id: "+cc.ChainId+")") } } @@ -41,14 +42,14 @@ func (cc *ChainConfig) newRpc() error { _, err := url.Parse(u) if err != nil { msg = fmt.Sprintf("❌ could not parse url %s: (%s) %s", cc.name, u, err) - l(msg) + l(slog.LevelInfo, msg) down = true return } cc.client, err = rpchttp.New(u, "/websocket") if err != nil { msg = fmt.Sprintf("❌ could not connect client for %s: (%s) %s", cc.name, u, err) - l(msg) + l(slog.LevelInfo, msg) down = true return } @@ -60,7 +61,7 @@ func (cc *ChainConfig) newRpc() error { if err != nil { msg = fmt.Sprintf("❌ could not get status for %s: (%s) %s", cc.name, u, err) down = true - l(msg) + l(slog.LevelInfo, msg) return } network, catching_up = n, c @@ -70,14 +71,14 @@ func (cc *ChainConfig) newRpc() error { if network != cc.ChainId { msg = fmt.Sprintf("chain id %s on %s does not match, expected %s, skipping", network, u, cc.ChainId) down = true - l(msg) + l(slog.LevelInfo, msg) return } if catching_up { msg = fmt.Sprint("đŸĸ node is not synced, skipping ", u) syncing = true down = true - l(msg) + l(slog.LevelInfo, msg) return } cc.noNodes = false @@ -107,25 +108,25 @@ func (cc *ChainConfig) newRpc() error { chainName := cc.getEffectiveChainName() u := getRegistryUrlByChainName(chainName) node := guessPublicEndpoint(u) - l(cc.ChainId, "⛑ attempting to use cosmos.directory fallback node (chain_name:", chainName+")", node) + l(slog.LevelInfo, cc.ChainId, "⛑ attempting to use cosmos.directory fallback node (chain_name:", chainName+")", node) if _, failed, _ := tryUrl(node); !failed { - l(cc.ChainId, "⛑ connected to cosmos.directory endpoint", node) + l(slog.LevelInfo, cc.ChainId, "⛑ connected to cosmos.directory endpoint", node) return nil } - l("âš ī¸ could not connect to cosmos.directory fallback for chain_name:", chainName) + l(slog.LevelWarn, "âš ī¸ could not connect to cosmos.directory fallback for chain_name:", chainName) } // Legacy fallback using chain_id lookup (when PublicFallback is explicitly enabled) if cc.PublicFallback { if u, ok := getRegistryUrl(cc.ChainId); ok { node := guessPublicEndpoint(u) - l(cc.ChainId, "⛑ attempting to use public fallback node (chain_id lookup)", node) + l(slog.LevelInfo, cc.ChainId, "⛑ attempting to use public fallback node (chain_id lookup)", node) if _, failed, _ := tryUrl(node); !failed { - l(cc.ChainId, "⛑ connected to public endpoint", node) + l(slog.LevelInfo, cc.ChainId, "⛑ connected to public endpoint", node) return nil } } else { - l("could not find a public endpoint for", cc.ChainId) + l(slog.LevelWarn, "could not find a public endpoint for", cc.ChainId) } } cc.noNodes = true @@ -198,7 +199,7 @@ func (cc *ChainConfig) monitorHealth(ctx context.Context, chainName string) { if td.Prom { td.statsChan <- cc.mkUpdate(metricNodeDownSeconds, time.Since(node.downSince).Seconds(), node.Url) } - l("âš ī¸ " + node.lastMsg) + l(slog.LevelWarn, "âš ī¸ "+node.lastMsg) } c, e := rpchttp.New(node.Url, "/websocket") if e != nil { @@ -231,14 +232,14 @@ func (cc *ChainConfig) monitorHealth(ctx context.Context, chainName string) { node.syncing = false node.downSince = time.Unix(0, 0) cc.noNodes = false - l(fmt.Sprintf("đŸŸĸ %-12s node %s is healthy", chainName, node.Url)) + l(slog.LevelInfo, fmt.Sprintf("đŸŸĸ %-12s node %s is healthy", chainName, node.Url)) }(node) } if cc.client == nil { e := cc.newRpc() if e != nil { - l("đŸ’Ĩ", cc.ChainId, e) + l(slog.LevelError, "đŸ’Ĩ", cc.ChainId, e) } } if cc.valInfo != nil { @@ -260,7 +261,7 @@ func (cc *ChainConfig) monitorHealth(ctx context.Context, chainName string) { } err = cc.GetValInfo(false) if err != nil { - l("❓ refreshing signing info for", cc.ValAddress, err) + l(slog.LevelWarn, "❓ refreshing signing info for", cc.ValAddress, err) } } } @@ -277,9 +278,9 @@ func (c *Config) pingHealthcheck() { for range ticker.C { _, err := http.Get(c.Healthcheck.PingURL) if err != nil { - l(fmt.Sprintf("❌ Failed to ping healthcheck URL: %s", err.Error())) + l(slog.LevelWarn, fmt.Sprintf("❌ Failed to ping healthcheck URL: %s", err.Error())) } else { - l(fmt.Sprintf("🏓 Successfully pinged healthcheck URL: %s", c.Healthcheck.PingURL)) + l(slog.LevelInfo, fmt.Sprintf("🏓 Successfully pinged healthcheck URL: %s", c.Healthcheck.PingURL)) } } }() diff --git a/td2/run.go b/td2/run.go index a1dd5fe..43613f1 100644 --- a/td2/run.go +++ b/td2/run.go @@ -3,7 +3,7 @@ package tenderduty import ( "encoding/json" "fmt" - "log" + "log/slog" "os" "os/signal" "syscall" @@ -25,9 +25,10 @@ func Run(configFile, stateFile, chainConfigDirectory string, password *string, d fmt.Println(p) } if fatal { - log.Fatal("tenderduty the configuration is invalid, refusing to start") + slog.Error("tenderduty configuration is invalid, refusing to start") + os.Exit(1) } - log.Println("tenderduty config is valid, starting tenderduty with", len(td.Chains), "chains") + slog.Info("tenderduty config is valid, starting tenderduty", "chains", len(td.Chains)) defer td.cancel() @@ -39,23 +40,23 @@ func Run(configFile, stateFile, chainConfigDirectory string, password *string, d var e error e = notifyPagerduty(msg) if e != nil { - l(msg.chain, "error sending alert to pagerduty", e.Error()) + l(slog.LevelWarn, msg.chain, "error sending alert to pagerduty", e.Error()) } e = notifyDiscord(msg) if e != nil { - l(msg.chain, "error sending alert to discord", e.Error()) + l(slog.LevelWarn, msg.chain, "error sending alert to discord", e.Error()) } e = notifyTg(msg) if e != nil { - l(msg.chain, "error sending alert to telegram", e.Error()) + l(slog.LevelWarn, msg.chain, "error sending alert to telegram", e.Error()) } e = notifySlack(msg) if e != nil { - l(msg.chain, "error sending alert to slack", e.Error()) + l(slog.LevelWarn, msg.chain, "error sending alert to slack", e.Error()) } e = notifyWebhook(msg) if e != nil { - l(msg.chain, "error sending alert to webhook", e.Error()) + l(slog.LevelWarn, msg.chain, "error sending alert to webhook", e.Error()) } }(alert) case <-td.ctx.Done(): @@ -66,7 +67,7 @@ func Run(configFile, stateFile, chainConfigDirectory string, password *string, d if td.EnableDash { go dash.Serve(td.Listen, td.updateChan, td.logChan, td.HideLogs, devMode) - l("starting dashboard on", td.Listen) + l(slog.LevelInfo, "starting dashboard on ", td.Listen) } else { go func() { for { @@ -107,22 +108,22 @@ func Run(configFile, stateFile, chainConfigDirectory string, password *string, d for { e := cc.newRpc() if e != nil { - l(cc.ChainId, e) + l(slog.LevelWarn, cc.ChainId, e) time.Sleep(5 * time.Second) continue } e = cc.GetMinSignedPerWindow() if e != nil { - l("🛑", cc.ChainId, e) + l(slog.LevelError, "🛑", cc.ChainId, e) } e = cc.GetValInfo(true) if e != nil { - l("🛑", cc.ChainId, e) + l(slog.LevelError, "🛑", cc.ChainId, e) } cc.WsRun() - l(cc.ChainId, "🌀 websocket exited! Restarting monitoring") + l(slog.LevelWarn, cc.ChainId, "🌀 websocket exited! Restarting monitoring") time.Sleep(5 * time.Second) } }(cc, k) @@ -144,11 +145,11 @@ func saveOnExit(stateFile string, saved chan any) { saveState := func() { defer close(saved) - log.Println("saving state...") + slog.Info("saving state") //#nosec -- variable specified on command line - f, e := os.OpenFile(stateFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + f, e := os.OpenFile(stateFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600) if e != nil { - log.Println(e) + slog.Error("failed to open state file for writing", "err", e) return } td.chainsMux.Lock() @@ -177,12 +178,12 @@ func saveOnExit(stateFile string, saved chan any) { NodesDown: nodesDown, }) if e != nil { - log.Println(e) + slog.Error("failed to marshal state", "err", e) return } _, _ = f.Write(b) _ = f.Close() - log.Println("tenderduty exiting.") + slog.Info("tenderduty exiting") } for { select { diff --git a/td2/types.go b/td2/types.go index 270d985..5ba2f9a 100644 --- a/td2/types.go +++ b/td2/types.go @@ -7,7 +7,7 @@ import ( "errors" "fmt" "io" - "log" + "log/slog" "net/http" "net/url" "os" @@ -172,20 +172,20 @@ type ProviderConfig struct { // ChainConfig represents a validator to be monitored on a chain, it is somewhat of a misnomer since multiple // validators can be monitored on a single chain. type ChainConfig struct { - name string - wsclient *TmConn // custom websocket client to work around wss:// bugs in tendermint - client *rpchttp.HTTP // legit tendermint client - noNodes bool // tracks if all nodes are down - valInfo *ValInfo // recent validator state, only refreshed every few minutes - lastValInfo *ValInfo // use for detecting newly-jailed/tombstone - totalBondedTokens float64 // total bonded tokens on the chain - totalSupply float64 // total supply of the chain, used for calculating APR - communityTax float64 // community tax rate, used for calculating APR - inflationRate float64 // inflation rate of the chain, used for calculating APR - baseAPR float64 // the base APR of a chain - denomMetadata *bank.Metadata // chain denom metadata - cryptoPrice *utils.CryptoPrice // coin price in a fiat currency - cosmosDirectoryData *CosmosDirectoryChainData // cached chain data from cosmos.directory + name string + wsclient *TmConn // custom websocket client to work around wss:// bugs in tendermint + client *rpchttp.HTTP // legit tendermint client + noNodes bool // tracks if all nodes are down + valInfo *ValInfo // recent validator state, only refreshed every few minutes + lastValInfo *ValInfo // use for detecting newly-jailed/tombstone + totalBondedTokens float64 // total bonded tokens on the chain + totalSupply float64 // total supply of the chain, used for calculating APR + communityTax float64 // community tax rate, used for calculating APR + inflationRate float64 // inflation rate of the chain, used for calculating APR + baseAPR float64 // the base APR of a chain + denomMetadata *bank.Metadata // chain denom metadata + cryptoPrice *utils.CryptoPrice // coin price in a fiat currency + cosmosDirectoryData *CosmosDirectoryChainData // cached chain data from cosmos.directory minSignedPerWindow float64 // instantly see the validator risk level blocksResults []int @@ -472,14 +472,14 @@ func validateConfig(c *Config) (fatal bool, problems []string) { go func() { e := refreshRegistry() if e != nil { - l("could not fetch chain registry paths, using defaults") + l(slog.LevelWarn, "could not fetch chain registry paths, using defaults") } for { time.Sleep(12 * time.Hour) - l("refreshing cosmos.registry paths") + l(slog.LevelInfo, "refreshing cosmos.registry paths") e = refreshRegistry() if e != nil { - l("could not refresh registry paths -", e) + l(slog.LevelWarn, "could not refresh registry paths -", e) } } }() @@ -529,7 +529,7 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri return nil, err } _ = resp.Body.Close() - log.Printf("downloaded %d bytes from %s", len(b), yamlFile) + slog.Info("downloaded remote config", "bytes", len(b), "url", yamlFile) decrypted, err := decrypt(b, *password) if err != nil { return nil, err @@ -567,22 +567,22 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri // Load additional chain configuration files chainConfigFiles, e := os.ReadDir(chainConfigDirectory) if e != nil { - l("Failed to scan chainConfigDirectory", e) + l(slog.LevelWarn, "failed to scan chainConfigDirectory", e) } for _, chainConfigFile := range chainConfigFiles { if chainConfigFile.IsDir() { - l("Skipping Directory: ", chainConfigFile.Name()) + l(slog.LevelInfo, "skipping directory:", chainConfigFile.Name()) continue } if !strings.HasSuffix(chainConfigFile.Name(), ".yml") { - l("Skipping non .yml file: ", chainConfigFile.Name()) + l(slog.LevelInfo, "skipping non .yml file:", chainConfigFile.Name()) continue } fmt.Println("Reading Chain Config File: ", chainConfigFile.Name()) chainConfig, e := loadChainConfig(path.Join(chainConfigDirectory, chainConfigFile.Name())) if e != nil { - l(fmt.Sprintf("Failed to read %s", chainConfigFile), e) + l(slog.LevelError, fmt.Sprintf("failed to read %s", chainConfigFile), e) return nil, e } @@ -593,7 +593,7 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri c.Chains = make(map[string]*ChainConfig) } c.Chains[chainName] = chainConfig - l(fmt.Sprintf("Added %s from ", chainName), chainConfigFile.Name()) + l(slog.LevelInfo, fmt.Sprintf("added %s from", chainName), chainConfigFile.Name()) } if len(c.Chains) == 0 { @@ -621,17 +621,17 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri //#nosec -- variable specified on command line sf, e := os.OpenFile(stateFile, os.O_RDONLY, 0600) if e != nil { - l("could not load saved state", e.Error()) + l(slog.LevelWarn, "could not load saved state", e.Error()) } b, e := io.ReadAll(sf) _ = sf.Close() if e != nil { - l("could not read saved state", e.Error()) + l(slog.LevelWarn, "could not read saved state", e.Error()) } saved := &savedState{} e = json.Unmarshal(b, saved) if e != nil { - l("could not unmarshal saved state", e.Error()) + l(slog.LevelWarn, "could not unmarshal saved state", e.Error()) } for k, v := range saved.Blocks { if c.Chains[k] != nil { @@ -724,10 +724,10 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri c.coinMarketCapClient = utils.NewCoinMarketCapClient(c.CoinMarketCapAPIToken, currency, c.tenderdutyCache, cacheExpiration, slugs) _, err := c.coinMarketCapClient.GetPrices(c.ctx) if err == nil { - l("💸 price conversion enabled") + l(slog.LevelInfo, "💸 price conversion enabled") } else { c.PriceConversion.Enabled = false - l("🛑 failed to enable price conversion, found error:", err) + l(slog.LevelWarn, "🛑 failed to enable price conversion, found error:", err) } } @@ -737,14 +737,14 @@ func loadConfig(yamlFile, stateFile, chainConfigDirectory string, password *stri func clearStale(alarms map[string]alertMsgCache, what string, hasPagerduty bool, hours float64) { for k := range alarms { if time.Since(alarms[k].SentTime).Hours() >= hours { - l(fmt.Sprintf("🗑 not restoring old alarm (%v >%.2f hours) from cache - %s", alarms[k], hours, k)) + l(slog.LevelInfo, fmt.Sprintf("🗑 not restoring old alarm (%v >%.2f hours) from cache - %s", alarms[k], hours, k)) if hasPagerduty && what == "pagerduty" { - l("NOTE: stale alarms may need to be manually cleared from PagerDuty!") + l(slog.LevelWarn, "NOTE: stale alarms may need to be manually cleared from PagerDuty!") } delete(alarms, k) continue } - l("📂 restored %s alarm state -", what, k) + l(slog.LevelInfo, "📂 restored %s alarm state -", what, k) } } diff --git a/td2/utils/price-conversion.go b/td2/utils/price-conversion.go index 8149b00..6003d46 100644 --- a/td2/utils/price-conversion.go +++ b/td2/utils/price-conversion.go @@ -239,10 +239,6 @@ func ConvertDecCoinToDisplayUnit(coins []github_com_cosmos_cosmos_sdk_types.DecC } else { displayDenom = metadata.Display } - if err := github_com_cosmos_cosmos_sdk_types.ValidateDenom(displayDenom); err != nil { - return nil, fmt.Errorf("invalid display denom %q for base %q: %w", displayDenom, metadata.Base, err) - } - // Find the exponent for the display denom foundDisplayDenom := false for _, unit := range metadata.DenomUnits { @@ -311,7 +307,12 @@ func ConvertDecCoinToDisplayUnit(coins []github_com_cosmos_cosmos_sdk_types.DecC convertedAmount = coin.Amount.Mul(multiplier) } - convertedCoins = append(convertedCoins, github_com_cosmos_cosmos_sdk_types.NewDecCoinFromDec(displayDenom, convertedAmount)) + // Use a struct literal instead of NewDecCoinFromDec so short display denoms (e.g. "om") + // can still be represented after conversion. + convertedCoins = append(convertedCoins, github_com_cosmos_cosmos_sdk_types.DecCoin{ + Denom: displayDenom, + Amount: convertedAmount, + }) } return &convertedCoins, nil @@ -328,17 +329,11 @@ func ConvertFloatInBaseUnitToDisplayUnit(value float64, metadata bank.Metadata) // If no display is set, default to base if metadata.Display == "" { displayDenom = metadata.Base - if err := github_com_cosmos_cosmos_sdk_types.ValidateDenom(displayDenom); err != nil { - return 0, "", fmt.Errorf("invalid display denom %q for base %q: %w", displayDenom, metadata.Base, err) - } // If display is base, no conversion needed return value, displayDenom, nil } else { displayDenom = metadata.Display } - if err := github_com_cosmos_cosmos_sdk_types.ValidateDenom(displayDenom); err != nil { - return 0, "", fmt.Errorf("invalid display denom %q for base %q: %w", displayDenom, metadata.Base, err) - } // Find the exponent for the display denom foundDisplayDenom := false diff --git a/td2/validator.go b/td2/validator.go index 9ebde59..480c10c 100644 --- a/td2/validator.go +++ b/td2/validator.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "strings" "time" @@ -178,7 +179,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { } } if first { - l("âš™ī¸", cc.ValAddress[:20], "... is using consensus key:", cc.valInfo.Valcons) + l("âš™ī¸ ", cc.ValAddress[:20], "... is using consensus key: ", cc.valInfo.Valcons) } } @@ -189,7 +190,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { cc.valInfo.VotingPowerPercent = cc.valInfo.DelegatedTokens / cc.totalBondedTokens // TODO:update statsChan } else { - l(err) + l(slog.LevelError, err) } // Query the chain's outstanding rewards @@ -202,7 +203,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { if err == nil { bondDenom = stakingParams.BondDenom } else { - l(fmt.Errorf("cannot query staking params for chain %s via ABCI, err: %w", cc.name, err)) + l(slog.LevelError, fmt.Errorf("cannot query staking params for chain %s via ABCI, err: %w", cc.name, err)) } if bondDenom == "" && cc.hasCosmosDirectoryData() { if cc.cosmosDirectoryData.Params.Staking.BondDenom != "" { @@ -220,7 +221,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { if err == nil { cc.denomMetadata = bankMeta } else { - l(fmt.Errorf("cannot query bank metadata for chain %s via ABCI, err: %w, trying cosmos.directory fallback", cc.name, err)) + l(slog.LevelError, fmt.Errorf("cannot query bank metadata for chain %s via ABCI, err: %w, trying cosmos.directory fallback", cc.name, err)) // Try cosmos.directory fallback (assets first, then chain data) bankMeta = cc.getBankMetadataFromCosmosDirectory(bondDenom) if bankMeta != nil { @@ -232,7 +233,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { if err == nil { cc.denomMetadata = bankMeta } else { - l(fmt.Errorf("cannot find bank metadata for chain %s in the GitHub JSON file, err: %w", cc.name, err)) + l(slog.LevelError, fmt.Errorf("cannot find bank metadata for chain %s in the GitHub JSON file, err: %w", cc.name, err)) } } } @@ -247,7 +248,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { if err == nil { rewards = rewardsConverted } else { - l(fmt.Errorf("cannot convert rewards to its display unit for chain %s, err: %w, the value will remain in the base unit", cc.name, err)) + l(slog.LevelDebug, fmt.Errorf("cannot convert rewards to its display unit for chain %s, err: %w, the value will remain in the base unit", cc.name, err)) } } @@ -256,7 +257,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { if err == nil { commission = commissionConverted } else { - l(fmt.Errorf("cannot convert commission to its display unit for chain %s, err: %w, the value will remain in the base unit", cc.name, err)) + l(slog.LevelDebug, fmt.Errorf("cannot convert commission to its display unit for chain %s, err: %w, the value will remain in the base unit", cc.name, err)) } } @@ -265,7 +266,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { // TODO:update statsChan } else { - l(fmt.Errorf("failed to query rewards and commission information for chain %s, err: %w", cc.name, err)) + l(slog.LevelError, fmt.Errorf("failed to query rewards and commission information for chain %s, err: %w", cc.name, err)) } if cc.denomMetadata != nil { @@ -283,7 +284,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { cc.baseAPR = inflationRate * (1 - communityTax) * totalSupply / cc.totalBondedTokens cc.valInfo.ValidatorAPR = cc.baseAPR * (1 - cc.valInfo.CommissionRate) } else { - l(fmt.Errorf("failed to query APR-related data for chain %s via ABCI (err: %w)", cc.name, err)) + l(slog.LevelError, fmt.Errorf("failed to query APR-related data for chain %s via ABCI (err: %w)", cc.name, err)) } if err != nil || cc.baseAPR == 0 { @@ -336,7 +337,7 @@ func (cc *ChainConfig) GetValInfo(first bool) (err error) { td.statsChan <- cc.mkUpdate(metricUnvotedProposals, float64(len(cc.unvotedOpenGovProposals)), "") } } else { - l(err) + l(slog.LevelError, err) } // Log if governance alerts are disabled (only on first run) diff --git a/td2/ws.go b/td2/ws.go index 0808b45..dbf46b1 100644 --- a/td2/ws.go +++ b/td2/ws.go @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "log" + "log/slog" "net/url" "strconv" "strings" @@ -82,7 +82,7 @@ func (cc *ChainConfig) WsRun() { // wait until our RPC client is connected and running. We will use the same URL for the websocket if cc.client == nil || cc.valInfo == nil || cc.valInfo.Conspub == nil { if started.Before(time.Now().Add(-2 * time.Minute)) { - l(cc.name, "websocket client timed out waiting for a working rpc endpoint, restarting") + l(slog.LevelWarn, cc.name, "websocket client timed out waiting for a working rpc endpoint, restarting") return } l("⏰ waiting for a healthy client for", cc.ChainId) @@ -95,14 +95,14 @@ func (cc *ChainConfig) WsRun() { //#nosec G402 -- configurable option cc.wsclient, err = NewClient(cc.client.Remote(), td.TLSSkipVerify) if err != nil { - l(err) + l(slog.LevelError, err) cancel() return } defer cc.wsclient.Close() err = cc.wsclient.SetCompressionLevel(3) if err != nil { - log.Println(err) + slog.Error("failed to set websocket compression level", "err", err) } // This go func processes the results returned by the listeners. It has most of the logic on where data is sent, @@ -133,7 +133,7 @@ func (cc *ChainConfig) WsRun() { warn := fmt.Sprintf("❌ warning %s missed %s %d on %s", cc.valInfo.Moniker, missTypes[signState], update.Height, cc.ChainId) info += warn + "\n" cc.lastError = time.Now().UTC().String() + " " + info - l(warn) + l(slog.LevelWarn, warn) } switch signState { @@ -249,7 +249,7 @@ func (cc *ChainConfig) WsRun() { go func() { e := handleBlocks(ctx, blockChan, resultChan, strings.ToUpper(hex.EncodeToString(cc.valInfo.Conspub))) if e != nil { - l("🛑", cc.ChainId, e) + l(slog.LevelError, "🛑", cc.ChainId, e) cancel() } }() @@ -261,7 +261,7 @@ func (cc *ChainConfig) WsRun() { for { _, msg, e = cc.wsclient.ReadMessage() if e != nil { - l(e) + l(slog.LevelError, e) cancel() return } @@ -285,7 +285,7 @@ func (cc *ChainConfig) WsRun() { q := fmt.Sprintf(`{"jsonrpc":"2.0","method":"subscribe","id":1,"params":{"query":"%s"}}`, subscribe) err = cc.wsclient.WriteMessage(websocket.TextMessage, []byte(q)) if err != nil { - l(err) + l(slog.LevelError, err) cancel() break } @@ -360,7 +360,7 @@ func handleBlocks(ctx context.Context, blocks chan *WsReply, results chan Status b := &rawBlock{} err := json.Unmarshal(block.Value(), b) if err != nil { - l("could not decode block", err) + l(slog.LevelError, "could not decode block", err) continue } upd := StatusUpdate{ @@ -402,7 +402,7 @@ func handleVotes(ctx context.Context, votes chan *WsReply, results chan StatusUp vote := &rawVote{} err := json.Unmarshal(reply.Value(), vote) if err != nil { - l(err) + l(slog.LevelError, err) continue } if vote.Vote.ValidatorAddress == address {