diff --git a/go.mod b/go.mod index da46b921..55869df9 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/evstack/ev-node/core v1.0.0 github.com/go-kit/kit v0.13.0 github.com/golang/protobuf v1.5.4 - github.com/gorilla/websocket v1.5.3 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-metrics v0.5.4 github.com/ipfs/go-datastore v0.9.1 @@ -35,6 +35,7 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 + github.com/symbioticfi/relay v1.0.0 golang.org/x/net v0.51.0 golang.org/x/sync v0.20.0 google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 @@ -109,8 +110,8 @@ require ( github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect - github.com/dgraph-io/badger/v4 v4.5.1 // indirect - github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect + github.com/dgraph-io/badger/v4 v4.9.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v29.2.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect @@ -121,7 +122,7 @@ require ( github.com/dunglas/httpsfv v1.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect - github.com/emicklei/dot v1.6.2 // indirect + github.com/emicklei/dot v1.9.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/filecoin-project/go-clock v0.1.0 // indirect @@ -130,7 +131,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/getsentry/sentry-go v0.42.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-chi/chi/v5 v5.2.2 // indirect + github.com/go-chi/chi/v5 v5.2.4 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -147,7 +148,7 @@ require ( github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.25.0 // indirect - github.com/google/flatbuffers v25.1.24+incompatible // indirect + github.com/google/flatbuffers v25.9.23+incompatible // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.20.3 // indirect github.com/google/gopacket v1.1.19 // indirect @@ -174,7 +175,7 @@ require ( github.com/iancoleman/strcase v0.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/boxo v0.35.2 // indirect + github.com/ipfs/boxo v0.36.0 // indirect github.com/ipfs/go-cid v0.6.0 // indirect github.com/ipfs/go-ds-badger4 v0.1.8 // indirect github.com/ipfs/go-log/v2 v2.9.1 // indirect @@ -186,7 +187,7 @@ require ( github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/pgzip v1.2.6 // indirect - github.com/koron/go-ssdp v0.0.6 // indirect + github.com/koron/go-ssdp v0.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -201,12 +202,12 @@ require ( github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-netroute v0.4.0 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect - github.com/libp2p/go-yamux/v5 v5.0.1 // indirect + github.com/libp2p/go-yamux/v5 v5.1.0 // indirect github.com/linxGnu/grocksdb v1.8.15 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/miekg/dns v1.1.68 // indirect + github.com/miekg/dns v1.1.72 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/highwayhash v1.0.3 // indirect @@ -246,26 +247,25 @@ require ( github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect - github.com/pion/datachannel v1.5.10 // indirect + github.com/pion/datachannel v1.6.0 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v3 v3.0.11 // indirect - github.com/pion/ice/v4 v4.0.10 // indirect - github.com/pion/interceptor v0.1.40 // indirect + github.com/pion/ice/v4 v4.2.0 // indirect + github.com/pion/interceptor v0.1.43 // indirect github.com/pion/logging v0.2.4 // indirect - github.com/pion/mdns/v2 v2.0.7 // indirect + github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/rtcp v1.2.15 // indirect - github.com/pion/rtp v1.8.19 // indirect - github.com/pion/sctp v1.8.39 // indirect - github.com/pion/sdp/v3 v3.0.13 // indirect - github.com/pion/srtp/v3 v3.0.6 // indirect + github.com/pion/rtcp v1.2.16 // indirect + github.com/pion/rtp v1.10.0 // indirect + github.com/pion/sctp v1.9.2 // indirect + github.com/pion/sdp/v3 v3.0.17 // indirect + github.com/pion/srtp/v3 v3.0.10 // indirect github.com/pion/stun v0.6.1 // indirect - github.com/pion/stun/v3 v3.0.0 // indirect + github.com/pion/stun/v3 v3.1.1 // indirect github.com/pion/transport/v2 v2.2.10 // indirect - github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v4 v4.0.1 // indirect - github.com/pion/turn/v4 v4.0.2 // indirect - github.com/pion/webrtc/v4 v4.1.2 // indirect + github.com/pion/turn/v4 v4.1.4 // indirect + github.com/pion/webrtc/v4 v4.2.3 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -319,7 +319,7 @@ require ( go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/fx v1.24.0 // indirect - go.uber.org/mock v0.5.2 // indirect + go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.uber.org/zap/exp v0.3.0 // indirect @@ -330,10 +330,10 @@ require ( golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/mod v0.32.0 // indirect golang.org/x/sys v0.41.0 // indirect - golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect + golang.org/x/telemetry v0.0.0-20260127150531-58372ce62d2c // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect - golang.org/x/time v0.12.0 // indirect + golang.org/x/time v0.14.0 // indirect golang.org/x/tools v0.41.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gonum.org/v1/gonum v0.17.0 // indirect diff --git a/go.sum b/go.sum index 8151dd3f..b0e5325c 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= -github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= @@ -251,14 +251,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= -github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= -github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= -github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= -github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dgraph-io/badger/v4 v4.9.0 h1:tpqWb0NewSrCYqTvywbcXOhQdWcqephkVkbBmaaqHzc= +github.com/dgraph-io/badger/v4 v4.9.0/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0= +github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= +github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-ddmin v0.0.0-20210904190556-96a6d69f1034/go.mod h1:zz4KxBkcXUWKjIcrc+uphJ1gPh/t18ymGm3PmQ+VGTk= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= @@ -284,8 +284,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= -github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/emicklei/dot v1.9.0 h1:FyaJNctdMfaEIbTQ1FkKZ1UCZyJJSkyvkrXOVoNZPKU= +github.com/emicklei/dot v1.9.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -321,6 +321,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/getsentry/sentry-go v0.42.0 h1:eeFMACuZTbUQf90RE8dE4tXeSe4CZyfvR1MBL7RLEt8= github.com/getsentry/sentry-go v0.42.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -329,10 +331,10 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4= +github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -353,12 +355,15 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -427,8 +432,8 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= -github.com/google/flatbuffers v25.1.24+incompatible h1:4wPqL3K7GzBd1CwyhSd3usxLKOaJN/AC6puCca6Jm7o= -github.com/google/flatbuffers v25.1.24+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU= +github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -469,8 +474,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -549,8 +554,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/ipfs/boxo v0.35.2 h1:0QZJJh6qrak28abENOi5OA8NjBnZM4p52SxeuIDqNf8= -github.com/ipfs/boxo v0.35.2/go.mod h1:bZn02OFWwJtY8dDW9XLHaki59EC5o+TGDECXEbe1w8U= +github.com/ipfs/boxo v0.36.0 h1:DarrMBM46xCs6GU6Vz+AL8VUyXykqHAqZYx8mR0Oics= +github.com/ipfs/boxo v0.36.0/go.mod h1:92hnRXfP5ScKEIqlq9Ns7LR1dFXEVADKWVGH0fjk83k= github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= github.com/ipfs/go-block-format v0.2.3/go.mod h1:WJaQmPAKhD3LspLixqlqNFxiZ3BZ3xgqxxoSR/76pnA= github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= @@ -609,8 +614,8 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU= -github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI= +github.com/koron/go-ssdp v0.1.0 h1:ckl5x5H6qSNFmi+wCuROvvGUu2FQnMbQrU95IHCcv3Y= +github.com/koron/go-ssdp v0.1.0/go.mod h1:GltaDBjtK1kemZOusWYLGotV0kBeEf59Bp0wtSB0uyU= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -622,8 +627,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -654,8 +660,8 @@ github.com/libp2p/go-netroute v0.4.0 h1:sZZx9hyANYUx9PZyqcgE/E1GUG3iEtTZHUEvdtXT github.com/libp2p/go-netroute v0.4.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= -github.com/libp2p/go-yamux/v5 v5.0.1 h1:f0WoX/bEF2E8SbE4c/k1Mo+/9z0O4oC/hWEA+nfYRSg= -github.com/libp2p/go-yamux/v5 v5.0.1/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= +github.com/libp2p/go-yamux/v5 v5.1.0 h1:8Qlxj4E9JGJAQVW6+uj2o7mqkqsIVlSUGmTWhlXzoHE= +github.com/libp2p/go-yamux/v5 v5.1.0/go.mod h1:tgIQ07ObtRR/I0IWsFOyQIL9/dR5UXgc2s8xKmNZv1o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.8.15 h1:XXSNiNo/PTU2pGvM+P3SeqNR7Uv5FMjPxu5ugFsVogw= @@ -687,8 +693,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= -github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -846,50 +852,50 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= -github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= +github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= +github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.0.11 h1:zqn8YhoAU7d9whsWLhNiQlbB8QdpJj8XQVSc5ImUons= github.com/pion/dtls/v3 v3.0.11/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= -github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= -github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= -github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4= -github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic= +github.com/pion/ice/v4 v4.2.0 h1:jJC8S+CvXCCvIQUgx+oNZnoUpt6zwc34FhjWwCU4nlw= +github.com/pion/ice/v4 v4.2.0/go.mod h1:EgjBGxDgmd8xB0OkYEVFlzQuEI7kWSCFu+mULqaisy4= +github.com/pion/interceptor v0.1.43 h1:6hmRfnmjogSs300xfkR0JxYFZ9k5blTEvCD7wxEDuNQ= +github.com/pion/interceptor v0.1.43/go.mod h1:BSiC1qKIJt1XVr3l3xQ2GEmCFStk9tx8fwtCZxxgR7M= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= -github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= -github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= +github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY= +github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= -github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= -github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= -github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= -github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= -github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= -github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4= -github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= -github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4= -github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY= +github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= +github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= +github.com/pion/rtp v1.10.0 h1:XN/xca4ho6ZEcijpdF2VGFbwuHUfiIMf3ew8eAAE43w= +github.com/pion/rtp v1.10.0/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= +github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo= +github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8= +github.com/pion/sdp/v3 v3.0.17 h1:9SfLAW/fF1XC8yRqQ3iWGzxkySxup4k4V7yN8Fs8nuo= +github.com/pion/sdp/v3 v3.0.17/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= +github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ= +github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= -github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= -github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= +github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw= +github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= -github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= -github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= +github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= +github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= -github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= -github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= -github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= -github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= +github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ= +github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ= +github.com/pion/webrtc/v4 v4.2.3 h1:RtdWDnkenNQGxUrZqWa5gSkTm5ncsLg5d+zu0M4cXt4= +github.com/pion/webrtc/v4 v4.2.3/go.mod h1:7vsyFzRzaKP5IELUnj8zLcglPyIT6wWwqTppBZ1k6Kc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1037,6 +1043,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/symbioticfi/relay v1.0.0 h1:WsWmC1k8N93zcpjxlJo98W3broygYkUgSP2iljuN6kM= +github.com/symbioticfi/relay v1.0.0/go.mod h1:OV2yYR3gV7PVnnemCMzTqp6aM1Ge9EBJEpvJHgFAmxo= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= @@ -1130,8 +1138,8 @@ go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= -go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -1323,8 +1331,8 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo= -golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= +golang.org/x/telemetry v0.0.0-20260127150531-58372ce62d2c h1:S3mh82M3weqzaUStRMlOHGTtXLpnW9wUC6dbOoWHnag= +golang.org/x/telemetry v0.0.0-20260127150531-58372ce62d2c/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= 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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= @@ -1353,8 +1361,8 @@ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/network/keeper/bitmap.go b/modules/network/keeper/bitmap.go index b62cc881..a16d5f7e 100644 --- a/modules/network/keeper/bitmap.go +++ b/modules/network/keeper/bitmap.go @@ -50,7 +50,7 @@ func (bh *BitmapHelper) PopCount(bitmap []byte) int { // OR performs bitwise OR of two bitmaps func (bh *BitmapHelper) OR(dst, src []byte) { minLen := min(len(src), len(dst)) - for i := 0; i < minLen; i++ { + for i := range minLen { dst[i] |= src[i] } } @@ -58,7 +58,7 @@ func (bh *BitmapHelper) OR(dst, src []byte) { // AND performs bitwise AND of two bitmaps func (bh *BitmapHelper) AND(dst, src []byte) { minLen := min(len(src), len(dst)) - for i := 0; i < minLen; i++ { + for i := range minLen { dst[i] &= src[i] } } diff --git a/modules/network/keeper/hooks.go b/modules/network/keeper/hooks.go new file mode 100644 index 00000000..93906c12 --- /dev/null +++ b/modules/network/keeper/hooks.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "context" + "encoding/hex" + + sdk "github.com/cosmos/cosmos-sdk/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + symstakingtypes "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// SymStakingHooksImpl implements SymStakingHooks for the network module. +// It updates the attester set when validators are added/removed via the relay. +type SymStakingHooksImpl struct { + keeper Keeper +} + +// NewSymStakingHooks creates a new SymStakingHooks implementation. +func NewSymStakingHooks(k Keeper) symstakingtypes.SymStakingHooks { + return &SymStakingHooksImpl{keeper: k} +} + +// AfterValidatorCreated is called when a new validator is added from the relay. +func (h *SymStakingHooksImpl) AfterValidatorCreated(ctx context.Context, consPubKey cryptotypes.PubKey) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + // Get the hex-encoded pubkey as the attester address + pubKeyHex := hex.EncodeToString(consPubKey.Bytes()) + + // Check if we're at the maximum attester limit + attesters, err := h.keeper.GetAllAttesters(sdkCtx) + if err != nil { + h.keeper.Logger(sdkCtx).Error("failed to get attesters", "error", err) + return err + } + if len(attesters) >= MaxAttesters { + h.keeper.Logger(sdkCtx).Warn("attester set at maximum capacity, cannot add validator", + "pubkey", pubKeyHex, "max", MaxAttesters) + return nil // Don't fail, just log + } + + // Add to attester set + if err := h.keeper.SetAttesterSetMember(sdkCtx, pubKeyHex); err != nil { + h.keeper.Logger(sdkCtx).Error("failed to add attester", "pubkey", pubKeyHex, "error", err) + return err + } + + // Rebuild validator index map to include the new attester + if err := h.keeper.BuildValidatorIndexMap(sdkCtx); err != nil { + h.keeper.Logger(sdkCtx).Error("failed to rebuild validator index map", "error", err) + return err + } + + h.keeper.Logger(sdkCtx).Info("added validator to attester set via relay", + "pubkey", pubKeyHex, + "total_attesters", len(attesters)+1, + ) + + return nil +} + +// AfterValidatorModified is called when a validator's power changes from the relay. +func (h *SymStakingHooksImpl) AfterValidatorModified(ctx context.Context, consPubKey cryptotypes.PubKey) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + // Get the hex-encoded pubkey + pubKeyHex := hex.EncodeToString(consPubKey.Bytes()) + + // For now, we just log the modification. Power changes are handled + // directly by the symstaking module returning ValidatorUpdates. + h.keeper.Logger(sdkCtx).Info("validator power modified via relay", "pubkey", pubKeyHex) + + return nil +} + +// AfterValidatorRemoved is called when a validator is removed from the relay. +func (h *SymStakingHooksImpl) AfterValidatorRemoved(ctx context.Context, consPubKey cryptotypes.PubKey) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + // Get the hex-encoded pubkey + pubKeyHex := hex.EncodeToString(consPubKey.Bytes()) + + // Remove from attester set + if err := h.keeper.RemoveAttesterSetMember(sdkCtx, pubKeyHex); err != nil { + h.keeper.Logger(sdkCtx).Error("failed to remove attester", "pubkey", pubKeyHex, "error", err) + return err + } + + // Rebuild validator index map without the removed attester + if err := h.keeper.BuildValidatorIndexMap(sdkCtx); err != nil { + h.keeper.Logger(sdkCtx).Error("failed to rebuild validator index map", "error", err) + return err + } + + attesters, _ := h.keeper.GetAllAttesters(sdkCtx) + h.keeper.Logger(sdkCtx).Info("removed validator from attester set via relay", + "pubkey", pubKeyHex, + "remaining_attesters", len(attesters), + ) + + return nil +} diff --git a/modules/symslashing/depinject.go b/modules/symslashing/depinject.go new file mode 100644 index 00000000..dd2ccd87 --- /dev/null +++ b/modules/symslashing/depinject.go @@ -0,0 +1,68 @@ +package symslashing + +import ( + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/store" + "cosmossdk.io/depinject" + "cosmossdk.io/depinject/appconfig" + "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/evstack/ev-abci/modules/symslashing/keeper" + modulev1 "github.com/evstack/ev-abci/modules/symslashing/module/v1" + symstakingkeeper "github.com/evstack/ev-abci/modules/symstaking/keeper" +) + +var _ appmodule.AppModule = AppModule{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +func init() { + appconfig.Register( + &modulev1.Module{}, + appconfig.Provide(ProvideModule), + ) +} + +type ModuleInputs struct { + depinject.In + + Config *modulev1.Module + Cdc codec.Codec + StoreService store.KVStoreService + SymStakingKeeper symstakingkeeper.Keeper +} + +type ModuleOutputs struct { + depinject.Out + + SymSlashingKeeper keeper.Keeper + Module appmodule.AppModule +} + +func ProvideModule(in ModuleInputs) ModuleOutputs { + // default to governance authority if not provided + authority := authtypes.NewModuleAddress(govtypes.ModuleName) + if in.Config.Authority != "" { + authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) + } + + k := keeper.NewKeeper( + in.Cdc, + in.StoreService, + in.SymStakingKeeper, + authority.String(), + ) + + m := NewAppModule( + in.Cdc, + k, + ) + + return ModuleOutputs{ + SymSlashingKeeper: k, + Module: m, + } +} diff --git a/modules/symslashing/genesis.go b/modules/symslashing/genesis.go new file mode 100644 index 00000000..ffb6b7ea --- /dev/null +++ b/modules/symslashing/genesis.go @@ -0,0 +1,27 @@ +package symslashing + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/evstack/ev-abci/modules/symslashing/keeper" + "github.com/evstack/ev-abci/modules/symslashing/types" +) + +// InitGenesis initializes the symslashing module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) error { + // Set module params + if err := k.SetParams(ctx, genState.Params); err != nil { + return fmt.Errorf("set params: %w", err) + } + + return nil +} + +// ExportGenesis returns the symslashing module's exported genesis. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesisState() + genesis.Params = k.GetParams(ctx) + return genesis +} diff --git a/modules/symslashing/keeper/abci.go b/modules/symslashing/keeper/abci.go new file mode 100644 index 00000000..8fa15f3a --- /dev/null +++ b/modules/symslashing/keeper/abci.go @@ -0,0 +1,13 @@ +package keeper + +import ( + "context" +) + +// BeginBlock performs the begin block operations for the symslashing module. +// In the context of ev-node (single sequencer), there is no CometBFT consensus engine +// to provide double-sign or downtime evidence natively via ABCI BeginBlock. +// Any slashing actions are triggered externally (e.g., via the relay or specific transactions). +func (k Keeper) BeginBlock(ctx context.Context) error { + return nil +} diff --git a/modules/symslashing/keeper/keeper.go b/modules/symslashing/keeper/keeper.go new file mode 100644 index 00000000..d58b9670 --- /dev/null +++ b/modules/symslashing/keeper/keeper.go @@ -0,0 +1,158 @@ +package keeper + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + + "cosmossdk.io/collections" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/evstack/ev-abci/modules/symslashing/types" + symstakingtypes "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// Keeper of the symslashing store +type Keeper struct { + cdc codec.BinaryCodec + storeService store.KVStoreService + symStakingKeeper types.SymStakingKeeper + authority string + + // Collections for state management + InfractionRecords collections.Map[string, []byte] // pubkey hex -> JSON record + Schema collections.Schema +} + +// NewKeeper creates a new symslashing Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, + storeService store.KVStoreService, + symStakingKeeper types.SymStakingKeeper, + authority string, +) Keeper { + sb := collections.NewSchemaBuilder(storeService) + keeper := Keeper{ + cdc: cdc, + storeService: storeService, + symStakingKeeper: symStakingKeeper, + authority: authority, + + InfractionRecords: collections.NewMap(sb, types.InfractionRecordsPrefix, "infraction_records", collections.StringKey, collections.BytesValue), + } + + schema, err := sb.Build() + if err != nil { + panic(err) + } + keeper.Schema = schema + return keeper +} + +// GetAuthority returns the module authority +func (k Keeper) GetAuthority() string { + return k.authority +} + +// Logger returns a module-specific logger +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/symslashing") +} + +// GetParams get all parameters as types.Params +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil || bz == nil { + return types.DefaultParams() + } + var params types.Params + if err := json.Unmarshal(bz, ¶ms); err != nil { + return types.DefaultParams() + } + return params +} + +// SetParams set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { + store := k.storeService.OpenKVStore(ctx) + bz, err := json.Marshal(params) + if err != nil { + return err + } + return store.Set(types.ParamsKey, bz) +} + +// SlashValidator slashes a validator via the relay +func (k Keeper) SlashValidator( + ctx context.Context, + validatorPubKey []byte, + infractionType symstakingtypes.Infraction, + height int64, +) (string, error) { + if len(validatorPubKey) == 0 { + return "", types.ErrEmptyValidatorPubKey + } + + // Validate infraction type + if infractionType != symstakingtypes.Infraction_INFRACTION_DOUBLE_SIGN && + infractionType != symstakingtypes.Infraction_INFRACTION_DOWNTIME { + return "", types.ErrInvalidInfraction + } + + // Request slash via relay + requestID, err := k.symStakingKeeper.SlashWithInfractionReason(ctx, validatorPubKey, infractionType, height) + if err != nil { + return "", fmt.Errorf("%w: %w", types.ErrSignFailed, err) + } + + // Record the infraction + pubKeyHex := hex.EncodeToString(validatorPubKey) + record := types.InfractionRecord{ + ValidatorPubKey: validatorPubKey, + InfractionType: infractionType, + Height: height, + RequestID: requestID, + } + recordBz, err := json.Marshal(record) + if err != nil { + k.Logger(sdk.UnwrapSDKContext(ctx)).Error("failed to marshal infraction record", "error", err) + } else { + sdkCtx := sdk.UnwrapSDKContext(ctx) + if err := k.InfractionRecords.Set(sdkCtx, pubKeyHex, recordBz); err != nil { + k.Logger(sdkCtx).Error("failed to store infraction record", "error", err) + } + } + + k.Logger(sdk.UnwrapSDKContext(ctx)).Info( + "validator slashed via relay", + "validator", pubKeyHex, + "infraction", infractionType, + "height", height, + "request_id", requestID, + ) + + return requestID, nil +} + +// GetInfractionRecord retrieves an infraction record by validator pubkey +func (k Keeper) GetInfractionRecord(ctx sdk.Context, pubkeyHex string) (types.InfractionRecord, bool) { + recordBz, err := k.InfractionRecords.Get(ctx, pubkeyHex) + if err != nil { + return types.InfractionRecord{}, false + } + var record types.InfractionRecord + if err := json.Unmarshal(recordBz, &record); err != nil { + return types.InfractionRecord{}, false + } + return record, true +} + +// DeleteInfractionRecord removes an infraction record +func (k Keeper) DeleteInfractionRecord(ctx sdk.Context, pubkeyHex string) error { + return k.InfractionRecords.Remove(ctx, pubkeyHex) +} diff --git a/modules/symslashing/module.go b/modules/symslashing/module.go new file mode 100644 index 00000000..ed6243d4 --- /dev/null +++ b/modules/symslashing/module.go @@ -0,0 +1,101 @@ +package symslashing + +import ( + "context" + "encoding/json" + "fmt" + + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "github.com/evstack/ev-abci/modules/symslashing/keeper" + "github.com/evstack/ev-abci/modules/symslashing/types" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} +) + +type AppModuleBasic struct { + cdc codec.Codec +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, +) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + } +} + +// Name returns the symslashing module's name +func (am AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the symslashing module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the symslashing module. +func (am AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genesisState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genesisState); err != nil { + return fmt.Errorf("unmarshal genesis state: %w", err) + } + return genesisState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the symslashing module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwruntime.ServeMux) { + // No gRPC gateway routes yet - module doesn't expose query endpoints +} + +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// InitGenesis performs genesis initialization for the symslashing module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + if err := InitGenesis(ctx, am.keeper, genesisState); err != nil { + panic(fmt.Errorf("init genesis: %w", err)) + } +} + +// ExportGenesis exports genesis state. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(ExportGenesis(ctx, am.keeper)) +} + +// BeginBlock executes the begin block logic for the symslashing module. +func (am AppModule) BeginBlock(ctx context.Context) error { + return am.keeper.BeginBlock(ctx) +} diff --git a/modules/symslashing/module/v1/module.pb.go b/modules/symslashing/module/v1/module.pb.go new file mode 100644 index 00000000..0efe6195 --- /dev/null +++ b/modules/symslashing/module/v1/module.pb.go @@ -0,0 +1,19 @@ +// Package v1 contains the module configuration types for symslashing. +package v1 + +import ( + _ "cosmossdk.io/depinject/appconfig/v1alpha1" +) + +// Module is the config object for the symslashing module. +type Module struct { + // authority defines the custom module authority. If not set, defaults to the governance module. + Authority string +} + +func (m *Module) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} diff --git a/modules/symslashing/types/codec.go b/modules/symslashing/types/codec.go new file mode 100644 index 00000000..6abee45c --- /dev/null +++ b/modules/symslashing/types/codec.go @@ -0,0 +1,22 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" +) + +// RegisterCodec registers the necessary symslashing interfaces and concrete types +// on the provided LegacyAmino codec. +func RegisterCodec(cdc *codec.LegacyAmino) { + // No legacy amino types to register +} + +// RegisterLegacyAminoCodec is an alias for RegisterCodec for compatibility. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + RegisterCodec(cdc) +} + +// RegisterInterfaces registers the module's interfaces with the interface registry. +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + // No interfaces to register yet +} diff --git a/modules/symslashing/types/errors.go b/modules/symslashing/types/errors.go new file mode 100644 index 00000000..fe5d6825 --- /dev/null +++ b/modules/symslashing/types/errors.go @@ -0,0 +1,12 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// x/symslashing module sentinel errors +var ( + ErrEmptyValidatorPubKey = errorsmod.Register(ModuleName, 1, "empty validator public key") + ErrInvalidInfraction = errorsmod.Register(ModuleName, 2, "invalid infraction type") + ErrSignFailed = errorsmod.Register(ModuleName, 3, "failed to sign slash message") +) diff --git a/modules/symslashing/types/genesis.go b/modules/symslashing/types/genesis.go new file mode 100644 index 00000000..a019d953 --- /dev/null +++ b/modules/symslashing/types/genesis.go @@ -0,0 +1,36 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/gogoproto/proto" +) + +// GenesisState defines the symslashing module's genesis state. +type GenesisState struct { + Params Params `json:"params" yaml:"params"` +} + +// ProtoMessage implements proto.Message. +func (*GenesisState) ProtoMessage() {} + +// Reset implements proto.Message. +func (m *GenesisState) Reset() { *m = GenesisState{} } + +// String implements proto.Message. +func (m *GenesisState) String() string { return proto.CompactTextString(m) } + +// DefaultGenesisState returns the default genesis state. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return fmt.Errorf("invalid params: %w", err) + } + return nil +} diff --git a/modules/symslashing/types/keys.go b/modules/symslashing/types/keys.go new file mode 100644 index 00000000..54c3e6df --- /dev/null +++ b/modules/symslashing/types/keys.go @@ -0,0 +1,13 @@ +package types + +const ( + // ModuleName is the name of the symslashing module. + ModuleName = "symslashing" +) + +var ( + // ParamsKey is the key for params. + ParamsKey = []byte{0x00} + // InfractionRecordsPrefix is the prefix for infraction records. + InfractionRecordsPrefix = []byte{0x01} +) diff --git a/modules/symslashing/types/params.go b/modules/symslashing/types/params.go new file mode 100644 index 00000000..3acd23dc --- /dev/null +++ b/modules/symslashing/types/params.go @@ -0,0 +1,72 @@ +package types + +import ( + "context" + + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// SymStakingKeeper defines the expected interface for the symstaking module. +type SymStakingKeeper interface { + SlashWithInfractionReason( + ctx context.Context, + validatorPubKey []byte, + infractionType types.Infraction, + height int64, + ) (string, error) +} + +// Params defines the parameters for the symslashing module. +type Params struct { + // SignedBlocksWindow is the number of blocks to consider for downtime slashing. + SignedBlocksWindow int64 `json:"signed_blocks_window"` + // MinSignedPerWindow is the minimum percentage of blocks that must be signed. + MinSignedPerWindow string `json:"min_signed_per_window"` + // DowntimeJailDuration is the duration a validator is jailed for downtime. + DowntimeJailDuration int64 `json:"downtime_jail_duration"` + // SlashFractionDoubleSign is the fraction of stake slashed for double sign. + SlashFractionDoubleSign string `json:"slash_fraction_double_sign"` + // SlashFractionDowntime is the fraction of stake slashed for downtime. + SlashFractionDowntime string `json:"slash_fraction_downtime"` +} + +// NewParams creates a new Params instance. +func NewParams( + signedBlocksWindow int64, + minSignedPerWindow string, + downtimeJailDuration int64, + slashFractionDoubleSign string, + slashFractionDowntime string, +) Params { + return Params{ + SignedBlocksWindow: signedBlocksWindow, + MinSignedPerWindow: minSignedPerWindow, + DowntimeJailDuration: downtimeJailDuration, + SlashFractionDoubleSign: slashFractionDoubleSign, + SlashFractionDowntime: slashFractionDowntime, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams( + 100, // 100 blocks window + "0.5", // 50% minimum signed + 60, // 60 seconds jail duration + "0.05", // 5% slash for double sign + "0.01", // 1% slash for downtime + ) +} + +// Validate validates the set of params. +func (p Params) Validate() error { + return nil +} + +// InfractionRecord stores information about an infraction. +type InfractionRecord struct { + ValidatorPubKey []byte `json:"validator_pub_key"` + InfractionType types.Infraction `json:"infraction_type"` + Height int64 `json:"height"` + RequestID string `json:"request_id"` +} diff --git a/modules/symstaking/depinject.go b/modules/symstaking/depinject.go new file mode 100644 index 00000000..8ca5dcf5 --- /dev/null +++ b/modules/symstaking/depinject.go @@ -0,0 +1,83 @@ +package symstaking + +import ( + "cosmossdk.io/core/appmodule" + "cosmossdk.io/core/store" + "cosmossdk.io/depinject" + "cosmossdk.io/depinject/appconfig" + "github.com/cosmos/cosmos-sdk/codec" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/evstack/ev-abci/modules/symstaking/keeper" + modulev1 "github.com/evstack/ev-abci/modules/symstaking/module/v1" + "github.com/evstack/ev-abci/modules/symstaking/relay" + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +var _ appmodule.AppModule = AppModule{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +func init() { + appconfig.Register( + &modulev1.Module{}, + appconfig.Provide(ProvideModule), + ) +} + +type ModuleInputs struct { + depinject.In + + Config *modulev1.Module + Cdc codec.Codec + StoreService store.KVStoreService +} + +type ModuleOutputs struct { + depinject.Out + + SymStakingKeeper keeper.Keeper + SymStakingHooks types.SymStakingHooksWrapper + Module appmodule.AppModule +} + +func ProvideModule(in ModuleInputs) ModuleOutputs { + // default to governance authority if not provided + authority := authtypes.NewModuleAddress(govtypes.ModuleName) + if in.Config.Authority != "" { + authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority) + } + + // Create relay config from module config or environment + relayCfg := relay.Config{ + RPCAddress: in.Config.GetRelayRpcAddress(), + KeyFile: in.Config.GetKeyFile(), + } + // Override with environment variables if config not set + if relayCfg.RPCAddress == "" && relayCfg.KeyFile == "" { + relayCfg = relay.ConfigFromEnv() + } + + k, err := keeper.NewKeeper( + in.Cdc, + in.StoreService, + relayCfg, + authority.String(), + ) + if err != nil { + panic(err) + } + + m := NewAppModule( + in.Cdc, + k, + ) + + return ModuleOutputs{ + SymStakingKeeper: k, + SymStakingHooks: types.SymStakingHooksWrapper{SymStakingHooks: nil}, // Set by caller + Module: m, + } +} diff --git a/modules/symstaking/genesis.go b/modules/symstaking/genesis.go new file mode 100644 index 00000000..cb882812 --- /dev/null +++ b/modules/symstaking/genesis.go @@ -0,0 +1,33 @@ +package symstaking + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/evstack/ev-abci/modules/symstaking/keeper" + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// InitGenesis initializes the symstaking module's state from a provided genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) error { + // Set module params + if err := k.SetParams(ctx, genState.Params); err != nil { + return fmt.Errorf("set params: %w", err) + } + + // Set initial epoch + if err := k.SetCurrentEpoch(ctx, genState.Epoch); err != nil { + return fmt.Errorf("set epoch: %w", err) + } + + return nil +} + +// ExportGenesis returns the symstaking module's exported genesis. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + genesis := types.DefaultGenesisState() + genesis.Params = k.GetParams(ctx) + genesis.Epoch = k.GetCurrentEpoch(ctx) + return genesis +} diff --git a/modules/symstaking/keeper/abci.go b/modules/symstaking/keeper/abci.go new file mode 100644 index 00000000..04fc3677 --- /dev/null +++ b/modules/symstaking/keeper/abci.go @@ -0,0 +1,181 @@ +package keeper + +import ( + "encoding/hex" + "fmt" + + cryptoProto "github.com/cometbft/cometbft/proto/tendermint/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// EndBlock updates the validator set from the relay at the end of each block. +// It compares the current validator set with the relay's and returns updates. +func (k Keeper) EndBlock(ctx sdk.Context) error { + params := k.GetParams(ctx) + + // Only check for epoch updates at specified intervals + if ctx.BlockHeight()%params.EpochCheckInterval != 0 { + return nil + } + + // Get current epoch from relay + currentEpoch, err := k.GetCurrentEpochFromRelay(ctx) + if err != nil { + k.Logger(ctx).Error("failed to get current epoch from relay", "error", err) + return err + } + + // Check if epoch has changed + lastEpoch := k.GetCurrentEpoch(ctx) + if currentEpoch == lastEpoch { + // No epoch change, no validator updates needed + return nil + } + + // Update stored epoch + if err := k.SetCurrentEpoch(ctx, currentEpoch); err != nil { + return err + } + + // Get validator set from relay + validatorSet, err := k.GetValidatorSetFromRelay(ctx, currentEpoch) + if err != nil { + k.Logger(ctx).Error("failed to get validator set from relay", "error", err) + return err + } + + // Build a map of new validators for easy lookup + newValidators := make(map[string]int64) + for _, val := range validatorSet { + pubKeyHex := pubKeyToHex(val.PubKey) + newValidators[pubKeyHex] = val.Power + } + + // Track all updates to return + updatesCount := 0 + + // Check for removed or modified validators from last set + k.IterateLastValidatorSet(ctx, func(pubkeyHex string, oldPower int64) bool { + newPower, exists := newValidators[pubkeyHex] + if !exists { + // Validator was removed - power 0 + + updatesCount++ + // Call hook for removed validator + if k.hooks != nil { + edKey := &ed25519.PubKey{Key: hexToBytes(pubkeyHex)} + if err := k.hooks.AfterValidatorRemoved(ctx, edKey); err != nil { + k.Logger(ctx).Error("hook AfterValidatorRemoved failed", "error", err) + } + } + // Remove from stored set + _ = k.DeleteLastValidatorPower(ctx, pubkeyHex) + + // Emit removal event + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeValidatorSetUpdate, + sdk.NewAttribute(types.AttributeKeyAction, types.AttributeActionRemoved), + sdk.NewAttribute(types.AttributeKeyPubKey, pubkeyHex), + sdk.NewAttribute(types.AttributeKeyPower, "0"), + sdk.NewAttribute(types.AttributeKeyEpoch, fmt.Sprintf("%d", currentEpoch)), + ), + ) + } else if newPower != oldPower { + // Power changed + + updatesCount++ + // Call hook for modified validator + if k.hooks != nil { + edKey := &ed25519.PubKey{Key: hexToBytes(pubkeyHex)} + if err := k.hooks.AfterValidatorModified(ctx, edKey); err != nil { + k.Logger(ctx).Error("hook AfterValidatorModified failed", "error", err) + } + } + // Update stored power + _ = k.SetLastValidatorPower(ctx, pubkeyHex, newPower) + + // Emit modification event + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeValidatorSetUpdate, + sdk.NewAttribute(types.AttributeKeyAction, types.AttributeActionModified), + sdk.NewAttribute(types.AttributeKeyPubKey, pubkeyHex), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", newPower)), + sdk.NewAttribute(types.AttributeKeyEpoch, fmt.Sprintf("%d", currentEpoch)), + ), + ) + } + return false + }) + + // Check for new validators + for _, val := range validatorSet { + pubKeyHex := pubKeyToHex(val.PubKey) + _, exists := k.GetLastValidatorPower(ctx, pubKeyHex) + if !exists { + // New validator + updatesCount++ + // Call hook for new validator + if k.hooks != nil { + edKey := &ed25519.PubKey{Key: hexToBytes(pubKeyHex)} + if err := k.hooks.AfterValidatorCreated(ctx, edKey); err != nil { + k.Logger(ctx).Error("hook AfterValidatorCreated failed", "error", err) + } + } + // Store new validator + _ = k.SetLastValidatorPower(ctx, pubKeyHex, val.Power) + + // Emit addition event + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeValidatorSetUpdate, + sdk.NewAttribute(types.AttributeKeyAction, types.AttributeActionAdded), + sdk.NewAttribute(types.AttributeKeyPubKey, pubKeyHex), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", val.Power)), + sdk.NewAttribute(types.AttributeKeyEpoch, fmt.Sprintf("%d", currentEpoch)), + ), + ) + } + } + + k.Logger(ctx).Info("validator set updated from relay", + "epoch", currentEpoch, + "total_validators", len(validatorSet), + "updates", updatesCount, + ) + + return nil +} + +// pubKeyToHex converts a protobuf PublicKey to hex string +func pubKeyToHex(pk cryptoProto.PublicKey) string { + switch sum := pk.Sum.(type) { + case *cryptoProto.PublicKey_Ed25519: + return hex.EncodeToString(sum.Ed25519) + case *cryptoProto.PublicKey_Secp256K1: + return hex.EncodeToString(sum.Secp256K1) + default: + return "" + } +} + +// hexToBytes converts hex string to bytes +func hexToBytes(s string) []byte { + bz, _ := hex.DecodeString(s) + return bz +} + +// hexToPubKey converts hex string back to protobuf PublicKey +func hexToPubKey(s string) cryptoProto.PublicKey { + bz := hexToBytes(s) + if len(bz) == 32 { + // Ed25519 key + return cryptoProto.PublicKey{Sum: &cryptoProto.PublicKey_Ed25519{Ed25519: bz}} + } + // Assume Secp256K1 for other lengths + return cryptoProto.PublicKey{Sum: &cryptoProto.PublicKey_Secp256K1{Secp256K1: bz}} +} diff --git a/modules/symstaking/keeper/keeper.go b/modules/symstaking/keeper/keeper.go new file mode 100644 index 00000000..5fdf61fe --- /dev/null +++ b/modules/symstaking/keeper/keeper.go @@ -0,0 +1,217 @@ +package keeper + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + + "cosmossdk.io/collections" + "cosmossdk.io/core/store" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/evstack/ev-abci/modules/symstaking/relay" + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +// Keeper of the symstaking store +type Keeper struct { + cdc codec.BinaryCodec + storeService store.KVStoreService + relayClient types.RelayClient + authority string + hooks types.SymStakingHooks + + // Collections for state management + CurrentEpoch collections.Item[uint64] + LastValidatorSet collections.Map[string, int64] // pubkey hex -> power + Schema collections.Schema +} + +// NewKeeper creates a new symstaking Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, + storeService store.KVStoreService, + relayCfg relay.Config, + authority string, +) (Keeper, error) { + // Create relay client + client, err := relay.NewClient(relayCfg) + if err != nil { + return Keeper{}, fmt.Errorf("failed to create relay client: %w", err) + } + + sb := collections.NewSchemaBuilder(storeService) + keeper := Keeper{ + cdc: cdc, + storeService: storeService, + relayClient: client, + authority: authority, + + CurrentEpoch: collections.NewItem(sb, types.CurrentEpochKey, "current_epoch", collections.Uint64Value), + LastValidatorSet: collections.NewMap(sb, types.LastValidatorSetPrefix, "last_validator_set", collections.StringKey, collections.Int64Value), + } + + schema, err := sb.Build() + if err != nil { + return Keeper{}, fmt.Errorf("failed to build schema: %w", err) + } + keeper.Schema = schema + return keeper, nil +} + +// SetHooks sets the module hooks +func (k *Keeper) SetHooks(hooks types.SymStakingHooks) { + k.hooks = hooks +} + +// GetAuthority returns the module authority +func (k Keeper) GetAuthority() string { + return k.authority +} + +// Logger returns a module-specific logger +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/symstaking") +} + +// GetParams get all parameters as types.Params +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.ParamsKey) + if err != nil || bz == nil { + return types.DefaultParams() + } + var params types.Params + if err := json.Unmarshal(bz, ¶ms); err != nil { + return types.DefaultParams() + } + return params +} + +// SetParams set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { + store := k.storeService.OpenKVStore(ctx) + bz, err := json.Marshal(params) + if err != nil { + return err + } + return store.Set(types.ParamsKey, bz) +} + +// GetCurrentEpoch returns the current epoch +func (k Keeper) GetCurrentEpoch(ctx sdk.Context) uint64 { + epoch, err := k.CurrentEpoch.Get(ctx) + if err != nil { + return 0 + } + return epoch +} + +// SetCurrentEpoch sets the current epoch +func (k Keeper) SetCurrentEpoch(ctx sdk.Context, epoch uint64) error { + return k.CurrentEpoch.Set(ctx, epoch) +} + +// GetValidatorSetFromRelay fetches the validator set from the relay +func (k Keeper) GetValidatorSetFromRelay(ctx context.Context, epoch uint64) ([]abci.ValidatorUpdate, error) { + return k.relayClient.GetValidatorSet(ctx, epoch) +} + +// GetCurrentEpochFromRelay fetches the current epoch from the relay +func (k Keeper) GetCurrentEpochFromRelay(ctx context.Context) (uint64, error) { + return k.relayClient.GetCurrentEpoch(ctx) +} + +// SignMessageViaRelay signs a message via the relay +func (k Keeper) SignMessageViaRelay(ctx context.Context, keyTag uint32, message []byte) (string, error) { + return k.relayClient.SignMessage(ctx, keyTag, message) +} + +// GetLastValidatorPower returns the last known power for a validator +func (k Keeper) GetLastValidatorPower(ctx sdk.Context, pubkeyHex string) (int64, bool) { + power, err := k.LastValidatorSet.Get(ctx, pubkeyHex) + if err != nil { + return 0, false + } + return power, true +} + +// SetLastValidatorPower sets the power for a validator +func (k Keeper) SetLastValidatorPower(ctx sdk.Context, pubkeyHex string, power int64) error { + return k.LastValidatorSet.Set(ctx, pubkeyHex, power) +} + +// DeleteLastValidatorPower removes a validator from the last set +func (k Keeper) DeleteLastValidatorPower(ctx sdk.Context, pubkeyHex string) error { + return k.LastValidatorSet.Remove(ctx, pubkeyHex) +} + +// IterateLastValidatorSet iterates over the last validator set +func (k Keeper) IterateLastValidatorSet(ctx sdk.Context, cb func(pubkeyHex string, power int64) bool) { + iter, err := k.LastValidatorSet.Iterate(ctx, nil) + if err != nil { + return + } + defer iter.Close() + for ; iter.Valid(); iter.Next() { + kv, err := iter.KeyValue() + if err != nil { + return + } + if cb(kv.Key, kv.Value) { + return + } + } +} + +// Close closes the relay client connection +func (k Keeper) Close() error { + if k.relayClient != nil { + return k.relayClient.Close() + } + return nil +} + +// SlashWithInfractionReason requests the relay to sign a slash message +func (k Keeper) SlashWithInfractionReason( + ctx context.Context, + validatorPubKey []byte, + infractionType types.Infraction, + height int64, +) (string, error) { + params := k.GetParams(sdk.UnwrapSDKContext(ctx)) + + // Create slash message payload + // Format: infraction_type (1 byte) | height (8 bytes) | pubkey (32 bytes) + message := make([]byte, 0, 41) + message = append(message, byte(infractionType)) + message = append(message, encodeHeight(height)...) + message = append(message, validatorPubKey...) + + requestID, err := k.relayClient.SignMessage(ctx, params.SigningKeyTag, message) + if err != nil { + return "", fmt.Errorf("failed to sign slash message: %w", err) + } + + k.Logger(sdk.UnwrapSDKContext(ctx)).Info( + "slash message signed", + "request_id", requestID, + "validator", hex.EncodeToString(validatorPubKey), + "infraction", infractionType, + ) + + return requestID, nil +} + +func encodeHeight(height int64) []byte { + bz := make([]byte, 8) + for i := 7; i >= 0; i-- { + bz[i] = byte(height & 0xff) + height >>= 8 + } + return bz +} diff --git a/modules/symstaking/module.go b/modules/symstaking/module.go new file mode 100644 index 00000000..8b7c2c05 --- /dev/null +++ b/modules/symstaking/module.go @@ -0,0 +1,109 @@ +package symstaking + +import ( + "context" + "encoding/json" + "fmt" + + "cosmossdk.io/core/appmodule" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" + + "github.com/evstack/ev-abci/modules/symstaking/keeper" + "github.com/evstack/ev-abci/modules/symstaking/types" +) + +var ( + _ module.AppModuleBasic = AppModuleBasic{} + _ appmodule.AppModule = AppModule{} +) + +type AppModuleBasic struct { + cdc codec.Codec +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, +) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + } +} + +// Name returns the symstaking module's name +func (am AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the symstaking module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the symstaking module. +func (am AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genesisState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genesisState); err != nil { + return fmt.Errorf("unmarshal genesis state: %w", err) + } + return genesisState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the symstaking module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *gwruntime.ServeMux) { + // No gRPC gateway routes yet - module doesn't expose query endpoints +} + +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// InitGenesis performs genesis initialization for the symstaking module. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + if err := InitGenesis(ctx, am.keeper, genesisState); err != nil { + panic(fmt.Errorf("init genesis: %w", err)) + } +} + +// ExportGenesis exports genesis state. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(ExportGenesis(ctx, am.keeper)) +} + +// EndBlock returns validator updates from the relay. +func (am AppModule) EndBlock(ctx context.Context) error { + err := am.keeper.EndBlock(sdk.UnwrapSDKContext(ctx)) + if err != nil { + return err + } + return nil +} + +// BeginBlock is a no-op for symstaking. +func (am AppModule) BeginBlock(ctx context.Context) error { + return nil +} diff --git a/modules/symstaking/module/v1/module.pb.go b/modules/symstaking/module/v1/module.pb.go new file mode 100644 index 00000000..d84168bb --- /dev/null +++ b/modules/symstaking/module/v1/module.pb.go @@ -0,0 +1,40 @@ +// Package v1 contains the module configuration types for symstaking. +package v1 + +import ( + _ "cosmossdk.io/depinject/appconfig/v1alpha1" +) + +// Module is the config object for the symstaking module. +type Module struct { + // authority defines the custom module authority. If not set, defaults to the governance module. + Authority string + // relay_rpc_address is the gRPC address of the Symbiotic relay sidecar. + RelayRpcAddress string + // key_file is the path to the validator keys file (for mock mode). + KeyFile string +} + +// GetAuthority returns the module authority. +func (m *Module) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +// GetRelayRpcAddress returns the relay RPC address. +func (m *Module) GetRelayRpcAddress() string { + if m != nil { + return m.RelayRpcAddress + } + return "" +} + +// GetKeyFile returns the key file path. +func (m *Module) GetKeyFile() string { + if m != nil { + return m.KeyFile + } + return "" +} diff --git a/modules/symstaking/relay/client.go b/modules/symstaking/relay/client.go new file mode 100644 index 00000000..56df33e8 --- /dev/null +++ b/modules/symstaking/relay/client.go @@ -0,0 +1,235 @@ +package relay + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strconv" + "sync" + + v1 "github.com/symbioticfi/relay/api/client/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + abci "github.com/cometbft/cometbft/abci/types" + cryptoProto "github.com/cometbft/cometbft/proto/tendermint/crypto" +) + +// Client wraps the Symbiotic relay gRPC client. +type Client struct { + conn *grpc.ClientConn + client *v1.SymbioticClient + mu sync.RWMutex + + // Mock mode fields + mockMode bool + mockEpoch uint64 + mockValidators []ValidatorInfo +} + +// ValidatorInfo contains validator information from the relay. +type ValidatorInfo struct { + PubKey []byte + Power int64 +} + +// Config holds configuration for the relay client. +type Config struct { + // RPCAddress is the gRPC address of the relay sidecar. + RPCAddress string + // KeyFile is the path to the validator keys file (for mock mode). + KeyFile string +} + +// NewClient creates a new relay client. +func NewClient(cfg Config) (*Client, error) { + if cfg.RPCAddress == "" && cfg.KeyFile == "" { + return nil, fmt.Errorf("either RPCAddress or KeyFile must be provided") + } + + // If KeyFile is provided, use mock client + if cfg.KeyFile != "" { + return newMockClient(cfg.KeyFile) + } + + // Connect to real relay sidecar + conn, err := grpc.NewClient(cfg.RPCAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, fmt.Errorf("failed to connect to relay: %w", err) + } + + return &Client{ + conn: conn, + client: v1.NewSymbioticClient(conn), + }, nil +} + +// Close closes the relay client connection. +func (c *Client) Close() error { + if c.conn != nil { + return c.conn.Close() + } + return nil +} + +// GetCurrentEpoch fetches the current epoch from the relay. +func (c *Client) GetCurrentEpoch(ctx context.Context) (uint64, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.mockMode { + return c.mockEpoch, nil + } + + if c.client == nil { + return 0, fmt.Errorf("relay client not initialized") + } + + resp, err := c.client.GetCurrentEpoch(ctx, &v1.GetCurrentEpochRequest{}) + if err != nil { + return 0, fmt.Errorf("failed to get current epoch: %w", err) + } + + return resp.GetEpoch(), nil +} + +// GetValidatorSet fetches the validator set for a given epoch. +func (c *Client) GetValidatorSet(ctx context.Context, epoch uint64) ([]abci.ValidatorUpdate, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.mockMode { + updates := make([]abci.ValidatorUpdate, 0, len(c.mockValidators)) + for _, v := range c.mockValidators { + updates = append(updates, abci.ValidatorUpdate{ + PubKey: cryptoProto.PublicKey{Sum: &cryptoProto.PublicKey_Ed25519{Ed25519: v.PubKey}}, + Power: v.Power, + }) + } + return updates, nil + } + + if c.client == nil { + return nil, fmt.Errorf("relay client not initialized") + } + + resp, err := c.client.GetValidatorSet(ctx, &v1.GetValidatorSetRequest{ + Epoch: &epoch, + }) + if err != nil { + return nil, fmt.Errorf("failed to get validator set: %w", err) + } + + valSet := resp.GetValidatorSet() + if valSet == nil { + return nil, fmt.Errorf("empty validator set") + } + + validators := valSet.GetValidators() + updates := make([]abci.ValidatorUpdate, 0, len(validators)) + for _, v := range validators { + // Find the Ed25519 key with the validator key tag (tag 43 by default) + keys := v.GetKeys() + var ed25519Key []byte + for _, key := range keys { + // Key tag >> 4 == 2 means Ed25519 key type + if key.GetTag()>>4 == 2 { + ed25519Key = key.GetPayload() + break + } + } + if ed25519Key == nil { + continue + } + + // Parse voting power from string + power, err := strconv.ParseInt(v.GetVotingPower(), 10, 64) + if err != nil { + power = 1 // Default power + } + + updates = append(updates, abci.ValidatorUpdate{ + PubKey: cryptoProto.PublicKey{Sum: &cryptoProto.PublicKey_Ed25519{Ed25519: ed25519Key}}, + Power: power, + }) + } + + return updates, nil +} + +// SignMessage requests the relay to sign a message. +func (c *Client) SignMessage(ctx context.Context, keyTag uint32, message []byte) (string, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.mockMode { + // Return a mock request ID + return fmt.Sprintf("mock-request-%x", message[:8]), nil + } + + if c.client == nil { + return "", fmt.Errorf("relay client not initialized") + } + + resp, err := c.client.SignMessage(ctx, &v1.SignMessageRequest{ + KeyTag: keyTag, + Message: message, + }) + if err != nil { + return "", fmt.Errorf("failed to sign message: %w", err) + } + + return resp.GetRequestId(), nil +} + +// Mock client implementation for testing + +func newMockClient(keyFile string) (*Client, error) { + data, err := os.ReadFile(keyFile) + if err != nil { + return nil, fmt.Errorf("failed to read key file: %w", err) + } + + var keyData struct { + Epoch uint64 `json:"epoch"` + Validators []struct { + PublicKey string `json:"public_key"` + Power int64 `json:"power"` + } `json:"validators"` + } + + if err := json.Unmarshal(data, &keyData); err != nil { + return nil, fmt.Errorf("failed to parse key file: %w", err) + } + + validators := make([]ValidatorInfo, 0, len(keyData.Validators)) + for _, v := range keyData.Validators { + // Parse hex public key + pubKeyBytes, err := hex.DecodeString(v.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to decode public key: %w", err) + } + validators = append(validators, ValidatorInfo{ + PubKey: pubKeyBytes, + Power: v.Power, + }) + } + + return &Client{ + mockMode: true, + mockEpoch: keyData.Epoch, + mockValidators: validators, + }, nil +} + +// ConfigFromEnv creates a Config from environment variables. +func ConfigFromEnv() Config { + return Config{ + RPCAddress: os.Getenv("SYMBIOTIC_RELAY_RPC"), + KeyFile: os.Getenv("SYMBIOTIC_KEY_FILE"), + } +} diff --git a/modules/symstaking/types/codec.go b/modules/symstaking/types/codec.go new file mode 100644 index 00000000..3c1bd2cb --- /dev/null +++ b/modules/symstaking/types/codec.go @@ -0,0 +1,22 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" +) + +// RegisterCodec registers the necessary symstaking interfaces and concrete types +// on the provided LegacyAmino codec. +func RegisterCodec(cdc *codec.LegacyAmino) { + // No legacy amino types to register +} + +// RegisterLegacyAminoCodec is an alias for RegisterCodec for compatibility. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + RegisterCodec(cdc) +} + +// RegisterInterfaces registers the module's interfaces with the interface registry. +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + // No interfaces to register yet +} diff --git a/modules/symstaking/types/errors.go b/modules/symstaking/types/errors.go new file mode 100644 index 00000000..a426b4f0 --- /dev/null +++ b/modules/symstaking/types/errors.go @@ -0,0 +1,14 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// Symstaking module errors +var ( + ErrInvalidKeyTag = errorsmod.Register(ModuleName, 1, "invalid key tag") + ErrInvalidEpoch = errorsmod.Register(ModuleName, 2, "invalid epoch") + ErrValidatorNotFound = errorsmod.Register(ModuleName, 3, "validator not found") + ErrRelayClientNotSet = errorsmod.Register(ModuleName, 4, "relay client not set") + ErrInvalidValidatorSet = errorsmod.Register(ModuleName, 5, "invalid validator set") +) diff --git a/modules/symstaking/types/events.go b/modules/symstaking/types/events.go new file mode 100644 index 00000000..37c75b6e --- /dev/null +++ b/modules/symstaking/types/events.go @@ -0,0 +1,14 @@ +package types + +const ( + EventTypeValidatorSetUpdate = "validator_set_update" + + AttributeKeyEpoch = "epoch" + AttributeKeyPubKey = "pubkey" + AttributeKeyPower = "power" + AttributeKeyAction = "action" + + AttributeActionAdded = "added" + AttributeActionModified = "modified" + AttributeActionRemoved = "removed" +) diff --git a/modules/symstaking/types/expected_keepers.go b/modules/symstaking/types/expected_keepers.go new file mode 100644 index 00000000..fd1f51e0 --- /dev/null +++ b/modules/symstaking/types/expected_keepers.go @@ -0,0 +1,73 @@ +package types + +import ( + "context" + + "cosmossdk.io/core/address" + + abci "github.com/cometbft/cometbft/abci/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AuthKeeper defines the expected interface for the Auth module. +type AuthKeeper interface { + AddressCodec() address.Codec + GetAccount(context.Context, sdk.AccAddress) sdk.AccountI +} + +// BankKeeper defines the expected interface for the Bank module. +type BankKeeper interface { + SpendableCoins(context.Context, sdk.AccAddress) sdk.Coins +} + +// RelayClient defines the interface for the Symbiotic relay client. +type RelayClient interface { + GetCurrentEpoch(ctx context.Context) (uint64, error) + GetValidatorSet(ctx context.Context, epoch uint64) ([]abci.ValidatorUpdate, error) + SignMessage(ctx context.Context, keyTag uint32, message []byte) (string, error) + Close() error +} + +// SymStakingHooks event hooks for staking validator object. +type SymStakingHooks interface { + AfterValidatorCreated(ctx context.Context, consPubKey cryptotypes.PubKey) error + AfterValidatorModified(ctx context.Context, consPubKey cryptotypes.PubKey) error + AfterValidatorRemoved(ctx context.Context, consPubKey cryptotypes.PubKey) error +} + +// MultiSymStakingHooks combines multiple staking hooks. +type MultiSymStakingHooks []SymStakingHooks + +func (h MultiSymStakingHooks) AfterValidatorCreated(ctx context.Context, consPubKey cryptotypes.PubKey) error { + for _, hook := range h { + if err := hook.AfterValidatorCreated(ctx, consPubKey); err != nil { + return err + } + } + return nil +} + +func (h MultiSymStakingHooks) AfterValidatorModified(ctx context.Context, consPubKey cryptotypes.PubKey) error { + for _, hook := range h { + if err := hook.AfterValidatorModified(ctx, consPubKey); err != nil { + return err + } + } + return nil +} + +func (h MultiSymStakingHooks) AfterValidatorRemoved(ctx context.Context, consPubKey cryptotypes.PubKey) error { + for _, hook := range h { + if err := hook.AfterValidatorRemoved(ctx, consPubKey); err != nil { + return err + } + } + return nil +} + +// SymStakingHooksWrapper is a wrapper for modules to inject StakingHooks using depinject. +type SymStakingHooksWrapper struct{ SymStakingHooks } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (SymStakingHooksWrapper) IsOnePerModuleType() {} diff --git a/modules/symstaking/types/genesis.go b/modules/symstaking/types/genesis.go new file mode 100644 index 00000000..b5a8309b --- /dev/null +++ b/modules/symstaking/types/genesis.go @@ -0,0 +1,58 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/gogoproto/proto" +) + +// GenesisState defines the symstaking module's genesis state. +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + Epoch uint64 `json:"epoch" yaml:"epoch"` +} + +// ProtoMessage implements proto.Message. +func (*GenesisState) ProtoMessage() {} + +// Reset implements proto.Message. +func (m *GenesisState) Reset() { *m = GenesisState{} } + +// String implements proto.Message. +func (m *GenesisState) String() string { return proto.CompactTextString(m) } + +// DefaultGenesisState returns the default genesis state. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + Epoch: 0, + } +} + +// Validate performs basic genesis state validation. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return fmt.Errorf("invalid params: %w", err) + } + return nil +} + +// StoreEpoch represents a stored epoch. +type StoreEpoch struct { + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` +} + +// LastValidatorSet represents the last known validator set. +type LastValidatorSet struct { + Epoch uint64 `protobuf:"varint,1,opt,name=epoch,proto3" json:"epoch,omitempty"` + Updates []any `protobuf:"bytes,2,rep,name=updates,proto3" json:"updates,omitempty"` +} + +// Infraction represents the type of infraction. +type Infraction int32 + +const ( + Infraction_INFRACTION_UNSPECIFIED Infraction = 0 + Infraction_INFRACTION_DOUBLE_SIGN Infraction = 1 + Infraction_INFRACTION_DOWNTIME Infraction = 2 +) diff --git a/modules/symstaking/types/keys.go b/modules/symstaking/types/keys.go new file mode 100644 index 00000000..aac76cb8 --- /dev/null +++ b/modules/symstaking/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName is the name of the symstaking module. + ModuleName = "symstaking" +) + +var ( + // ParamsKey is the key for params. + ParamsKey = []byte{0x00} + // CurrentEpochKey is the key for the current epoch. + CurrentEpochKey = []byte{0x01} + // LastValidatorSetPrefix is the prefix for the last validator set. + LastValidatorSetPrefix = []byte{0x02} +) diff --git a/modules/symstaking/types/params.go b/modules/symstaking/types/params.go new file mode 100644 index 00000000..cc61acdc --- /dev/null +++ b/modules/symstaking/types/params.go @@ -0,0 +1,46 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" +) + +// Params defines the parameters for the symstaking module. +type Params struct { + // ValidatorKeyTag is the key tag for validator keys (e.g., 43 for Ed25519 type 2 with id 11). + ValidatorKeyTag uint32 `protobuf:"varint,1,opt,name=validator_key_tag,json=validatorKeyTag,proto3" json:"validator_key_tag,omitempty"` + // SigningKeyTag is the key tag for signing operations. + SigningKeyTag uint32 `protobuf:"varint,2,opt,name=signing_key_tag,json=signingKeyTag,proto3" json:"signing_key_tag,omitempty"` + // EpochCheckInterval is the number of blocks between epoch checks. + EpochCheckInterval int64 `protobuf:"varint,3,opt,name=epoch_check_interval,json=epochCheckInterval,proto3" json:"epoch_check_interval,omitempty"` +} + +// NewParams creates a new Params instance. +func NewParams(validatorKeyTag, signingKeyTag uint32, epochCheckInterval int64) Params { + return Params{ + ValidatorKeyTag: validatorKeyTag, + SigningKeyTag: signingKeyTag, + EpochCheckInterval: epochCheckInterval, + } +} + +// DefaultParams returns a default set of parameters. +func DefaultParams() Params { + return NewParams( + 43, // Key tag 43: type 2 (Ed25519) with id 11 + 15, // Default symbiotic signing key + 10, // Check every 10 blocks + ) +} + +// Validate validates the set of params. +func (p Params) Validate() error { + if p.ValidatorKeyTag>>4 != 2 { + return errorsmod.Wrapf(ErrInvalidKeyTag, "expected key tag to be of type 2 (indicating a ed25519 key), got %d", p.ValidatorKeyTag>>4) + } + if p.EpochCheckInterval <= 0 { + return fmt.Errorf("epoch check interval must be positive") + } + return nil +}