diff --git a/.github/workflows/backend-integration-test.yml b/.github/workflows/backend-integration-test.yml index e05ef5c93..48243480e 100644 --- a/.github/workflows/backend-integration-test.yml +++ b/.github/workflows/backend-integration-test.yml @@ -33,7 +33,7 @@ jobs: cache-dependency-path: 'backend/go.sum' - name: Test with the Go CLI working-directory: backend - run: go test ./pkg/api/... -config "${{ secrets.CI_CONFIG_PATH }}" + run: go test -failfast ./pkg/api/... -config "${{ secrets.CI_CONFIG_PATH }}" diff --git a/.github/workflows/backend-publish-docker.yml b/.github/workflows/backend-publish-docker.yml index b3ea36103..adfb81822 100644 --- a/.github/workflows/backend-publish-docker.yml +++ b/.github/workflows/backend-publish-docker.yml @@ -6,6 +6,7 @@ on: push: paths: - 'backend/**' + - '.github/**' branches: - main - staging @@ -27,6 +28,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Set version + run: | + echo "BEACONCHAIN_VERSION=$(TZ=UTC0 git show --quiet --date='format-local:%Y%m%d%H%M%S' --format="%cd" $GITHUB_SHA)-$(git describe $GITHUB_SHA --always --tags)" >> "$GITHUB_ENV" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. @@ -42,6 +46,9 @@ jobs: uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + ${{ env.BEACONCHAIN_VERSION }} + type=ref,event=branch # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. diff --git a/.github/workflows/frontend-publish-docker.yml b/.github/workflows/frontend-publish-docker.yml index 6e092c37d..8c85468f9 100644 --- a/.github/workflows/frontend-publish-docker.yml +++ b/.github/workflows/frontend-publish-docker.yml @@ -6,6 +6,7 @@ on: push: paths: - 'frontend/**' + - '.github/**' branches: - main - staging @@ -31,7 +32,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Set version run: | - echo "BEACONCHAIN_VERSION=$(git describe --always --tags)" >> "$GITHUB_ENV" + echo "BEACONCHAIN_VERSION=$(TZ=UTC0 git show --quiet --date='format-local:%Y%m%d%H%M%S' --format="%cd" $GITHUB_SHA)-$(git describe $GITHUB_SHA --always --tags)" >> "$GITHUB_ENV" # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 @@ -45,6 +46,9 @@ jobs: uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + ${{ env.BEACONCHAIN_VERSION }} + type=ref,event=branch # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 54f6ccf1c..90ce205f3 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -52,7 +52,6 @@ func Run() { // enable light-weight db connection monitoring monitoring.Init(false) monitoring.Start() - defer monitoring.Stop() } var dataAccessor dataaccess.DataAccessor @@ -98,7 +97,7 @@ func Run() { }() utils.WaitForCtrlC() - + monitoring.Stop() // this will emit a clean shutdown event log.Info("shutting down server") if srv != nil { shutDownCtx, cancelShutDownCtx := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/backend/cmd/monitoring/main.go b/backend/cmd/monitoring/main.go index 57df9f824..a4bef3a60 100644 --- a/backend/cmd/monitoring/main.go +++ b/backend/cmd/monitoring/main.go @@ -55,8 +55,8 @@ func Run() { monitoring.Init(true) monitoring.Start() - defer monitoring.Stop() // gotta wait forever utils.WaitForCtrlC() + monitoring.Stop() } diff --git a/backend/go.mod b/backend/go.mod index fe53e1c8e..b53006df9 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -22,6 +22,7 @@ require ( github.com/doug-martin/goqu/v9 v9.19.0 github.com/ethereum/go-ethereum v1.13.12 github.com/fergusstrange/embedded-postgres v1.29.0 + github.com/gavv/httpexpect/v2 v2.16.0 github.com/go-faker/faker/v4 v4.3.0 github.com/go-redis/redis/v8 v8.11.5 github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7 @@ -89,6 +90,8 @@ require ( cloud.google.com/go/storage v1.36.0 // indirect github.com/ClickHouse/ch-go v0.58.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect + github.com/ajg/form v1.5.1 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect @@ -124,6 +127,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/fatih/color v1.16.0 // indirect + github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.3 // indirect @@ -135,12 +139,14 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect @@ -149,6 +155,7 @@ require ( github.com/herumi/bls-eth-go-binary v1.31.0 // indirect github.com/holiman/uint256 v1.2.4 // indirect github.com/huandu/go-clone v1.6.0 // indirect + github.com/imkira/go-interpol v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect github.com/ipfs/boxo v0.8.0 // indirect github.com/ipfs/go-bitfield v1.1.0 // indirect @@ -181,6 +188,7 @@ require ( github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -207,7 +215,9 @@ require ( github.com/prysmaticlabs/prysm/v3 v3.2.2 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/rs/zerolog v1.29.1 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/segmentio/asm v1.2.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -215,6 +225,8 @@ require ( github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.34.0 // indirect github.com/wealdtech/go-bytesutil v1.2.0 // indirect github.com/wealdtech/go-merkletree v1.0.1-0.20190605192610-2bb163c2ea2a // indirect github.com/wealdtech/go-multicodec v1.4.0 // indirect @@ -224,6 +236,9 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect @@ -247,6 +262,7 @@ require ( gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect + moul.io/http2curl/v2 v2.3.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index f3e0980f2..7d41b5693 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -41,8 +41,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= +github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= @@ -51,6 +55,7 @@ github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885 h1:UdHe github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:ceKFatoD+hfHWWeHOAYue1J+XgOJjE7dw8l3JtIRTGY= github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw= github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= @@ -177,6 +182,7 @@ github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -233,6 +239,8 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+ne github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -251,6 +259,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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect/v2 v2.16.0 h1:Ty2favARiTYTOkCRZGX7ojXXjGyNAIohM1lZ3vqaEwI= +github.com/gavv/httpexpect/v2 v2.16.0/go.mod h1:uJLaO+hQ25ukBJtQi750PsztObHybNllN+t+MbbW8PY= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= @@ -299,6 +309,8 @@ github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280 h1:zHl4a19bwoa3 github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280/go.mod h1:1PLeTVRw8Rpmi0o/kRuoJEXOXecZRqSjoAxEMbj+usA= github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5 h1:8kVoXCPhDwSjaGlKzBVQeE8n49k6jZumBGiP26FHNy0= github.com/gobitfly/prysm/v3 v3.0.0-20230216184552-2f3f1e8190d5/go.mod h1:+v+em7rOykPs93APGWCX/95/3uxU8bSVmbZ4+YNJzdA= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= @@ -357,6 +369,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -407,6 +421,8 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/herumi/bls-eth-go-binary v1.31.0 h1:9eeW3EA4epCb7FIHt2luENpAW69MvKGL5jieHlBiP+w= github.com/herumi/bls-eth-go-binary v1.31.0/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= +github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -426,6 +442,8 @@ github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFck github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= @@ -564,6 +582,7 @@ github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhd github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -657,6 +676,8 @@ github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dz github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= @@ -747,9 +768,11 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhM github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= @@ -811,9 +834,14 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= @@ -845,6 +873,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -861,6 +890,7 @@ github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbe github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= 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/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -880,6 +910,11 @@ github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60Nt github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw= github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= github.com/warpfork/go-testmark v0.11.0 h1:J6LnV8KpceDvo7spaNU4+DauH2n1x+6RaO2rJrmpQ9U= @@ -921,11 +956,19 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1 h1:Ebo6J5AMXgJ3A438ECYotA0aK7ETqjQx9WoZvVxzKBE= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJacKKYjzQa7cVgX5U2nc= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -987,6 +1030,7 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= @@ -1002,6 +1046,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= @@ -1072,6 +1117,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1113,6 +1159,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1168,6 +1215,7 @@ gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= 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= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1226,6 +1274,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= +moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= diff --git a/backend/pkg/api/api_test.go b/backend/pkg/api/api_test.go index 03febe7bd..c422bf17f 100644 --- a/backend/pkg/api/api_test.go +++ b/backend/pkg/api/api_test.go @@ -1,19 +1,20 @@ package api_test import ( - "bytes" - "encoding/json" + "crypto/tls" "flag" - "io" + "fmt" "net/http" "net/http/cookiejar" "net/http/httptest" "os" "os/exec" + "sort" "testing" "time" embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/gavv/httpexpect/v2" "github.com/gobitfly/beaconchain/pkg/api" dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access" api_types "github.com/gobitfly/beaconchain/pkg/api/types" @@ -27,50 +28,17 @@ import ( "golang.org/x/crypto/bcrypt" ) -var ts *testServer +var ts *httptest.Server var dataAccessor dataaccess.DataAccessor var postgres *embeddedpostgres.EmbeddedPostgres -type testServer struct { - *httptest.Server -} - -func (ts *testServer) request(t *testing.T, method, urlPath string, data io.Reader) (int, string) { - req, err := http.NewRequest(method, ts.URL+urlPath, data) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rs, err := ts.Client().Do(req) - if err != nil { - t.Fatal(err) - } - defer rs.Body.Close() - body, err := io.ReadAll(rs.Body) +func TestMain(m *testing.M) { + err := setup() if err != nil { - t.Fatal(err) - } - bytes.TrimSpace(body) - return rs.StatusCode, string(body) -} -func (ts *testServer) get(t *testing.T, urlPath string) (int, string) { - return ts.request(t, http.MethodGet, urlPath, nil) -} -func (ts *testServer) post(t *testing.T, urlPath string, data io.Reader) (int, string) { - return ts.request(t, http.MethodPost, urlPath, data) -} - -func (ts *testServer) parseErrorResonse(t *testing.T, body string) api_types.ApiErrorResponse { - resp := api_types.ApiErrorResponse{} - if err := json.Unmarshal([]byte(body), &resp); err != nil { - t.Fatal(err) + log.Error(err, "error setting up test environment", 0) + teardown() + os.Exit(1) } - return resp -} - -func TestMain(m *testing.M) { - setup() log.Info("test setup completed") code := m.Run() teardown() @@ -82,15 +50,21 @@ func TestMain(m *testing.M) { } func teardown() { - dataAccessor.Close() - ts.Close() - err := postgres.Stop() - if err != nil { - log.Error(err, "error stopping embedded postgres", 0) + if dataAccessor != nil { + dataAccessor.Close() + } + if ts != nil { + ts.Close() + } + if postgres != nil { + err := postgres.Stop() + if err != nil { + log.Error(err, "error stopping embedded postgres", 0) + } } } -func setup() { +func setup() error { configPath := flag.String("config", "", "Path to the config file, if empty string defaults will be used") flag.Parse() @@ -102,17 +76,17 @@ func setup() { postgres = embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Username("postgres")) err := postgres.Start() if err != nil { - log.Fatal(err, "error starting embedded postgres", 0) + return fmt.Errorf("error starting embedded postgres: %w", err) } // connection the the embedded db and run migrations tempDb, err := sqlx.Connect("postgres", "host=localhost port=5432 user=postgres password=postgres dbname=postgres sslmode=disable") if err != nil { - log.Fatal(err, "error connection to test db", 0) + return fmt.Errorf("error connection to test db: %w", err) } if err := goose.Up(tempDb.DB, "../../pkg/commons/db/migrations/postgres"); err != nil { - log.Fatal(err, "error running migrations", 0) + return fmt.Errorf("error running migrations: %w", err) } // insert dummy user for testing (email: admin@admin, password: admin) @@ -123,13 +97,24 @@ func setup() { string(pHash), "admin@admin.com", time.Now().Unix(), "admin", true, ) if err != nil { - log.Fatal(err, "error inserting user", 0) + return fmt.Errorf("error inserting user: %w", err) + } + + // required for shared dashboard + pHash, _ = bcrypt.GenerateFromPassword([]byte("admin"), 10) + _, err = tempDb.Exec(` + INSERT INTO users (id, password, email, register_ts, api_key, email_confirmed) + VALUES ($1, $2, $3, TO_TIMESTAMP($4), $5, $6)`, + 122558, string(pHash), "admin2@admin.com", time.Now().Unix(), "admin2", true, + ) + if err != nil { + return fmt.Errorf("error inserting user 2: %w", err) } cfg := &types.Config{} err = utils.ReadConfig(cfg, *configPath) if err != nil { - log.Fatal(err, "error reading config file", 0) + return fmt.Errorf("error reading config file: %w", err) } // hardcode db connection details for testing @@ -156,21 +141,56 @@ func setup() { log.Info("initializing api router") router := api.NewApiRouter(dataAccessor, cfg) - ts = &testServer{httptest.NewTLSServer(router)} + ts = httptest.NewTLSServer(router) jar, err := cookiejar.New(nil) if err != nil { - log.Fatal(err, "error creating cookie jar", 0) + return fmt.Errorf("error creating cookie jar: %w", err) } - ts.Server.Client().Jar = jar + ts.Client().Jar = jar + + return nil +} + +func getExpectConfig(t *testing.T, ts *httptest.Server) httpexpect.Config { + return httpexpect.Config{ + BaseURL: ts.URL, + Reporter: httpexpect.NewAssertReporter(t), + Client: &http.Client{ + Jar: httpexpect.NewCookieJar(), + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + // accept any certificate; for testing only! + //nolint: gosec + InsecureSkipVerify: true, + }, + }, + }, + Printers: []httpexpect.Printer{ + httpexpect.NewCurlPrinter(t), + }, + } +} + +func login(e *httpexpect.Expect) { + e.POST("/api/i/login"). + WithHeader("Content-Type", "application/json"). + WithJSON(map[string]interface{}{"email": "admin@admin.com", "password": "admin"}). + Expect(). + Status(http.StatusOK) +} + +func logout(e *httpexpect.Expect) { + e.POST("/api/i/logout"). + Expect(). + Status(http.StatusOK) } func TestInternalGetProductSummaryHandler(t *testing.T) { - code, body := ts.get(t, "/api/i/product-summary") - assert.Equal(t, http.StatusOK, code) + e := httpexpect.WithConfig(getExpectConfig(t, ts)) respData := api_types.InternalGetProductSummaryResponse{} - err := json.Unmarshal([]byte(body), &respData) - assert.Nil(t, err, "error unmarshalling response") + e.GET("/api/i/product-summary").Expect().Status(http.StatusOK).JSON().Decode(&respData) + assert.NotEqual(t, 0, respData.Data.ValidatorsPerDashboardLimit, "ValidatorsPerDashboardLimit should not be 0") assert.NotEqual(t, 0, len(respData.Data.ApiProducts), "ApiProducts should not be empty") assert.NotEqual(t, 0, len(respData.Data.ExtraDashboardValidatorsPremiumAddon), "ExtraDashboardValidatorsPremiumAddon should not be empty") @@ -178,59 +198,72 @@ func TestInternalGetProductSummaryHandler(t *testing.T) { } func TestInternalGetLatestStateHandler(t *testing.T) { - code, body := ts.get(t, "/api/i/latest-state") - assert.Equal(t, http.StatusOK, code) + e := httpexpect.WithConfig(getExpectConfig(t, ts)) respData := api_types.InternalGetLatestStateResponse{} - err := json.Unmarshal([]byte(body), &respData) - assert.Nil(t, err, "error unmarshalling response") + e.GET("/api/i/latest-state").Expect().Status(http.StatusOK).JSON().Decode(&respData) + assert.NotEqual(t, uint64(0), respData.Data.LatestSlot, "latest slot should not be 0") assert.NotEqual(t, uint64(0), respData.Data.FinalizedEpoch, "finalized epoch should not be 0") } func TestInternalLoginHandler(t *testing.T) { + e := httpexpect.WithConfig(getExpectConfig(t, ts)) t.Run("login with email in wrong format", func(t *testing.T) { - code, body := ts.post(t, "/api/i/login", bytes.NewBuffer([]byte(`{"email": "admin", "password": "admin"}`))) - assert.Equal(t, http.StatusBadRequest, code) - resp := ts.parseErrorResonse(t, body) - assert.Equal(t, "email: given value 'admin' has incorrect format", resp.Error, "unexpected error message") + e.POST("/api/i/login"). + WithHeader("Content-Type", "application/json"). + WithJSON(map[string]interface{}{"email": "admin", "password": "admin"}). + Expect(). + Status(http.StatusBadRequest). + JSON(). + Object(). + HasValue("error", "email: given value 'admin' has incorrect format") }) t.Run("login with correct user and wrong password", func(t *testing.T) { - code, body := ts.post(t, "/api/i/login", bytes.NewBufferString(`{"email": "admin@admin.com", "password": "wrong"}`)) - assert.Equal(t, http.StatusUnauthorized, code, "login should not be successful") - resp := ts.parseErrorResonse(t, body) - assert.Equal(t, "unauthorized: invalid email or password", resp.Error, "unexpected error message") + e.POST("/api/i/login"). + WithHeader("Content-Type", "application/json"). + WithJSON(map[string]interface{}{"email": "admin@admin.com", "password": "wrong"}). + Expect(). + Status(http.StatusUnauthorized). + JSON(). + Object(). + HasValue("error", "unauthorized: invalid email or password") }) t.Run("login with correct user and password", func(t *testing.T) { - code, _ := ts.post(t, "/api/i/login", bytes.NewBufferString(`{"email": "admin@admin.com", "password": "admin"}`)) - assert.Equal(t, http.StatusOK, code, "login should be successful") + login(e) }) t.Run("check if user is logged in and has a valid session", func(t *testing.T) { - code, body := ts.get(t, "/api/i/users/me") - assert.Equal(t, http.StatusOK, code, "call to users/me should be successful") - meResponse := &api_types.InternalGetUserInfoResponse{} - err := json.Unmarshal([]byte(body), meResponse) - assert.Nil(t, err, "error unmarshalling response") + e.GET("/api/i/users/me"). + Expect(). + Status(http.StatusOK). + JSON(). + Decode(&meResponse) + // check if email is censored assert.Equal(t, meResponse.Data.Email, "a***n@a***n.com", "email should be a***n@a***n.com") }) t.Run("check if logout works", func(t *testing.T) { - code, _ := ts.post(t, "/api/i/logout", bytes.NewBufferString(``)) - assert.Equal(t, http.StatusOK, code, "logout should be successful") + logout(e) }) t.Run("// check if user is logged out", func(t *testing.T) { - code, _ := ts.get(t, "/api/i/users/me") - assert.Equal(t, http.StatusUnauthorized, code, "call to users/me should be unauthorized") + e.GET("/api/i/users/me"). + Expect(). + Status(http.StatusUnauthorized) }) } func TestInternalSearchHandler(t *testing.T) { + e := httpexpect.WithConfig(getExpectConfig(t, ts)) + // search for validator with index 5 - code, body := ts.post(t, "/api/i/search", bytes.NewBufferString(` + resp := api_types.InternalPostSearchResponse{} + e.POST("/api/i/search"). + WithHeader("Content-Type", "application/json"). + WithBytes([]byte(` { "input":"5", "networks":[ @@ -246,18 +279,17 @@ func TestInternalSearchHandler(t *testing.T) { "validator_by_public_key", "validators_by_graffiti" ] - }`)) - assert.Equal(t, http.StatusOK, code) + }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) - resp := api_types.InternalPostSearchResponse{} - err := json.Unmarshal([]byte(body), &resp) - assert.Nil(t, err, "error unmarshalling response") assert.NotEqual(t, 0, len(resp.Data), "response data should not be empty") assert.NotNil(t, resp.Data[0].NumValue, "validator index should not be nil") assert.Equal(t, uint64(5), *resp.Data[0].NumValue, "validator index should be 5") // search for validator by pubkey - code, body = ts.post(t, "/api/i/search", bytes.NewBufferString(` + resp = api_types.InternalPostSearchResponse{} + e.POST("/api/i/search"). + WithHeader("Content-Type", "application/json"). + WithBytes([]byte(` { "input":"0x9699af2bad9826694a480cb523cbe545dc41db955356b3b0d4871f1cf3e4924ae4132fa8c374a0505ae2076d3d65b3e0", "networks":[ @@ -273,19 +305,17 @@ func TestInternalSearchHandler(t *testing.T) { "validator_by_public_key", "validators_by_graffiti" ] - }`)) - assert.Equal(t, http.StatusOK, code) + }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) - resp = api_types.InternalPostSearchResponse{} - err = json.Unmarshal([]byte(body), &resp) - assert.Nil(t, err, "error unmarshalling response") assert.NotEqual(t, 0, len(resp.Data), "response data should not be empty") assert.NotNil(t, resp.Data[0].NumValue, "validator index should not be nil") assert.Equal(t, uint64(5), *resp.Data[0].NumValue, "validator index should be 5") // search for validator by withdawal address - code, body = ts.post(t, "/api/i/search", bytes.NewBufferString(` - { + resp = api_types.InternalPostSearchResponse{} + e.POST("/api/i/search"). + WithHeader("Content-Type", "application/json"). + WithBytes([]byte(`{ "input":"0x0e5dda855eb1de2a212cd1f62b2a3ee49d20c444", "networks":[ 17000 @@ -300,41 +330,142 @@ func TestInternalSearchHandler(t *testing.T) { "validator_by_public_key", "validators_by_graffiti" ] - }`)) - assert.Equal(t, http.StatusOK, code) + }`)).Expect().Status(http.StatusOK).JSON().Decode(&resp) - resp = api_types.InternalPostSearchResponse{} - err = json.Unmarshal([]byte(body), &resp) - assert.Nil(t, err, "error unmarshalling response") assert.NotEqual(t, 0, len(resp.Data), "response data should not be empty") assert.NotNil(t, resp.Data[0].NumValue, "validator index should not be nil") assert.Greater(t, *resp.Data[0].NumValue, uint64(0), "returned number of validators should be greater than 0") } -func TestSlotVizHandler(t *testing.T) { - code, body := ts.get(t, "/api/i/validator-dashboards/NQ/slot-viz") - assert.Equal(t, http.StatusOK, code) - - resp := api_types.GetValidatorDashboardSlotVizResponse{} - err := json.Unmarshal([]byte(body), &resp) - assert.Nil(t, err, "error unmarshalling response") - assert.Equal(t, 4, len(resp.Data), "response data should contain the last 4 epochs") - - headStateCount := 0 - for _, epoch := range resp.Data { - if epoch.State == "head" { // count the amount of head epochs returned, should be exactly 1 - headStateCount++ - } - attestationAssignments := 0 - assert.Equal(t, 32, len(epoch.Slots), "each epoch should contain 32 slots") +func TestPublicAndSharedDashboards(t *testing.T) { + t.Parallel() + e := httpexpect.WithConfig(getExpectConfig(t, ts)) + + dashboardIds := []struct { + id string + isShared bool + }{ + {id: "NQ", isShared: false}, + {id: "MSwxNTU2MSwxNTY", isShared: false}, + {id: "v-80d7edaa-74fb-4129-a41e-7700756961cf", isShared: true}, + } - for _, slot := range epoch.Slots { - if slot.Attestations != nil { // count the amount of attestation assignments for each epoch, should be exactly 1 - attestationAssignments++ + for _, dashboardId := range dashboardIds { + t.Run(fmt.Sprintf("[%s]: test slot viz", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardSlotVizResponse{} + e.GET("/api/i/validator-dashboards/{id}/slot-viz", dashboardId.id). + Expect(). + Status(http.StatusOK). + JSON().Decode(&resp) + + assert.Equal(t, 4, len(resp.Data), "response data should contain the last 4 epochs") + + headStateCount := 0 + for _, epoch := range resp.Data { + if epoch.State == "head" { // count the amount of head epochs returned, should be exactly 1 + headStateCount++ + } + attestationAssignments := 0 + assert.Equal(t, 32, len(epoch.Slots), "each epoch should contain 32 slots") + + for _, slot := range epoch.Slots { + if slot.Attestations != nil { // count the amount of attestation assignments for each epoch, should be exactly 1 + attestationAssignments++ + } + } + + assert.GreaterOrEqual(t, attestationAssignments, 1, "epoch should have at least one attestation assignment") } - } - - assert.Equal(t, 1, attestationAssignments, "epoch should have exactly one attestation assignment") + assert.Equal(t, 1, headStateCount, "one of the last 4 epochs should be in head state") + }) + + t.Run(fmt.Sprintf("[%s]: test dashboard overview", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardResponse{} + e.GET("/api/i/validator-dashboards/{id}", dashboardId.id). + Expect(). + Status(http.StatusOK). + JSON().Decode(&resp) + + numValidators := resp.Data.Validators.Exited + resp.Data.Validators.Offline + resp.Data.Validators.Pending + resp.Data.Validators.Online + resp.Data.Validators.Slashed + assert.Greater(t, numValidators, uint64(0), "dashboard should contain at least one validator") + assert.Greater(t, len(resp.Data.Groups), 0, "dashboard should contain at least one group") + }) + + t.Run(fmt.Sprintf("[%s]: test group summary", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardSummaryResponse{} + e.GET("/api/i/validator-dashboards/{id}/summary", dashboardId.id). + WithQuery("period", "last_24h"). + WithQuery("limit", "10"). + WithQuery("sort", "efficiency:desc"). + Expect().Status(http.StatusOK).JSON().Decode(&resp) + + assert.Greater(t, len(resp.Data), 0, "dashboard should contain at least one group summary row") + + t.Run(fmt.Sprintf("[%s / %d]: test group details", dashboardId.id, resp.Data[0].GroupId), func(t *testing.T) { + groupResp := api_types.GetValidatorDashboardGroupSummaryResponse{} + e.GET("/api/i/validator-dashboards/{id}/groups/{groupId}/summary", dashboardId.id, resp.Data[0].GroupId). + WithQuery("period", "all_time"). + Expect(). + Status(http.StatusOK). + JSON().Decode(&groupResp) + + assert.Greater(t, groupResp.Data.AttestationsHead.Success+groupResp.Data.AttestationsHead.Failed, uint64(0), "group should have at least head attestation") + assert.Greater(t, groupResp.Data.AttestationsSource.Success+groupResp.Data.AttestationsSource.Failed, uint64(0), "group should have at least source attestation") + assert.Greater(t, groupResp.Data.AttestationsTarget.Success+groupResp.Data.AttestationsTarget.Failed, uint64(0), "group should have at least target attestation") + }) + }) + + t.Run(fmt.Sprintf("[%s]: test group summary chart", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardSummaryChartResponse{} + e.GET("/api/i/validator-dashboards/{id}/summary-chart", dashboardId.id). + WithQuery("aggregation", "hourly"). + WithQuery("before_ts", time.Now().Unix()). + WithQuery("efficiency_type", "all"). + WithQuery("group_ids", "-1,-2"). + Expect().Status(http.StatusOK).JSON().Decode(&resp) + + assert.Greater(t, len(resp.Data.Categories), 0, "group summary chart categories should not be empty") + assert.Greater(t, len(resp.Data.Series), 0, "group summary chart series should not be empty") + }) + + t.Run(fmt.Sprintf("[%s]: test rewards", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardRewardsResponse{} + e.GET("/api/i/validator-dashboards/{id}/rewards", dashboardId.id). + WithQuery("limit", 10). + WithQuery("sort", "epoch:desc"). + Expect().Status(http.StatusOK).JSON().Decode(&resp) + + assert.Greater(t, len(resp.Data), 0, "rewards response should not be empty") + assert.LessOrEqual(t, len(resp.Data), 10, "rewards response should not contain more than 10 entries") + assert.True(t, sort.SliceIsSorted(resp.Data, func(i, j int) bool { + return resp.Data[i].Epoch > resp.Data[j].Epoch + }), "rewards should be sorted by epoch in descending order") + + resp = api_types.GetValidatorDashboardRewardsResponse{} + e.GET("/api/i/validator-dashboards/{id}/rewards", dashboardId.id). + WithQuery("limit", 10). + WithQuery("sort", "epoch:asc"). + Expect().Status(http.StatusOK).JSON().Decode(&resp) + assert.Greater(t, len(resp.Data), 0, "rewards response should not be empty") + assert.LessOrEqual(t, len(resp.Data), 10, "rewards response should not contain more than 10 entries") + assert.True(t, sort.SliceIsSorted(resp.Data, func(i, j int) bool { + return resp.Data[i].Epoch < resp.Data[j].Epoch + }), "rewards should be sorted by epoch in ascending order") + + rewardDetails := api_types.GetValidatorDashboardGroupRewardsResponse{} + e.GET("/api/i/validator-dashboards/{id}/groups/{group_id}/rewards/{epoch}", dashboardId.id, resp.Data[0].GroupId, resp.Data[0].Epoch). + WithQuery("limit", 10). + WithQuery("sort", "epoch:asc"). + Expect().Status(http.StatusOK).JSON().Decode(&rewardDetails) + }) + + t.Run(fmt.Sprintf("[%s]: test rewards chart", dashboardId.id), func(t *testing.T) { + resp := api_types.GetValidatorDashboardRewardsChartResponse{} + e.GET("/api/i/validator-dashboards/{id}/rewards-chart", dashboardId.id). + Expect().Status(http.StatusOK).JSON().Decode(&resp) + + assert.Greater(t, len(resp.Data.Categories), 0, "rewards chart categories should not be empty") + assert.Greater(t, len(resp.Data.Series), 0, "rewards chart series should not be empty") + }) } - assert.Equal(t, 1, headStateCount, "one of the last 4 epochs should be in head state") } diff --git a/backend/pkg/api/data_access/healthz.go b/backend/pkg/api/data_access/healthz.go index 525ed0db7..303184532 100644 --- a/backend/pkg/api/data_access/healthz.go +++ b/backend/pkg/api/data_access/healthz.go @@ -4,6 +4,7 @@ import ( "context" "slices" + ch "github.com/ClickHouse/clickhouse-go/v2" "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gobitfly/beaconchain/pkg/commons/db" "github.com/gobitfly/beaconchain/pkg/commons/log" @@ -19,7 +20,17 @@ func (d *DataAccessService) GetHealthz(ctx context.Context, showAll bool) types. var results []types.HealthzResult var response types.HealthzData query := ` - with active_reports as ( + with clean_shutdown_events as ( + SELECT + emitter, + toNullable(inserted_at) as inserted_at + FROM + status_reports + WHERE + deployment_type = {deployment_type:String} + AND inserted_at >= now() - interval 1 days + AND event_id = {clean_shutdown_event_id:String} + ), active_reports as ( SELECT event_id, emitter, @@ -31,7 +42,8 @@ func (d *DataAccessService) GetHealthz(ctx context.Context, showAll bool) types. status, metadata FROM status_reports - WHERE expires_at > now() and deployment_type = ? + LEFT JOIN clean_shutdown_events cse ON status_reports.emitter = clean_shutdown_events.emitter + WHERE expires_at > now() and deployment_type = {deployment_type:String} and (status_reports.inserted_at < cse.inserted_at or cse.inserted_at is null) ORDER BY event_id ASC, emitter ASC, @@ -99,7 +111,7 @@ func (d *DataAccessService) GetHealthz(ctx context.Context, showAll bool) types. response.Reports = make(map[string][]types.HealthzResult) response.ReportingUUID = utils.GetUUID() response.DeploymentType = utils.Config.DeploymentType - err := db.ClickHouseReader.SelectContext(ctx, &results, query, utils.Config.DeploymentType) + err := db.ClickHouseReader.SelectContext(ctx, &results, query, ch.Named("deployment_type", utils.Config.DeploymentType), ch.Named("clean_shutdown_event_id", constants.CleanShutdownEvent)) if err != nil { response.Reports["response_error"] = []types.HealthzResult{ { diff --git a/backend/pkg/api/data_access/user.go b/backend/pkg/api/data_access/user.go index 8f566faac..598f83995 100644 --- a/backend/pkg/api/data_access/user.go +++ b/backend/pkg/api/data_access/user.go @@ -682,6 +682,7 @@ func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64 dbReturn := []struct { Id uint64 `db:"id"` Name string `db:"name"` + Network uint64 `db:"network"` IsArchived sql.NullString `db:"is_archived"` PublicId sql.NullString `db:"public_id"` PublicName sql.NullString `db:"public_name"` @@ -692,6 +693,7 @@ func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64 SELECT uvd.id, uvd.name, + uvd.network, uvd.is_archived, uvds.public_id, uvds.name AS public_name, @@ -709,6 +711,7 @@ func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64 validatorDashboardMap[row.Id] = &t.ValidatorDashboard{ Id: row.Id, Name: row.Name, + Network: row.Network, PublicIds: []t.VDBPublicId{}, IsArchived: row.IsArchived.Valid, ArchivedReason: row.IsArchived.String, diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go index 3d4aa939d..9163a51ab 100644 --- a/backend/pkg/api/data_access/vdb_management.go +++ b/backend/pkg/api/data_access/vdb_management.go @@ -67,6 +67,7 @@ func (d *DataAccessService) GetValidatorDashboardInfo(ctx context.Context, dashb wg.Go(func() error { dbReturn := []struct { Name string `db:"name"` + Network uint64 `db:"network"` IsArchived sql.NullString `db:"is_archived"` PublicId sql.NullString `db:"public_id"` PublicName sql.NullString `db:"public_name"` @@ -76,6 +77,7 @@ func (d *DataAccessService) GetValidatorDashboardInfo(ctx context.Context, dashb err := d.alloyReader.SelectContext(ctx, &dbReturn, ` SELECT uvd.name, + uvd.network, uvd.is_archived, uvds.public_id, uvds.name AS public_name, @@ -95,6 +97,7 @@ func (d *DataAccessService) GetValidatorDashboardInfo(ctx context.Context, dashb mutex.Lock() result.Id = uint64(dashboardId) result.Name = dbReturn[0].Name + result.Network = dbReturn[0].Network result.IsArchived = dbReturn[0].IsArchived.Valid result.ArchivedReason = dbReturn[0].IsArchived.String @@ -273,6 +276,20 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d data := t.VDBOverviewData{} eg := errgroup.Group{} var err error + + // Network + if dashboardId.Validators == nil { + eg.Go(func() error { + query := `SELECT network + FROM + users_val_dashboards + WHERE + id = $1` + return d.alloyReader.GetContext(ctx, &data.Network, query, dashboardId.Id) + }) + } + // TODO handle network of validator set dashboards + // Groups if dashboardId.Validators == nil && !dashboardId.AggregateGroups { // should have valid primary id diff --git a/backend/pkg/api/data_access/vdb_slotviz.go b/backend/pkg/api/data_access/vdb_slotviz.go index 98d8c60d9..67c494a79 100644 --- a/backend/pkg/api/data_access/vdb_slotviz.go +++ b/backend/pkg/api/data_access/vdb_slotviz.go @@ -4,7 +4,6 @@ import ( "context" t "github.com/gobitfly/beaconchain/pkg/api/types" - "github.com/gobitfly/beaconchain/pkg/commons/cache" "github.com/gobitfly/beaconchain/pkg/commons/utils" ) @@ -17,8 +16,16 @@ func (d *DataAccessService) GetValidatorDashboardSlotViz(ctx context.Context, da validatorsMap := utils.SliceToMap(validatorsArray) + maxValidatorsInResponse := 6 + + dutiesInfo, err := d.services.GetCurrentDutiesInfo() + if err != nil { + return nil, err + } + // Get min/max slot/epoch - headEpoch := cache.LatestEpoch.Get() // Reminder: Currently it is possible to get the head epoch from the cache but nothing sets it in v2 + headEpoch := utils.EpochOfSlot(dutiesInfo.LatestSlot) + slotsPerEpoch := utils.Config.Chain.ClConfig.SlotsPerEpoch minEpoch := uint64(0) @@ -27,13 +34,6 @@ func (d *DataAccessService) GetValidatorDashboardSlotViz(ctx context.Context, da } maxEpoch := headEpoch + 1 - maxValidatorsInResponse := 6 - - dutiesInfo, err := d.services.GetCurrentDutiesInfo() - if err != nil { - return nil, err - } - epochToIndexMap := make(map[uint64]uint64) slotToIndexMap := make(map[uint64]uint64) diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go index 54f25b5c9..ecc9794db 100644 --- a/backend/pkg/api/handlers/common.go +++ b/backend/pkg/api/handlers/common.go @@ -67,6 +67,7 @@ var ( reEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") rePassword = regexp.MustCompile(`^.{5,}$`) reEmailUserToken = regexp.MustCompile(`^[a-z0-9]{40}$`) + reJsonContentType = regexp.MustCompile(`^application\/json(;.*)?$`) ) const ( @@ -184,7 +185,7 @@ func (v *validationError) checkUserEmailToken(token string) string { // return error only if internal error occurs, otherwise add error to validationError and/or return nil func (v *validationError) checkBody(data interface{}, r *http.Request) error { // check if content type is application/json - if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { + if contentType := r.Header.Get("Content-Type"); !reJsonContentType.MatchString(contentType) { v.add("request body", "'Content-Type' header must be 'application/json'") } diff --git a/backend/pkg/api/types/dashboard.go b/backend/pkg/api/types/dashboard.go index eaf49a431..d3e334722 100644 --- a/backend/pkg/api/types/dashboard.go +++ b/backend/pkg/api/types/dashboard.go @@ -7,6 +7,7 @@ type AccountDashboard struct { type ValidatorDashboard struct { Id uint64 `json:"id"` Name string `json:"name"` + Network uint64 `json:"network"` PublicIds []VDBPublicId `json:"public_ids,omitempty"` IsArchived bool `json:"is_archived"` ArchivedReason string `json:"archived_reason,omitempty" tstype:"'user' | 'dashboard_limit' | 'validator_limit' | 'group_limit'"` diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go index cef626e57..39d5e5d0d 100644 --- a/backend/pkg/api/types/validator_dashboard.go +++ b/backend/pkg/api/types/validator_dashboard.go @@ -28,6 +28,7 @@ type VDBOverviewBalances struct { type VDBOverviewData struct { Name string `json:"name,omitempty"` + Network uint64 `json:"network"` Groups []VDBOverviewGroup `json:"groups"` Validators VDBOverviewValidators `json:"validators"` Efficiency PeriodicValues[float64] `json:"efficiency"` diff --git a/backend/pkg/commons/db/ens.go b/backend/pkg/commons/db/ens.go index f1cd54e64..46001df56 100644 --- a/backend/pkg/commons/db/ens.go +++ b/backend/pkg/commons/db/ens.go @@ -174,6 +174,11 @@ func (bigtable *Bigtable) TransformEnsNameRegistered(blk *types.Eth1Block, cache log.WarnWithFields(logFields, "error unpacking ens-log") continue } + if err = verifyName(r.Name); err != nil { + logFields["error"] = err + log.WarnWithFields(logFields, "error verifying ens-name") + continue + } keys[fmt.Sprintf("%s:ENS:V:N:%s", bigtable.chainId, r.Name)] = true keys[fmt.Sprintf("%s:ENS:V:A:%x", bigtable.chainId, r.Owner)] = true } else if bytes.Equal(lTopic, ensContracts.ENSETHRegistrarControllerParsedABI.Events["NameRenewed"].ID.Bytes()) { @@ -185,6 +190,11 @@ func (bigtable *Bigtable) TransformEnsNameRegistered(blk *types.Eth1Block, cache log.WarnWithFields(logFields, "error unpacking ens-log") continue } + if err = verifyName(r.Name); err != nil { + logFields["error"] = err + log.WarnWithFields(logFields, "error verifying ens-name") + continue + } keys[fmt.Sprintf("%s:ENS:V:N:%s", bigtable.chainId, r.Name)] = true } } else if ensContract == "OldEnsRegistrarController" { diff --git a/backend/pkg/commons/log/log.go b/backend/pkg/commons/log/log.go index dc9c11b0b..60684f6cb 100644 --- a/backend/pkg/commons/log/log.go +++ b/backend/pkg/commons/log/log.go @@ -36,12 +36,7 @@ func Infof(format string, args ...interface{}) { } func InfoWithFields(additionalInfos Fields, msg string) { - logFields := logrus.NewEntry(logrus.New()) - for name, info := range additionalInfos { - logFields = logFields.WithField(name, info) - } - - logFields.Info(msg) + logrus.WithFields(additionalInfos).Info(msg) } func Warn(args ...interface{}) { @@ -53,12 +48,7 @@ func Warnf(format string, args ...interface{}) { } func WarnWithFields(additionalInfos Fields, msg string) { - logFields := logrus.NewEntry(logrus.New()) - for name, info := range additionalInfos { - logFields = logFields.WithField(name, info) - } - - logFields.Warn(msg) + logrus.WithFields(additionalInfos).Warn(msg) } func Tracef(format string, args ...interface{}) { @@ -66,21 +56,11 @@ func Tracef(format string, args ...interface{}) { } func TraceWithFields(additionalInfos Fields, msg string) { - logFields := logrus.NewEntry(logrus.New()) - for name, info := range additionalInfos { - logFields = logFields.WithField(name, info) - } - - logFields.Trace(msg) + logrus.WithFields(additionalInfos).Trace(msg) } func DebugWithFields(additionalInfos Fields, msg string) { - logFields := logrus.NewEntry(logrus.New()) - for name, info := range additionalInfos { - logFields = logFields.WithField(name, info) - } - - logFields.Debug(msg) + logrus.WithFields(additionalInfos).Debug(msg) } func Debugf(format string, args ...interface{}) { @@ -96,7 +76,7 @@ func logErrorInfo(err error, callerSkip int, additionalInfos ...Fields) *logrus. } pc, fullFilePath, line, ok := runtime.Caller(callerSkip + 2) if ok { - logFields = logFields.WithFields(logrus.Fields{ + logFields = logFields.WithFields(Fields{ "_file": filepath.Base(fullFilePath), "_function": runtime.FuncForPC(pc).Name(), "_line": line, @@ -152,4 +132,4 @@ func logErrorInfo(err error, callerSkip int, additionalInfos ...Fields) *logrus. return logFields } -type Fields map[string]interface{} +type Fields = logrus.Fields diff --git a/backend/pkg/commons/utils/uuid.go b/backend/pkg/commons/utils/uuid.go index 6932f1c76..5142a1143 100644 --- a/backend/pkg/commons/utils/uuid.go +++ b/backend/pkg/commons/utils/uuid.go @@ -6,6 +6,7 @@ import ( "github.com/bwmarrin/snowflake" "github.com/gobitfly/beaconchain/pkg/commons/log" "github.com/google/uuid" + "golang.org/x/exp/rand" ) // uuid that you can get - gets set to a random value on startup/first read @@ -30,7 +31,8 @@ func GetSnowflake() int64 { return v.(*snowflake.Node).Generate().Int64() } - node, err := snowflake.NewNode(1) + nodeId := rand.Int63() & 0xFF + node, err := snowflake.NewNode(nodeId) if err != nil { log.Fatal(err, "snowflake generator failed to start", 0) return 0 diff --git a/backend/pkg/monitoring/constants/main.go b/backend/pkg/monitoring/constants/main.go index 7ffa891c2..9e20f037b 100644 --- a/backend/pkg/monitoring/constants/main.go +++ b/backend/pkg/monitoring/constants/main.go @@ -11,3 +11,5 @@ const ( Failure StatusType = "failure" Default time.Duration = -1 * time.Second ) + +const CleanShutdownEvent = "clean_shutdown" diff --git a/backend/pkg/monitoring/monitoring.go b/backend/pkg/monitoring/monitoring.go index f814d3a66..b24c3cb9a 100644 --- a/backend/pkg/monitoring/monitoring.go +++ b/backend/pkg/monitoring/monitoring.go @@ -2,9 +2,11 @@ package monitoring import ( "github.com/gobitfly/beaconchain/pkg/commons/db" + "github.com/gobitfly/beaconchain/pkg/commons/log" "github.com/gobitfly/beaconchain/pkg/commons/metrics" "github.com/gobitfly/beaconchain/pkg/commons/types" "github.com/gobitfly/beaconchain/pkg/commons/utils" + "github.com/gobitfly/beaconchain/pkg/monitoring/constants" "github.com/gobitfly/beaconchain/pkg/monitoring/services" ) @@ -33,6 +35,7 @@ func Init(full bool) { &services.ServiceClickhouseRollings{}, &services.ServiceClickhouseEpoch{}, &services.ServiceTimeoutDetector{}, + &services.CleanShutdownSpamDetector{}, ) } @@ -42,13 +45,17 @@ func Init(full bool) { } func Start() { + log.Infof("starting monitoring services") for _, service := range monitoredServices { service.Start() } } func Stop() { + log.Infof("stopping monitoring services") for _, service := range monitoredServices { service.Stop() } + // this prevents status reports that werent shut down cleanly from triggering alerts + services.NewStatusReport(constants.CleanShutdownEvent, constants.Default, constants.Default)(constants.Success, nil) } diff --git a/backend/pkg/monitoring/services/base.go b/backend/pkg/monitoring/services/base.go index 8bea11b47..549b4d67f 100644 --- a/backend/pkg/monitoring/services/base.go +++ b/backend/pkg/monitoring/services/base.go @@ -3,11 +3,8 @@ package services import ( "context" "fmt" - "os" - "os/signal" "sync" "sync/atomic" - "syscall" "time" "github.com/gobitfly/beaconchain/pkg/commons/db" @@ -45,31 +42,13 @@ func (s *ServiceBase) Stop() { s.wg.Wait() } -var isShuttingDown atomic.Bool -var once sync.Once - -func autoGracefulStop() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-c - isShuttingDown.Store(true) -} - func NewStatusReport(id string, timeout time.Duration, check_interval time.Duration) func(status constants.StatusType, metadata map[string]string) { runId := uuid.New().String() - // run if it hasnt started yet - once.Do(func() { go autoGracefulStop() }) return func(status constants.StatusType, metadata map[string]string) { // acquire snowflake synchronously flake := utils.GetSnowflake() - + now := time.Now() go func() { - // check if we are alive - if isShuttingDown.Load() { - log.Info("shutting down, not reporting status") - return - } - if metadata == nil { metadata = make(map[string]string) } @@ -82,14 +61,23 @@ func NewStatusReport(id string, timeout time.Duration, check_interval time.Durat ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - timeouts_at := time.Now().Add(1 * time.Minute) + timeouts_at := now.Add(1 * time.Minute) if timeout != constants.Default { - timeouts_at = time.Now().Add(timeout) + timeouts_at = now.Add(timeout) } expires_at := timeouts_at.Add(5 * time.Minute) if check_interval >= 5*time.Minute { expires_at = timeouts_at.Add(check_interval) } + log.TraceWithFields(log.Fields{ + "emitter": id, + "event_id": utils.GetUUID(), + "deployment_type": utils.Config.DeploymentType, + "insert_id": flake, + "expires_at": expires_at, + "timeouts_at": timeouts_at, + "metadata": metadata, + }, "sending status report") var err error if db.ClickHouseNativeWriter != nil { err = db.ClickHouseNativeWriter.AsyncInsert( diff --git a/backend/pkg/monitoring/services/clean_shutdown_spam.go b/backend/pkg/monitoring/services/clean_shutdown_spam.go new file mode 100644 index 000000000..0dcf01391 --- /dev/null +++ b/backend/pkg/monitoring/services/clean_shutdown_spam.go @@ -0,0 +1,86 @@ +package services + +import ( + "context" + "encoding/json" + "strconv" + "time" + + "github.com/gobitfly/beaconchain/pkg/commons/db" + "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/utils" + "github.com/gobitfly/beaconchain/pkg/monitoring/constants" +) + +type CleanShutdownSpamDetector struct { + ServiceBase +} + +func (s *CleanShutdownSpamDetector) Start() { + if !s.running.CompareAndSwap(false, true) { + // already running, return error + return + } + s.wg.Add(1) + go s.internalProcess() +} + +func (s *CleanShutdownSpamDetector) internalProcess() { + defer s.wg.Done() + s.runChecks() + for { + select { + case <-s.ctx.Done(): + return + case <-time.After(30 * time.Second): + s.runChecks() + } + } +} + +func (s *CleanShutdownSpamDetector) runChecks() { + id := "monitoring_clean_shutdown_spam" + r := NewStatusReport(id, constants.Default, 30*time.Second) + r(constants.Running, nil) + if db.ClickHouseReader == nil { + r(constants.Failure, map[string]string{"error": "clickhouse reader is nil"}) + // ignore + return + } + log.Tracef("checking clean shutdown spam") + + query := ` + SELECT + emitter + FROM + status_reports + WHERE + deployment_type = ? + AND inserted_at >= now() - interval 5 minutes + AND event_id = ? + ` + ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second) + defer cancel() + var emitters []string + err := db.ClickHouseReader.SelectContext(ctx, &emitters, query, utils.Config.DeploymentType, constants.CleanShutdownEvent) + if err != nil { + r(constants.Failure, map[string]string{"error": err.Error()}) + return + } + threshold := 10 + md := map[string]string{ + "count": strconv.Itoa(len(emitters)), + "threshold": strconv.Itoa(threshold), + } + if len(emitters) > threshold { + payload, err := json.Marshal(emitters) + if err != nil { + r(constants.Failure, map[string]string{"error": err.Error()}) + return + } + md["emitters"] = string(payload) + r(constants.Failure, md) + return + } + r(constants.Success, md) +} diff --git a/backend/pkg/monitoring/services/clickhouse_rollings.go b/backend/pkg/monitoring/services/clickhouse_rollings.go index 259bf5a44..8bab09dc1 100644 --- a/backend/pkg/monitoring/services/clickhouse_rollings.go +++ b/backend/pkg/monitoring/services/clickhouse_rollings.go @@ -71,7 +71,7 @@ func (s *ServiceClickhouseRollings) runChecks() { err := db.ClickHouseReader.GetContext(ctx, &tsEpochTable, ` SELECT max(epoch_timestamp) - FROM holesky.validator_dashboard_data_epoch`, + FROM validator_dashboard_data_epoch`, ) if err != nil { r(constants.Failure, map[string]string{"error": err.Error()}) @@ -81,7 +81,7 @@ func (s *ServiceClickhouseRollings) runChecks() { err = db.ClickHouseReader.GetContext(ctx, &epochRollingTable, fmt.Sprintf(` SELECT max(epoch_end) - FROM holesky.validator_dashboard_data_rolling_%s`, + FROM validator_dashboard_data_rolling_%s`, rolling, ), ) diff --git a/backend/pkg/monitoring/services/timeout_detector.go b/backend/pkg/monitoring/services/timeout_detector.go index 720bb80dc..83f85ccd3 100644 --- a/backend/pkg/monitoring/services/timeout_detector.go +++ b/backend/pkg/monitoring/services/timeout_detector.go @@ -61,7 +61,7 @@ func (s *ServiceTimeoutDetector) runChecks() { status, metadata FROM status_reports - WHERE expires_at > now() and deployment_type = ? + WHERE expires_at > now() and deployment_type = ? and emitter not in (select distinct emitter from status_reports where event_id = ? and inserted_at > now() - interval 1 days) ORDER BY event_id ASC, emitter ASC, @@ -87,6 +87,7 @@ func (s *ServiceTimeoutDetector) runChecks() { ) SELECT event_id, + emitter, status, inserted_at, expires_at, @@ -101,13 +102,14 @@ func (s *ServiceTimeoutDetector) runChecks() { defer cancel() var victims []struct { EventID string `db:"event_id"` + Emitter string `db:"emitter"` Status string `db:"status"` InsertedAt time.Time `db:"inserted_at"` ExpiresAt time.Time `db:"expires_at"` TimeoutsAt time.Time `db:"timeouts_at"` Metadata map[string]string `db:"metadata"` } - err := db.ClickHouseReader.SelectContext(ctx, &victims, query, utils.Config.DeploymentType) + err := db.ClickHouseReader.SelectContext(ctx, &victims, query, utils.Config.DeploymentType, constants.CleanShutdownEvent) if err != nil { r(constants.Failure, map[string]string{"error": err.Error()}) return diff --git a/frontend/.env-example b/frontend/.env-example index 563b8dc92..8e5b8de1b 100644 --- a/frontend/.env-example +++ b/frontend/.env-example @@ -11,4 +11,5 @@ NUXT_PUBLIC_V1_DOMAIN: "" NUXT_PUBLIC_LOG_FILE: "" NUXT_PUBLIC_CHAIN_ID_BY_DEFAULT: "" NUXT_PUBLIC_MAINTENANCE_TS: "1717700652" +NUXT_PUBLIC_DEPLOYMENT_TYPE: "development" PRIVATE_SSR_SECRET: "" diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index 2ddbc4e8a..a97dbcf2a 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -1,17 +1,18 @@ { "conventionalCommits.scopes": [ - "checkout", - "ci", - "customFetch", - "DashboardChartSummaryChartFilter", - "DashboardGroupManagementModal", - "eslint", - "git", - "i18n", - "mainHeader", - "qrCode", - "vscode" -], + "checkout", + "ci", + "customFetch", + "DashboardChartSummaryChartFilter", + "DashboardGroupManagementModal", + "eslint", + "git", + "i18n", + "mainHeader", + "qrCode", + "vscode", + "DashboardValidatorManagmentModal" + ], "editor.codeActionsOnSave": { "source.fixAll.eslint": "always" }, diff --git a/frontend/components/dashboard/ValidatorManagementModal.vue b/frontend/components/dashboard/DashboardValidatorManagementModal.vue similarity index 98% rename from frontend/components/dashboard/ValidatorManagementModal.vue rename to frontend/components/dashboard/DashboardValidatorManagementModal.vue index f64946de6..94762a647 100644 --- a/frontend/components/dashboard/ValidatorManagementModal.vue +++ b/frontend/components/dashboard/DashboardValidatorManagementModal.vue @@ -553,6 +553,15 @@ const premiumLimit = computed( " /> +
+
+ {{ $t("dashboard.validator.col.status") }} +
+ +
{{ $t("dashboard.validator.col.withdrawal_credential") }} @@ -645,6 +654,7 @@ const premiumLimit = computed( display: flex; flex-direction: column; overflow-y: hidden; + justify-content: space-between; :deep(.p-datatable-wrapper) { flex-grow: 1; diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 8c932276c..81bb64d3a 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -78,6 +78,7 @@ export default defineNuxtConfig({ apiClient: process.env.PUBLIC_API_CLIENT, apiKey: process.env.PUBLIC_API_KEY, chainIdByDefault: process.env.PUBLIC_CHAIN_ID_BY_DEFAULT, + deploymentType: process.env.PUBLIC_DEPLOYMENT_TYPE, domain: process.env.PUBLIC_DOMAIN, gitVersion, legacyApiClient: process.env.PUBLIC_LEGACY_API_CLIENT, diff --git a/frontend/stores/dashboard/useUserDashboardStore.ts b/frontend/stores/dashboard/useUserDashboardStore.ts index a8cd42ddf..0d79f1d4d 100644 --- a/frontend/stores/dashboard/useUserDashboardStore.ts +++ b/frontend/stores/dashboard/useUserDashboardStore.ts @@ -121,6 +121,7 @@ export function useUserDashboardStore() { id: res.data.id, is_archived: false, name: res.data.name, + network: res.data.network, validator_count: 0, }, ], diff --git a/frontend/types/api/archiver.ts b/frontend/types/api/archiver.ts new file mode 100644 index 000000000..a0ecfe481 --- /dev/null +++ b/frontend/types/api/archiver.ts @@ -0,0 +1,16 @@ +// Code generated by tygo. DO NOT EDIT. +/* eslint-disable */ + +////////// +// source: archiver.go + +export interface ArchiverDashboard { + DashboardId: number /* uint64 */; + IsArchived: boolean; + GroupCount: number /* uint64 */; + ValidatorCount: number /* uint64 */; +} +export interface ArchiverDashboardArchiveReason { + DashboardId: number /* uint64 */; + ArchivedReason: any /* enums.VDBArchivedReason */; +} diff --git a/frontend/types/api/dashboard.ts b/frontend/types/api/dashboard.ts index 3ab7c3768..16a840350 100644 --- a/frontend/types/api/dashboard.ts +++ b/frontend/types/api/dashboard.ts @@ -12,6 +12,7 @@ export interface AccountDashboard { export interface ValidatorDashboard { id: number /* uint64 */; name: string; + network: number /* uint64 */; public_ids?: VDBPublicId[]; is_archived: boolean; archived_reason?: 'user' | 'dashboard_limit' | 'validator_limit' | 'group_limit'; diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts index af31ad96f..10fb9d3e1 100644 --- a/frontend/types/api/validator_dashboard.ts +++ b/frontend/types/api/validator_dashboard.ts @@ -28,6 +28,7 @@ export interface VDBOverviewBalances { } export interface VDBOverviewData { name?: string; + network: number /* uint64 */; groups: VDBOverviewGroup[]; validators: VDBOverviewValidators; efficiency: PeriodicValues;