diff --git a/.github/workflows/backend-integration-test.yml b/.github/workflows/backend-integration-test.yml new file mode 100644 index 000000000..e05ef5c93 --- /dev/null +++ b/.github/workflows/backend-integration-test.yml @@ -0,0 +1,39 @@ +name: Backend-Integration-Test +on: + push: + paths: + - 'backend/**' + branches: + - main + - staging + pull_request: + paths: + - 'backend/**' + branches: + - '*' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + checks: write + +jobs: + build: + name: integration-test + runs-on: self-hosted + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'backend/go.mod' + 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 }}" + + + diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index bcc36a377..54f6ccf1c 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -60,6 +60,7 @@ func Run() { dataAccessor = dataaccess.NewDummyService() } else { dataAccessor = dataaccess.NewDataAccessService(cfg) + dataAccessor.StartDataAccessServices() } defer dataAccessor.Close() @@ -70,7 +71,7 @@ func Run() { router.Use(metrics.HttpMiddleware) go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/archiver/main.go b/backend/cmd/archiver/main.go new file mode 100644 index 000000000..51efe8336 --- /dev/null +++ b/backend/cmd/archiver/main.go @@ -0,0 +1,47 @@ +package archiver + +import ( + "flag" + "os" + + dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access" + + "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/types" + "github.com/gobitfly/beaconchain/pkg/commons/utils" + "github.com/gobitfly/beaconchain/pkg/commons/version" + + "github.com/gobitfly/beaconchain/pkg/archiver" +) + +func Run() { + fs := flag.NewFlagSet("fs", flag.ExitOnError) + configPath := fs.String("config", "", "Path to the config file, if empty string defaults will be used") + versionFlag := fs.Bool("version", false, "Show version and exit") + _ = fs.Parse(os.Args[2:]) + + if *versionFlag { + log.Info(version.Version) + log.Info(version.GoVersion) + return + } + + cfg := &types.Config{} + err := utils.ReadConfig(cfg, *configPath) + if err != nil { + log.Fatal(err, "error reading config file", 0) + } + utils.Config = cfg + + log.InfoWithFields(log.Fields{"config": *configPath, "version": version.Version, "commit": version.GitCommit, "chainName": utils.Config.Chain.ClConfig.ConfigName}, "starting") + + dataAccessor := dataaccess.NewDataAccessService(cfg) + defer dataAccessor.Close() + + archiver, err := archiver.NewArchiver(dataAccessor) + if err != nil { + log.Fatal(err, "error initializing archiving service", 0) + } + go archiver.Start() + utils.WaitForCtrlC() +} diff --git a/backend/cmd/blobindexer/main.go b/backend/cmd/blobindexer/main.go index af49e3f5c..9f7d37b4a 100644 --- a/backend/cmd/blobindexer/main.go +++ b/backend/cmd/blobindexer/main.go @@ -31,7 +31,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/eth1indexer/main.go b/backend/cmd/eth1indexer/main.go index a88e1edd5..badcc7745 100644 --- a/backend/cmd/eth1indexer/main.go +++ b/backend/cmd/eth1indexer/main.go @@ -96,7 +96,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/ethstore_exporter/main.go b/backend/cmd/ethstore_exporter/main.go index 5a189a36d..502f91e2e 100644 --- a/backend/cmd/ethstore_exporter/main.go +++ b/backend/cmd/ethstore_exporter/main.go @@ -55,7 +55,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/exporter/main.go b/backend/cmd/exporter/main.go index 4f80cf773..2bf0a014e 100644 --- a/backend/cmd/exporter/main.go +++ b/backend/cmd/exporter/main.go @@ -46,7 +46,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 18dcc85d8..813939263 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -5,6 +5,7 @@ import ( "os" "github.com/gobitfly/beaconchain/cmd/api" + "github.com/gobitfly/beaconchain/cmd/archiver" "github.com/gobitfly/beaconchain/cmd/blobindexer" "github.com/gobitfly/beaconchain/cmd/eth1indexer" "github.com/gobitfly/beaconchain/cmd/ethstore_exporter" @@ -32,6 +33,8 @@ func main() { switch target { case "api": api.Run() + case "archiver": + archiver.Run() case "blobindexer": blobindexer.Run() case "eth1indexer": diff --git a/backend/cmd/node_jobs_processor/main.go b/backend/cmd/node_jobs_processor/main.go index 04330e80b..427a1d97b 100644 --- a/backend/cmd/node_jobs_processor/main.go +++ b/backend/cmd/node_jobs_processor/main.go @@ -42,7 +42,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/notification_collector/main.go b/backend/cmd/notification_collector/main.go index 29d2d6a7f..428bc6588 100644 --- a/backend/cmd/notification_collector/main.go +++ b/backend/cmd/notification_collector/main.go @@ -55,7 +55,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/notification_sender/main.go b/backend/cmd/notification_sender/main.go index 4932e1345..e78508597 100644 --- a/backend/cmd/notification_sender/main.go +++ b/backend/cmd/notification_sender/main.go @@ -53,7 +53,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/rewards_exporter/main.go b/backend/cmd/rewards_exporter/main.go index 11894cfe8..874a33558 100644 --- a/backend/cmd/rewards_exporter/main.go +++ b/backend/cmd/rewards_exporter/main.go @@ -58,7 +58,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/signatures/main.go b/backend/cmd/signatures/main.go index f663db7aa..6eb7c216d 100644 --- a/backend/cmd/signatures/main.go +++ b/backend/cmd/signatures/main.go @@ -53,7 +53,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/statistics/main.go b/backend/cmd/statistics/main.go index 708b61adc..17f22e364 100644 --- a/backend/cmd/statistics/main.go +++ b/backend/cmd/statistics/main.go @@ -76,7 +76,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/cmd/user_service/main.go b/backend/cmd/user_service/main.go index 807b4c058..79ba47060 100644 --- a/backend/cmd/user_service/main.go +++ b/backend/cmd/user_service/main.go @@ -46,7 +46,7 @@ func Run() { if utils.Config.Metrics.Enabled { go func() { log.Infof("serving metrics on %v", utils.Config.Metrics.Address) - if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof); err != nil { + if err := metrics.Serve(utils.Config.Metrics.Address, utils.Config.Metrics.Pprof, utils.Config.Metrics.PprofExtra); err != nil { log.Fatal(err, "error serving metrics", 0) } }() diff --git a/backend/go.mod b/backend/go.mod index b82b96ef1..fe53e1c8e 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -21,6 +21,7 @@ require ( github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 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/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 @@ -61,6 +62,7 @@ require ( github.com/rocket-pool/smartnode v1.13.6 github.com/shopspring/decimal v1.3.1 github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d github.com/wealdtech/go-ens/v3 v3.6.0 github.com/wealdtech/go-eth2-types/v2 v2.8.2 @@ -194,6 +196,7 @@ require ( github.com/paulmach/orb v0.10.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/common v0.47.0 // indirect @@ -220,6 +223,7 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect 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/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 diff --git a/backend/go.sum b/backend/go.sum index c41a5f688..f3e0980f2 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -237,6 +237,8 @@ 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= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fergusstrange/embedded-postgres v1.29.0 h1:Uv8hdhoiaNMuH0w8UuGXDHr60VoAQPFdgx7Qf3bzXJM= +github.com/fergusstrange/embedded-postgres v1.29.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= @@ -840,8 +842,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 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 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= @@ -852,8 +855,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= 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= @@ -914,6 +917,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +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/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= diff --git a/backend/pkg/api/api_test.go b/backend/pkg/api/api_test.go new file mode 100644 index 000000000..03febe7bd --- /dev/null +++ b/backend/pkg/api/api_test.go @@ -0,0 +1,340 @@ +package api_test + +import ( + "bytes" + "encoding/json" + "flag" + "io" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "os" + "os/exec" + "testing" + "time" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/gobitfly/beaconchain/pkg/api" + dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access" + api_types "github.com/gobitfly/beaconchain/pkg/api/types" + "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/types" + "github.com/gobitfly/beaconchain/pkg/commons/utils" + "github.com/gobitfly/beaconchain/pkg/commons/version" + "github.com/jmoiron/sqlx" + "github.com/pressly/goose/v3" + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/bcrypt" +) + +var ts *testServer +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) + 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) + } + return resp +} + +func TestMain(m *testing.M) { + setup() + log.Info("test setup completed") + code := m.Run() + teardown() + + // wait till service initialization is completed (TODO: find a better way to do this) + // time.Sleep(30 * time.Second) + + os.Exit(code) +} + +func teardown() { + dataAccessor.Close() + ts.Close() + err := postgres.Stop() + if err != nil { + log.Error(err, "error stopping embedded postgres", 0) + } +} + +func setup() { + configPath := flag.String("config", "", "Path to the config file, if empty string defaults will be used") + + flag.Parse() + + // terminate any currently running postgres instances + _ = exec.Command("pkill", "-9", "postgres").Run() + + // start embedded postgres + postgres = embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Username("postgres")) + err := postgres.Start() + if err != nil { + log.Fatal(err, "error starting embedded postgres", 0) + } + + // 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) + } + + if err := goose.Up(tempDb.DB, "../../pkg/commons/db/migrations/postgres"); err != nil { + log.Fatal(err, "error running migrations", 0) + } + + // insert dummy user for testing (email: admin@admin, password: admin) + pHash, _ := bcrypt.GenerateFromPassword([]byte("admin"), 10) + _, err = tempDb.Exec(` + INSERT INTO users (password, email, register_ts, api_key, email_confirmed) + VALUES ($1, $2, TO_TIMESTAMP($3), $4, $5)`, + string(pHash), "admin@admin.com", time.Now().Unix(), "admin", true, + ) + if err != nil { + log.Fatal(err, "error inserting user", 0) + } + + cfg := &types.Config{} + err = utils.ReadConfig(cfg, *configPath) + if err != nil { + log.Fatal(err, "error reading config file", 0) + } + + // hardcode db connection details for testing + cfg.Frontend.ReaderDatabase.Host = "localhost" + cfg.Frontend.ReaderDatabase.Port = "5432" + cfg.Frontend.ReaderDatabase.Name = "postgres" + cfg.Frontend.ReaderDatabase.Password = "postgres" + cfg.Frontend.ReaderDatabase.Username = "postgres" + + cfg.Frontend.WriterDatabase.Host = "localhost" + cfg.Frontend.WriterDatabase.Port = "5432" + cfg.Frontend.WriterDatabase.Name = "postgres" + cfg.Frontend.WriterDatabase.Password = "postgres" + cfg.Frontend.WriterDatabase.Username = "postgres" + + utils.Config = cfg + + log.InfoWithFields(log.Fields{"config": *configPath, "version": version.Version, "commit": version.GitCommit, "chainName": utils.Config.Chain.ClConfig.ConfigName}, "starting") + + log.Info("initializing data access service") + dataAccessor = dataaccess.NewDataAccessService(cfg) + dataAccessor.StartDataAccessServices() + + log.Info("initializing api router") + router := api.NewApiRouter(dataAccessor, cfg) + + ts = &testServer{httptest.NewTLSServer(router)} + jar, err := cookiejar.New(nil) + if err != nil { + log.Fatal(err, "error creating cookie jar", 0) + } + ts.Server.Client().Jar = jar +} + +func TestInternalGetProductSummaryHandler(t *testing.T) { + code, body := ts.get(t, "/api/i/product-summary") + assert.Equal(t, http.StatusOK, code) + + respData := api_types.InternalGetProductSummaryResponse{} + err := json.Unmarshal([]byte(body), &respData) + assert.Nil(t, err, "error unmarshalling response") + 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") + assert.NotEqual(t, 0, len(respData.Data.PremiumProducts), "PremiumProducts should not be empty") +} + +func TestInternalGetLatestStateHandler(t *testing.T) { + code, body := ts.get(t, "/api/i/latest-state") + assert.Equal(t, http.StatusOK, code) + + respData := api_types.InternalGetLatestStateResponse{} + err := json.Unmarshal([]byte(body), &respData) + assert.Nil(t, err, "error unmarshalling response") + 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) { + 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") + }) + 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") + }) + + 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") + }) + + 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") + // 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") + }) + 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") + }) +} + +func TestInternalSearchHandler(t *testing.T) { + // search for validator with index 5 + code, body := ts.post(t, "/api/i/search", bytes.NewBufferString(` + { + "input":"5", + "networks":[ + 17000 + ], + "types":[ + "validators_by_deposit_ens_name", + "validators_by_deposit_address", + "validators_by_withdrawal_ens_name", + "validators_by_withdrawal_address", + "validators_by_withdrawal_credential", + "validator_by_index", + "validator_by_public_key", + "validators_by_graffiti" + ] + }`)) + assert.Equal(t, http.StatusOK, code) + + 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(` + { + "input":"0x9699af2bad9826694a480cb523cbe545dc41db955356b3b0d4871f1cf3e4924ae4132fa8c374a0505ae2076d3d65b3e0", + "networks":[ + 17000 + ], + "types":[ + "validators_by_deposit_ens_name", + "validators_by_deposit_address", + "validators_by_withdrawal_ens_name", + "validators_by_withdrawal_address", + "validators_by_withdrawal_credential", + "validator_by_index", + "validator_by_public_key", + "validators_by_graffiti" + ] + }`)) + assert.Equal(t, http.StatusOK, code) + + 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(` + { + "input":"0x0e5dda855eb1de2a212cd1f62b2a3ee49d20c444", + "networks":[ + 17000 + ], + "types":[ + "validators_by_deposit_ens_name", + "validators_by_deposit_address", + "validators_by_withdrawal_ens_name", + "validators_by_withdrawal_address", + "validators_by_withdrawal_credential", + "validator_by_index", + "validator_by_public_key", + "validators_by_graffiti" + ] + }`)) + assert.Equal(t, http.StatusOK, code) + + 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") + + for _, slot := range epoch.Slots { + if slot.Attestations != nil { // count the amount of attestation assignments for each epoch, should be exactly 1 + attestationAssignments++ + } + } + + 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") +} diff --git a/backend/pkg/api/data_access/archiver.go b/backend/pkg/api/data_access/archiver.go new file mode 100644 index 000000000..09fd605a3 --- /dev/null +++ b/backend/pkg/api/data_access/archiver.go @@ -0,0 +1,107 @@ +package dataaccess + +import ( + "context" + "database/sql" + "fmt" + + "github.com/doug-martin/goqu/v9" + t "github.com/gobitfly/beaconchain/pkg/api/types" +) + +type ArchiverRepository interface { + GetValidatorDashboardsCountInfo(ctx context.Context) (map[uint64][]t.ArchiverDashboard, error) + UpdateValidatorDashboardsArchiving(ctx context.Context, dashboards []t.ArchiverDashboardArchiveReason) error + RemoveValidatorDashboards(ctx context.Context, dashboardIds []uint64) error +} + +func (d *DataAccessService) GetValidatorDashboardsCountInfo(ctx context.Context) (map[uint64][]t.ArchiverDashboard, error) { + result := make(map[uint64][]t.ArchiverDashboard) + + type DashboardInfo struct { + Id uint64 `db:"id"` + UserId uint64 `db:"user_id"` + IsArchived sql.NullString `db:"is_archived"` + GroupCount uint64 `db:"group_count"` + ValidatorCount uint64 `db:"validator_count"` + } + + var dbReturn []DashboardInfo + err := d.readerDb.Select(&dbReturn, ` + WITH dashboards_groups AS + (SELECT + dashboard_id, + COUNT(id) AS group_count + FROM users_val_dashboards_groups + GROUP BY dashboard_id), + dashboards_validators AS + (SELECT + dashboard_id, + COUNT(validator_index) AS validator_count + FROM users_val_dashboards_validators + GROUP BY dashboard_id) + SELECT + uvd.id, + uvd.user_id, + uvd.is_archived, + COALESCE(dg.group_count, 0) AS group_count, + COALESCE(dv.validator_count, 0) AS validator_count + FROM users_val_dashboards uvd + LEFT JOIN dashboards_groups dg ON uvd.id = dg.dashboard_id + LEFT JOIN dashboards_validators dv ON uvd.id = dv.dashboard_id + `) + if err != nil { + return nil, err + } + + for _, dashboardInfo := range dbReturn { + if _, ok := result[dashboardInfo.UserId]; !ok { + result[dashboardInfo.UserId] = make([]t.ArchiverDashboard, 0) + } + + dashboard := t.ArchiverDashboard{ + DashboardId: dashboardInfo.Id, + IsArchived: dashboardInfo.IsArchived.Valid, + GroupCount: dashboardInfo.GroupCount, + ValidatorCount: dashboardInfo.ValidatorCount, + } + + result[dashboardInfo.UserId] = append(result[dashboardInfo.UserId], dashboard) + } + + return result, nil +} + +func (d *DataAccessService) UpdateValidatorDashboardsArchiving(ctx context.Context, dashboards []t.ArchiverDashboardArchiveReason) error { + ds := goqu.Dialect("postgres").Update("users_val_dashboards") + + cases := goqu.Case() + for _, dashboard := range dashboards { + cases = cases.When(goqu.I("id").Eq(dashboard.DashboardId), dashboard.ArchivedReason.ToString()) + } + + ds = ds.Set(goqu.Record{"is_archived": cases}) + + // Restrict the query to the ids we want to set + ids := make([]interface{}, len(dashboards)) + for i, dashboard := range dashboards { + ids[i] = dashboard.DashboardId + } + ds = ds.Where(goqu.I("id").In(ids...)) + + query, args, err := ds.Prepared(true).ToSQL() + if err != nil { + return fmt.Errorf("error preparing query: %v", err) + } + + _, err = d.writerDb.ExecContext(ctx, query, args...) + return err +} + +func (d *DataAccessService) RemoveValidatorDashboards(ctx context.Context, dashboardIds []uint64) error { + // Delete the dashboard + _, err := d.writerDb.ExecContext(ctx, ` + DELETE FROM users_val_dashboards WHERE id = ANY($1) + `, dashboardIds) + return err +} diff --git a/backend/pkg/api/data_access/data_access.go b/backend/pkg/api/data_access/data_access.go index aa8adead6..c4b3b8338 100644 --- a/backend/pkg/api/data_access/data_access.go +++ b/backend/pkg/api/data_access/data_access.go @@ -27,9 +27,11 @@ type DataAccessor interface { NotificationsRepository AdminRepository BlockRepository + ArchiverRepository ProtocolRepository HealthzRepository + StartDataAccessServices() Close() GetLatestFinalizedEpoch() (uint64, error) @@ -80,12 +82,6 @@ func NewDataAccessService(cfg *types.Config) *DataAccessService { db.BigtableClient = das.bigtable db.PersistentRedisDbClient = das.persistentRedisDbClient - // Create the services - das.services = services.NewServices(das.readerDb, das.writerDb, das.alloyReader, das.alloyWriter, das.clickhouseReader, das.bigtable, das.persistentRedisDbClient) - - // Initialize the services - das.services.InitServices() - return das } @@ -249,6 +245,14 @@ func createDataAccessService(cfg *types.Config) *DataAccessService { return &dataAccessService } +func (d *DataAccessService) StartDataAccessServices() { + // Create the services + d.services = services.NewServices(d.readerDb, d.writerDb, d.alloyReader, d.alloyWriter, d.clickhouseReader, d.bigtable, d.persistentRedisDbClient) + + // Initialize the services + d.services.InitServices() +} + func (d *DataAccessService) Close() { if d.readerDb != nil { d.readerDb.Close() diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index ea5b3f05d..6197a72b2 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -56,50 +56,63 @@ func commonFakeData(a interface{}) error { return faker.FakeData(a, options.WithRandomMapAndSliceMaxSize(5)) } +func (d *DummyService) StartDataAccessServices() { + // nothing to start +} + +// used for any non-pointer data, e.g. all primitive types or slices +func getDummyData[T any]() (T, error) { + var r T + err := commonFakeData(&r) + return r, err +} + +// used for any struct data that should be returned as a pointer +func getDummyStruct[T any]() (*T, error) { + var r T + err := commonFakeData(&r) + return &r, err +} + +// used for any table data that should be returned with paging +func getDummyWithPaging[T any]() ([]T, *t.Paging, error) { + r := []T{} + p := t.Paging{} + _ = commonFakeData(&r) + err := commonFakeData(&p) + return r, &p, err +} + func (d *DummyService) Close() { // nothing to close } func (d *DummyService) GetLatestSlot() (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetLatestFinalizedEpoch() (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetLatestBlock() (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetBlockHeightAt(slot uint64) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetLatestExchangeRates() ([]t.EthConversionRate, error) { - r := []t.EthConversionRate{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.EthConversionRate]() } func (d *DummyService) GetUserByEmail(ctx context.Context, email string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) CreateUser(ctx context.Context, email, password string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) RemoveUser(ctx context.Context, userId uint64) error { @@ -115,15 +128,11 @@ func (d *DummyService) UpdateUserPassword(ctx context.Context, userId uint64, pa } func (d *DummyService) GetEmailConfirmationTime(ctx context.Context, userId uint64) (time.Time, error) { - r := time.Time{} - err := commonFakeData(&r) - return r, err + return getDummyData[time.Time]() } func (d *DummyService) GetPasswordResetTime(ctx context.Context, userId uint64) (time.Time, error) { - r := time.Time{} - err := commonFakeData(&r) - return r, err + return getDummyData[time.Time]() } func (d *DummyService) UpdateEmailConfirmationTime(ctx context.Context, userId uint64) error { @@ -147,124 +156,94 @@ func (d *DummyService) UpdatePasswordResetHash(ctx context.Context, userId uint6 } func (d *DummyService) GetUserInfo(ctx context.Context, userId uint64) (*t.UserInfo, error) { - r := t.UserInfo{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.UserInfo]() } func (d *DummyService) GetUserCredentialInfo(ctx context.Context, userId uint64) (*t.UserCredentialInfo, error) { - r := t.UserCredentialInfo{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.UserCredentialInfo]() } func (d *DummyService) GetUserIdByApiKey(ctx context.Context, apiKey string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetUserIdByConfirmationHash(ctx context.Context, hash string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetUserIdByResetHash(ctx context.Context, hash string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetProductSummary(ctx context.Context) (*t.ProductSummary, error) { - r := t.ProductSummary{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.ProductSummary]() } func (d *DummyService) GetFreeTierPerks(ctx context.Context) (*t.PremiumPerks, error) { - r := t.PremiumPerks{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.PremiumPerks]() } func (d *DummyService) GetValidatorDashboardUser(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.DashboardUser, error) { - r := t.DashboardUser{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.DashboardUser]() } func (d *DummyService) GetValidatorDashboardIdByPublicId(ctx context.Context, publicDashboardId t.VDBIdPublic) (*t.VDBIdPrimary, error) { - var r t.VDBIdPrimary - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBIdPrimary]() } func (d *DummyService) GetValidatorDashboardInfo(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.ValidatorDashboard, error) { - r := t.ValidatorDashboard{} + r, err := getDummyStruct[t.ValidatorDashboard]() // return semi-valid data to not break staging - //nolint:errcheck - commonFakeData(&r) r.IsArchived = false - return &r, nil + return r, err } func (d *DummyService) GetValidatorDashboardName(ctx context.Context, dashboardId t.VDBIdPrimary) (string, error) { - r := "" - err := commonFakeData(&r) - return r, err + return getDummyData[string]() } func (d *DummyService) GetValidatorsFromSlices(indices []uint64, publicKeys []string) ([]t.VDBValidator, error) { - r := []t.VDBValidator{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.VDBValidator]() } func (d *DummyService) GetUserDashboards(ctx context.Context, userId uint64) (*t.UserDashboardsData, error) { - r := t.UserDashboardsData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.UserDashboardsData]() } func (d *DummyService) CreateValidatorDashboard(ctx context.Context, userId uint64, name string, network uint64) (*t.VDBPostReturnData, error) { - r := t.VDBPostReturnData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPostReturnData]() } func (d *DummyService) GetValidatorDashboardOverview(ctx context.Context, dashboardId t.VDBId, protocolModes t.VDBProtocolModes) (*t.VDBOverviewData, error) { - r := t.VDBOverviewData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBOverviewData]() } func (d *DummyService) RemoveValidatorDashboard(ctx context.Context, dashboardId t.VDBIdPrimary) error { return nil } -func (d *DummyService) UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archived bool) (*t.VDBPostArchivingReturnData, error) { - r := t.VDBPostArchivingReturnData{} - err := commonFakeData(&r) - return &r, err +func (d *DummyService) RemoveValidatorDashboards(ctx context.Context, dashboardIds []uint64) error { + return nil +} + +func (d *DummyService) UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archivedReason *enums.VDBArchivedReason) (*t.VDBPostArchivingReturnData, error) { + return getDummyStruct[t.VDBPostArchivingReturnData]() +} + +func (d *DummyService) UpdateValidatorDashboardsArchiving(ctx context.Context, dashboards []t.ArchiverDashboardArchiveReason) error { + return nil } func (d *DummyService) UpdateValidatorDashboardName(ctx context.Context, dashboardId t.VDBIdPrimary, name string) (*t.VDBPostReturnData, error) { - r := t.VDBPostReturnData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPostReturnData]() } func (d *DummyService) CreateValidatorDashboardGroup(ctx context.Context, dashboardId t.VDBIdPrimary, name string) (*t.VDBPostCreateGroupData, error) { - r := t.VDBPostCreateGroupData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPostCreateGroupData]() } func (d *DummyService) UpdateValidatorDashboardGroup(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, name string) (*t.VDBPostCreateGroupData, error) { - r := t.VDBPostCreateGroupData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPostCreateGroupData]() } func (d *DummyService) RemoveValidatorDashboardGroup(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64) error { @@ -276,41 +255,27 @@ func (d *DummyService) GetValidatorDashboardGroupExists(ctx context.Context, das } func (d *DummyService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) { - r := []t.VDBPostValidatorsData{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.VDBPostValidatorsData]() } func (d *DummyService) AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) { - r := []t.VDBPostValidatorsData{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.VDBPostValidatorsData]() } func (d *DummyService) AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) { - r := []t.VDBPostValidatorsData{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.VDBPostValidatorsData]() } func (d *DummyService) AddValidatorDashboardValidatorsByGraffiti(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, graffiti string, limit uint64) ([]t.VDBPostValidatorsData, error) { - r := []t.VDBPostValidatorsData{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.VDBPostValidatorsData]() } func (d *DummyService) GetValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBId, groupId int64, cursor string, colSort t.Sort[enums.VDBManageValidatorsColumn], search string, limit uint64) ([]t.VDBManageValidatorsTableRow, *t.Paging, error) { - r := []t.VDBManageValidatorsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBManageValidatorsTableRow]() } func (d *DummyService) RemoveValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) error { @@ -318,21 +283,15 @@ func (d *DummyService) RemoveValidatorDashboardValidators(ctx context.Context, d } func (d *DummyService) CreateValidatorDashboardPublicId(ctx context.Context, dashboardId t.VDBIdPrimary, name string, shareGroups bool) (*t.VDBPublicId, error) { - r := t.VDBPublicId{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPublicId]() } func (d *DummyService) GetValidatorDashboardPublicId(ctx context.Context, publicDashboardId t.VDBIdPublic) (*t.VDBPublicId, error) { - r := t.VDBPublicId{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPublicId]() } func (d *DummyService) UpdateValidatorDashboardPublicId(ctx context.Context, publicDashboardId t.VDBIdPublic, name string, shareGroups bool) (*t.VDBPublicId, error) { - r := t.VDBPublicId{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBPublicId]() } func (d *DummyService) RemoveValidatorDashboardPublicId(ctx context.Context, publicDashboardId t.VDBIdPublic) error { @@ -348,293 +307,175 @@ func (d *DummyService) GetValidatorDashboardSlotViz(ctx context.Context, dashboa } func (d *DummyService) GetValidatorDashboardSummary(ctx context.Context, dashboardId t.VDBId, period enums.TimePeriod, cursor string, colSort t.Sort[enums.VDBSummaryColumn], search string, limit uint64, protocolModes t.VDBProtocolModes) ([]t.VDBSummaryTableRow, *t.Paging, error) { - r := []t.VDBSummaryTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBSummaryTableRow]() } func (d *DummyService) GetValidatorDashboardGroupSummary(ctx context.Context, dashboardId t.VDBId, groupId int64, period enums.TimePeriod, protocolModes t.VDBProtocolModes) (*t.VDBGroupSummaryData, error) { - r := t.VDBGroupSummaryData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBGroupSummaryData]() } func (d *DummyService) GetValidatorDashboardSummaryChart(ctx context.Context, dashboardId t.VDBId, groupIds []int64, efficiency enums.VDBSummaryChartEfficiencyType, aggregation enums.ChartAggregation, afterTs uint64, beforeTs uint64) (*t.ChartData[int, float64], error) { - r := t.ChartData[int, float64]{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.ChartData[int, float64]]() } func (d *DummyService) GetValidatorDashboardSummaryValidators(ctx context.Context, dashboardId t.VDBId, groupId int64) (*t.VDBGeneralSummaryValidators, error) { - r := t.VDBGeneralSummaryValidators{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBGeneralSummaryValidators]() } func (d *DummyService) GetValidatorDashboardSyncSummaryValidators(ctx context.Context, dashboardId t.VDBId, groupId int64, period enums.TimePeriod) (*t.VDBSyncSummaryValidators, error) { - r := t.VDBSyncSummaryValidators{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBSyncSummaryValidators]() } func (d *DummyService) GetValidatorDashboardSlashingsSummaryValidators(ctx context.Context, dashboardId t.VDBId, groupId int64, period enums.TimePeriod) (*t.VDBSlashingsSummaryValidators, error) { - r := t.VDBSlashingsSummaryValidators{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBSlashingsSummaryValidators]() } func (d *DummyService) GetValidatorDashboardProposalSummaryValidators(ctx context.Context, dashboardId t.VDBId, groupId int64, period enums.TimePeriod) (*t.VDBProposalSummaryValidators, error) { - r := t.VDBProposalSummaryValidators{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBProposalSummaryValidators]() } func (d *DummyService) GetValidatorDashboardRewards(ctx context.Context, dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBRewardsColumn], search string, limit uint64, protocolModes t.VDBProtocolModes) ([]t.VDBRewardsTableRow, *t.Paging, error) { - r := []t.VDBRewardsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBRewardsTableRow]() } func (d *DummyService) GetValidatorDashboardGroupRewards(ctx context.Context, dashboardId t.VDBId, groupId int64, epoch uint64, protocolModes t.VDBProtocolModes) (*t.VDBGroupRewardsData, error) { - r := t.VDBGroupRewardsData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBGroupRewardsData]() } func (d *DummyService) GetValidatorDashboardRewardsChart(ctx context.Context, dashboardId t.VDBId, protocolModes t.VDBProtocolModes) (*t.ChartData[int, decimal.Decimal], error) { - r := t.ChartData[int, decimal.Decimal]{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.ChartData[int, decimal.Decimal]]() } func (d *DummyService) GetValidatorDashboardDuties(ctx context.Context, dashboardId t.VDBId, epoch uint64, groupId int64, cursor string, colSort t.Sort[enums.VDBDutiesColumn], search string, limit uint64, protocolModes t.VDBProtocolModes) ([]t.VDBEpochDutiesTableRow, *t.Paging, error) { - r := []t.VDBEpochDutiesTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBEpochDutiesTableRow]() } func (d *DummyService) GetValidatorDashboardBlocks(ctx context.Context, dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBBlocksColumn], search string, limit uint64, protocolModes t.VDBProtocolModes) ([]t.VDBBlocksTableRow, *t.Paging, error) { - r := []t.VDBBlocksTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBBlocksTableRow]() } func (d *DummyService) GetValidatorDashboardHeatmap(ctx context.Context, dashboardId t.VDBId, protocolModes t.VDBProtocolModes, aggregation enums.ChartAggregation, afterTs uint64, beforeTs uint64) (*t.VDBHeatmap, error) { - r := t.VDBHeatmap{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBHeatmap]() } func (d *DummyService) GetValidatorDashboardGroupHeatmap(ctx context.Context, dashboardId t.VDBId, groupId uint64, protocolModes t.VDBProtocolModes, aggregation enums.ChartAggregation, timestamp uint64) (*t.VDBHeatmapTooltipData, error) { - r := t.VDBHeatmapTooltipData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBHeatmapTooltipData]() } -func (d *DummyService) GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) { - r := []t.VDBExecutionDepositsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err +func (d *DummyService) GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) { + return getDummyWithPaging[t.VDBExecutionDepositsTableRow]() } -func (d *DummyService) GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) { - r := []t.VDBConsensusDepositsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err +func (d *DummyService) GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) { + return getDummyWithPaging[t.VDBConsensusDepositsTableRow]() } func (d *DummyService) GetValidatorDashboardTotalElDeposits(ctx context.Context, dashboardId t.VDBId) (*t.VDBTotalExecutionDepositsData, error) { - r := t.VDBTotalExecutionDepositsData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBTotalExecutionDepositsData]() } func (d *DummyService) GetValidatorDashboardTotalClDeposits(ctx context.Context, dashboardId t.VDBId) (*t.VDBTotalConsensusDepositsData, error) { - r := t.VDBTotalConsensusDepositsData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBTotalConsensusDepositsData]() } func (d *DummyService) GetValidatorDashboardWithdrawals(ctx context.Context, dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBWithdrawalsColumn], search string, limit uint64, protocolModes t.VDBProtocolModes) ([]t.VDBWithdrawalsTableRow, *t.Paging, error) { - r := []t.VDBWithdrawalsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return []t.VDBWithdrawalsTableRow{}, &t.Paging{}, nil } func (d *DummyService) GetValidatorDashboardTotalWithdrawals(ctx context.Context, dashboardId t.VDBId, search string, protocolModes t.VDBProtocolModes) (*t.VDBTotalWithdrawalsData, error) { - r := t.VDBTotalWithdrawalsData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBTotalWithdrawalsData]() } func (d *DummyService) GetValidatorDashboardRocketPool(ctx context.Context, dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBRocketPoolColumn], search string, limit uint64) ([]t.VDBRocketPoolTableRow, *t.Paging, error) { - r := []t.VDBRocketPoolTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBRocketPoolTableRow]() } func (d *DummyService) GetValidatorDashboardTotalRocketPool(ctx context.Context, dashboardId t.VDBId, search string) (*t.VDBRocketPoolTableRow, error) { - r := t.VDBRocketPoolTableRow{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBRocketPoolTableRow]() } func (d *DummyService) GetValidatorDashboardNodeRocketPool(ctx context.Context, dashboardId t.VDBId, node string) (*t.VDBNodeRocketPoolData, error) { - r := t.VDBNodeRocketPoolData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.VDBNodeRocketPoolData]() } func (d *DummyService) GetValidatorDashboardRocketPoolMinipools(ctx context.Context, dashboardId t.VDBId, node string, cursor string, colSort t.Sort[enums.VDBRocketPoolMinipoolsColumn], search string, limit uint64) ([]t.VDBRocketPoolMinipoolsTableRow, *t.Paging, error) { - r := []t.VDBRocketPoolMinipoolsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.VDBRocketPoolMinipoolsTableRow]() } func (d *DummyService) GetAllNetworks() ([]t.NetworkInfo, error) { - r := []t.NetworkInfo{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.NetworkInfo]() } func (d *DummyService) GetSearchValidatorByIndex(ctx context.Context, chainId, index uint64) (*t.SearchValidator, error) { - r := t.SearchValidator{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidator]() } func (d *DummyService) GetSearchValidatorByPublicKey(ctx context.Context, chainId uint64, publicKey []byte) (*t.SearchValidator, error) { - r := t.SearchValidator{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidator]() } func (d *DummyService) GetSearchValidatorsByDepositAddress(ctx context.Context, chainId uint64, address []byte) (*t.SearchValidatorsByDepositAddress, error) { - r := t.SearchValidatorsByDepositAddress{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidatorsByDepositAddress]() } func (d *DummyService) GetSearchValidatorsByDepositEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByDepositEnsName, error) { - r := t.SearchValidatorsByDepositEnsName{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidatorsByDepositEnsName]() } func (d *DummyService) GetSearchValidatorsByWithdrawalCredential(ctx context.Context, chainId uint64, credential []byte) (*t.SearchValidatorsByWithdrwalCredential, error) { - r := t.SearchValidatorsByWithdrwalCredential{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidatorsByWithdrwalCredential]() } func (d *DummyService) GetSearchValidatorsByWithdrawalEnsName(ctx context.Context, chainId uint64, ensName string) (*t.SearchValidatorsByWithrawalEnsName, error) { - r := t.SearchValidatorsByWithrawalEnsName{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidatorsByWithrawalEnsName]() } func (d *DummyService) GetSearchValidatorsByGraffiti(ctx context.Context, chainId uint64, graffiti string) (*t.SearchValidatorsByGraffiti, error) { - r := t.SearchValidatorsByGraffiti{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.SearchValidatorsByGraffiti]() } func (d *DummyService) GetUserValidatorDashboardCount(ctx context.Context, userId uint64, active bool) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetValidatorDashboardGroupCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetValidatorDashboardValidatorsCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetValidatorDashboardPublicIdCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetNotificationOverview(ctx context.Context, userId uint64) (*t.NotificationOverviewData, error) { - r := t.NotificationOverviewData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.NotificationOverviewData]() } func (d *DummyService) GetDashboardNotifications(ctx context.Context, userId uint64, chainId uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) { - r := []t.NotificationDashboardsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.NotificationDashboardsTableRow]() } func (d *DummyService) GetValidatorDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationValidatorDashboardDetail, error) { - r := t.NotificationValidatorDashboardDetail{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.NotificationValidatorDashboardDetail]() } func (d *DummyService) GetAccountDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationAccountDashboardDetail, error) { - r := t.NotificationAccountDashboardDetail{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.NotificationAccountDashboardDetail]() } func (d *DummyService) GetMachineNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationMachinesColumn], search string, limit uint64) ([]t.NotificationMachinesTableRow, *t.Paging, error) { - r := []t.NotificationMachinesTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.NotificationMachinesTableRow]() } func (d *DummyService) GetClientNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationClientsColumn], search string, limit uint64) ([]t.NotificationClientsTableRow, *t.Paging, error) { - r := []t.NotificationClientsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.NotificationClientsTableRow]() } func (d *DummyService) GetRocketPoolNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationRocketPoolColumn], search string, limit uint64) ([]t.NotificationRocketPoolTableRow, *t.Paging, error) { - r := []t.NotificationRocketPoolTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.NotificationRocketPoolTableRow]() } func (d *DummyService) GetNetworkNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationNetworksColumn], search string, limit uint64) ([]t.NotificationNetworksTableRow, *t.Paging, error) { - r := []t.NotificationNetworksTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) - return r, &p, err + return getDummyWithPaging[t.NotificationNetworksTableRow]() } func (d *DummyService) GetNotificationSettings(ctx context.Context, userId uint64) (*t.NotificationSettings, error) { - r := t.NotificationSettings{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.NotificationSettings]() } func (d *DummyService) UpdateNotificationSettingsGeneral(ctx context.Context, userId uint64, settings t.NotificationSettingsGeneral) error { return nil @@ -649,10 +490,7 @@ func (d *DummyService) DeleteNotificationSettingsPairedDevice(ctx context.Contex return nil } func (d *DummyService) GetNotificationSettingsDashboards(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationSettingsDashboardColumn], search string, limit uint64) ([]t.NotificationSettingsDashboardsTableRow, *t.Paging, error) { - r := []t.NotificationSettingsDashboardsTableRow{} - p := t.Paging{} - _ = commonFakeData(&r) - err := commonFakeData(&p) + r, p, err := getDummyWithPaging[t.NotificationSettingsDashboardsTableRow]() for i, n := range r { var settings interface{} if n.IsAccountDashboard { @@ -663,7 +501,7 @@ func (d *DummyService) GetNotificationSettingsDashboards(ctx context.Context, us _ = commonFakeData(&settings) r[i].Settings = settings } - return r, &p, err + return r, p, err } func (d *DummyService) UpdateNotificationSettingsValidatorDashboard(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, settings t.NotificationSettingsValidatorDashboard) error { return nil @@ -676,9 +514,7 @@ func (d *DummyService) CreateAdConfiguration(ctx context.Context, key, jquerySel } func (d *DummyService) GetAdConfigurations(ctx context.Context, keys []string) ([]t.AdConfigurationData, error) { - r := []t.AdConfigurationData{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.AdConfigurationData]() } func (d *DummyService) UpdateAdConfiguration(ctx context.Context, key, jquerySelector string, insertMode enums.AdInsertMode, refreshInterval uint64, forAllUsers bool, bannerId uint64, htmlContent string, enabled bool) error { @@ -690,15 +526,11 @@ func (d *DummyService) RemoveAdConfiguration(ctx context.Context, key string) er } func (d *DummyService) GetLatestExportedChartTs(ctx context.Context, aggregation enums.ChartAggregation) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) GetUserIdByRefreshToken(claimUserID, claimAppID, claimDeviceID uint64, hashedRefreshToken string) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) MigrateMobileSession(oldHashedRefreshToken, newHashedRefreshToken, deviceID, deviceName string) error { @@ -706,9 +538,7 @@ func (d *DummyService) MigrateMobileSession(oldHashedRefreshToken, newHashedRefr } func (d *DummyService) GetAppDataFromRedirectUri(callback string) (*t.OAuthAppData, error) { - r := t.OAuthAppData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.OAuthAppData]() } func (d *DummyService) AddUserDevice(userID uint64, hashedRefreshToken string, deviceID, deviceName string, appID uint64) error { @@ -720,9 +550,7 @@ func (d *DummyService) AddMobileNotificationToken(userID uint64, deviceID, notif } func (d *DummyService) GetAppSubscriptionCount(userID uint64) (uint64, error) { - r := uint64(0) - err := commonFakeData(&r) - return r, err + return getDummyData[uint64]() } func (d *DummyService) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails t.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error { @@ -730,121 +558,86 @@ func (d *DummyService) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetai } func (d *DummyService) GetBlockOverview(ctx context.Context, chainId, block uint64) (*t.BlockOverview, error) { - r := t.BlockOverview{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.BlockOverview]() } func (d *DummyService) GetBlockTransactions(ctx context.Context, chainId, block uint64) ([]t.BlockTransactionTableRow, error) { - r := []t.BlockTransactionTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockTransactionTableRow]() } func (d *DummyService) GetBlock(ctx context.Context, chainId, block uint64) (*t.BlockSummary, error) { - r := t.BlockSummary{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.BlockSummary]() } func (d *DummyService) GetBlockVotes(ctx context.Context, chainId, block uint64) ([]t.BlockVoteTableRow, error) { - r := []t.BlockVoteTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockVoteTableRow]() } func (d *DummyService) GetBlockAttestations(ctx context.Context, chainId, block uint64) ([]t.BlockAttestationTableRow, error) { - r := []t.BlockAttestationTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockAttestationTableRow]() } func (d *DummyService) GetBlockWithdrawals(ctx context.Context, chainId, block uint64) ([]t.BlockWithdrawalTableRow, error) { - r := []t.BlockWithdrawalTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockWithdrawalTableRow]() } func (d *DummyService) GetBlockBlsChanges(ctx context.Context, chainId, block uint64) ([]t.BlockBlsChangeTableRow, error) { - r := []t.BlockBlsChangeTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockBlsChangeTableRow]() } func (d *DummyService) GetBlockVoluntaryExits(ctx context.Context, chainId, block uint64) ([]t.BlockVoluntaryExitTableRow, error) { - r := []t.BlockVoluntaryExitTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockVoluntaryExitTableRow]() } func (d *DummyService) GetBlockBlobs(ctx context.Context, chainId, block uint64) ([]t.BlockBlobTableRow, error) { - r := []t.BlockBlobTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockBlobTableRow]() } func (d *DummyService) GetSlot(ctx context.Context, chainId, block uint64) (*t.BlockSummary, error) { - r := t.BlockSummary{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.BlockSummary]() } func (d *DummyService) GetSlotOverview(ctx context.Context, chainId, block uint64) (*t.BlockOverview, error) { - r := t.BlockOverview{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.BlockOverview]() } func (d *DummyService) GetSlotTransactions(ctx context.Context, chainId, block uint64) ([]t.BlockTransactionTableRow, error) { - r := []t.BlockTransactionTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockTransactionTableRow]() } func (d *DummyService) GetSlotVotes(ctx context.Context, chainId, block uint64) ([]t.BlockVoteTableRow, error) { - r := []t.BlockVoteTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockVoteTableRow]() } func (d *DummyService) GetSlotAttestations(ctx context.Context, chainId, block uint64) ([]t.BlockAttestationTableRow, error) { - r := []t.BlockAttestationTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockAttestationTableRow]() } func (d *DummyService) GetSlotWithdrawals(ctx context.Context, chainId, block uint64) ([]t.BlockWithdrawalTableRow, error) { - r := []t.BlockWithdrawalTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockWithdrawalTableRow]() } func (d *DummyService) GetSlotBlsChanges(ctx context.Context, chainId, block uint64) ([]t.BlockBlsChangeTableRow, error) { - r := []t.BlockBlsChangeTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockBlsChangeTableRow]() } func (d *DummyService) GetSlotVoluntaryExits(ctx context.Context, chainId, block uint64) ([]t.BlockVoluntaryExitTableRow, error) { - r := []t.BlockVoluntaryExitTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockVoluntaryExitTableRow]() } func (d *DummyService) GetSlotBlobs(ctx context.Context, chainId, block uint64) ([]t.BlockBlobTableRow, error) { - r := []t.BlockBlobTableRow{} - err := commonFakeData(&r) - return r, err + return getDummyData[[]t.BlockBlobTableRow]() +} + +func (d *DummyService) GetValidatorDashboardsCountInfo(ctx context.Context) (map[uint64][]t.ArchiverDashboard, error) { + return getDummyData[map[uint64][]t.ArchiverDashboard]() } func (d *DummyService) GetRocketPoolOverview(ctx context.Context) (*t.RocketPoolData, error) { - r := t.RocketPoolData{} - err := commonFakeData(&r) - return &r, err + return getDummyStruct[t.RocketPoolData]() } func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) types.HealthzData { - r := types.HealthzData{} - _ = commonFakeData(&r) + r, _ := getDummyData[types.HealthzData]() return r } diff --git a/backend/pkg/api/data_access/user.go b/backend/pkg/api/data_access/user.go index 907bd24d2..8f566faac 100644 --- a/backend/pkg/api/data_access/user.go +++ b/backend/pkg/api/data_access/user.go @@ -441,7 +441,7 @@ var freeTierProduct t.PremiumProduct = t.PremiumProduct{ ProductName: "Free", PremiumPerks: t.PremiumPerks{ AdFree: false, - ValidatorDasboards: 1, + ValidatorDashboards: 1, ValidatorsPerDashboard: 20, ValidatorGroupsPerDashboard: 1, ShareCustomDashboards: false, @@ -546,7 +546,7 @@ func (d *DataAccessService) GetProductSummary(ctx context.Context) (*t.ProductSu ProductName: "Guppy", PremiumPerks: t.PremiumPerks{ AdFree: true, - ValidatorDasboards: 1, + ValidatorDashboards: 1, ValidatorsPerDashboard: 100, ValidatorGroupsPerDashboard: 3, ShareCustomDashboards: true, @@ -579,7 +579,7 @@ func (d *DataAccessService) GetProductSummary(ctx context.Context) (*t.ProductSu ProductName: "Dolphin", PremiumPerks: t.PremiumPerks{ AdFree: true, - ValidatorDasboards: 2, + ValidatorDashboards: 2, ValidatorsPerDashboard: 300, ValidatorGroupsPerDashboard: 10, ShareCustomDashboards: true, @@ -612,7 +612,7 @@ func (d *DataAccessService) GetProductSummary(ctx context.Context) (*t.ProductSu ProductName: "Orca", PremiumPerks: t.PremiumPerks{ AdFree: true, - ValidatorDasboards: 2, + ValidatorDashboards: 2, ValidatorsPerDashboard: 1000, ValidatorGroupsPerDashboard: 30, ShareCustomDashboards: true, diff --git a/backend/pkg/api/data_access/vdb.go b/backend/pkg/api/data_access/vdb.go index d922127be..1d38e5537 100644 --- a/backend/pkg/api/data_access/vdb.go +++ b/backend/pkg/api/data_access/vdb.go @@ -16,7 +16,7 @@ type ValidatorDashboardRepository interface { CreateValidatorDashboard(ctx context.Context, userId uint64, name string, network uint64) (*t.VDBPostReturnData, error) RemoveValidatorDashboard(ctx context.Context, dashboardId t.VDBIdPrimary) error - UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archived bool) (*t.VDBPostArchivingReturnData, error) + UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archivedReason *enums.VDBArchivedReason) (*t.VDBPostArchivingReturnData, error) UpdateValidatorDashboardName(ctx context.Context, dashboardId t.VDBIdPrimary, name string) (*t.VDBPostReturnData, error) @@ -67,8 +67,8 @@ type ValidatorDashboardRepository interface { GetValidatorDashboardHeatmap(ctx context.Context, dashboardId t.VDBId, protocolModes t.VDBProtocolModes, aggregation enums.ChartAggregation, afterTs uint64, beforeTs uint64) (*t.VDBHeatmap, error) GetValidatorDashboardGroupHeatmap(ctx context.Context, dashboardId t.VDBId, groupId uint64, protocolModes t.VDBProtocolModes, aggregation enums.ChartAggregation, timestamp uint64) (*t.VDBHeatmapTooltipData, error) - GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) - GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) + GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) + GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) GetValidatorDashboardTotalElDeposits(ctx context.Context, dashboardId t.VDBId) (*t.VDBTotalExecutionDepositsData, error) GetValidatorDashboardTotalClDeposits(ctx context.Context, dashboardId t.VDBId) (*t.VDBTotalConsensusDepositsData, error) diff --git a/backend/pkg/api/data_access/vdb_deposits.go b/backend/pkg/api/data_access/vdb_deposits.go index c8a4d5b14..7d32cc002 100644 --- a/backend/pkg/api/data_access/vdb_deposits.go +++ b/backend/pkg/api/data_access/vdb_deposits.go @@ -19,7 +19,7 @@ import ( "github.com/shopspring/decimal" ) -func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) { +func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBExecutionDepositsTableRow, *t.Paging, error) { var err error currentDirection := enums.DESC // TODO: expose over parameter var currentCursor t.ELDepositsCursor @@ -177,7 +177,7 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context, return responseData, p, nil } -func (d *DataAccessService) GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, search string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) { +func (d *DataAccessService) GetValidatorDashboardClDeposits(ctx context.Context, dashboardId t.VDBId, cursor string, limit uint64) ([]t.VDBConsensusDepositsTableRow, *t.Paging, error) { var err error currentDirection := enums.DESC // TODO: expose over parameter var currentCursor t.CLDepositsCursor diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go index f783f6e6f..3d4aa939d 100644 --- a/backend/pkg/api/data_access/vdb_management.go +++ b/backend/pkg/api/data_access/vdb_management.go @@ -229,64 +229,25 @@ func (d *DataAccessService) CreateValidatorDashboard(ctx context.Context, userId } func (d *DataAccessService) RemoveValidatorDashboard(ctx context.Context, dashboardId t.VDBIdPrimary) error { - tx, err := d.alloyWriter.BeginTxx(ctx, nil) - if err != nil { - return fmt.Errorf("error starting db transactions to remove a validator dashboard: %w", err) - } - defer utils.Rollback(tx) - - // Delete the dashboard - _, err = tx.ExecContext(ctx, ` + _, err := d.alloyWriter.ExecContext(ctx, ` DELETE FROM users_val_dashboards WHERE id = $1 `, dashboardId) - if err != nil { - return err - } - - // Delete all groups for the dashboard - _, err = tx.ExecContext(ctx, ` - DELETE FROM users_val_dashboards_groups WHERE dashboard_id = $1 - `, dashboardId) - if err != nil { - return err - } - - // Delete all validators for the dashboard - _, err = tx.ExecContext(ctx, ` - DELETE FROM users_val_dashboards_validators WHERE dashboard_id = $1 - `, dashboardId) - if err != nil { - return err - } - - // Delete all shared dashboards for the dashboard - _, err = tx.ExecContext(ctx, ` - DELETE FROM users_val_dashboards_sharing WHERE dashboard_id = $1 - `, dashboardId) - if err != nil { - return err - } - - err = tx.Commit() - if err != nil { - return fmt.Errorf("error committing tx to remove a validator dashboard: %w", err) - } - return nil + return err } -func (d *DataAccessService) UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archived bool) (*t.VDBPostArchivingReturnData, error) { +func (d *DataAccessService) UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archivedReason *enums.VDBArchivedReason) (*t.VDBPostArchivingReturnData, error) { result := &t.VDBPostArchivingReturnData{} - var archivedReason *string - if archived { - reason := enums.VDBArchivedReasons.User.ToString() - archivedReason = &reason + var archivedReasonText *string + if archivedReason != nil { + reason := archivedReason.ToString() + archivedReasonText = &reason } err := d.alloyWriter.GetContext(ctx, result, ` UPDATE users_val_dashboards SET is_archived = $1 WHERE id = $2 RETURNING id, is_archived IS NOT NULL AS is_archived - `, archivedReason, dashboardId) + `, archivedReasonText, dashboardId) if err != nil { return nil, err } @@ -333,9 +294,11 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d if err := d.alloyReader.SelectContext(ctx, &queryResult, query, dashboardId.Id); err != nil { return err } + for _, res := range queryResult { data.Groups = append(data.Groups, t.VDBOverviewGroup{Id: uint64(res.Id), Name: res.Name, Count: res.Count}) } + return nil }) } @@ -352,6 +315,10 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d return fmt.Errorf("error retrieving validators from dashboard id: %v", err) } + if dashboardId.Validators != nil || dashboardId.AggregateGroups { + data.Groups = append(data.Groups, t.VDBOverviewGroup{Id: t.DefaultGroupId, Name: t.DefaultGroupName, Count: uint64(len(validators))}) + } + // Status pubKeyList := make([][]byte, 0, len(validators)) for _, validator := range validators { @@ -582,33 +549,11 @@ func (d *DataAccessService) UpdateValidatorDashboardGroup(ctx context.Context, d } func (d *DataAccessService) RemoveValidatorDashboardGroup(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64) error { - tx, err := d.alloyWriter.BeginTxx(ctx, nil) - if err != nil { - return fmt.Errorf("error starting db transactions to remove a validator dashboard group: %w", err) - } - defer utils.Rollback(tx) - // Delete the group - _, err = tx.ExecContext(ctx, ` + _, err := d.alloyWriter.ExecContext(ctx, ` DELETE FROM users_val_dashboards_groups WHERE dashboard_id = $1 AND id = $2 `, dashboardId, groupId) - if err != nil { - return err - } - - // Delete all validators for the group - _, err = tx.ExecContext(ctx, ` - DELETE FROM users_val_dashboards_validators WHERE dashboard_id = $1 AND group_id = $2 - `, dashboardId, groupId) - if err != nil { - return err - } - - err = tx.Commit() - if err != nil { - return fmt.Errorf("error committing tx to remove a validator dashboard group: %w", err) - } - return nil + return err } func (d *DataAccessService) GetValidatorDashboardGroupCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error) { diff --git a/backend/pkg/api/data_access/vdb_slotviz.go b/backend/pkg/api/data_access/vdb_slotviz.go index 0e77f7034..98d8c60d9 100644 --- a/backend/pkg/api/data_access/vdb_slotviz.go +++ b/backend/pkg/api/data_access/vdb_slotviz.go @@ -19,7 +19,6 @@ func (d *DataAccessService) GetValidatorDashboardSlotViz(ctx context.Context, da // 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 - latestProposedSlot := cache.LatestProposedSlot.Get() slotsPerEpoch := utils.Config.Chain.ClConfig.SlotsPerEpoch minEpoch := uint64(0) @@ -205,7 +204,7 @@ func (d *DataAccessService) GetValidatorDashboardSlotViz(ctx context.Context, da } attestationsRef := slotVizEpochs[epochIdx].Slots[slotIdx].Attestations - if uint64(slot) >= latestProposedSlot { + if uint64(slot) >= dutiesInfo.LatestProposedSlot { if attestationsRef.Scheduled == nil { attestationsRef.Scheduled = &t.VDBSlotVizDuty{} } diff --git a/backend/pkg/api/handlers/auth.go b/backend/pkg/api/handlers/auth.go index 1bd6e3bd7..62e55e23c 100644 --- a/backend/pkg/api/handlers/auth.go +++ b/backend/pkg/api/handlers/auth.go @@ -443,6 +443,9 @@ func (h *HandlerService) InternalPostLogin(w http.ResponseWriter, r *http.Reques // fetch user userId, err := h.dai.GetUserByEmail(r.Context(), email) if err != nil { + if errors.Is(err, dataaccess.ErrNotFound) { + err = errBadCredentials + } handleErr(w, r, err) return } diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go index b80d317eb..54f25b5c9 100644 --- a/backend/pkg/api/handlers/common.go +++ b/backend/pkg/api/handlers/common.go @@ -81,7 +81,7 @@ const ( gnosis = "gnosis" allowEmpty = true forbidEmpty = false - maxArchivedDashboardsCount = 10 + MaxArchivedDashboardsCount = 10 ) var ( @@ -592,7 +592,7 @@ func (v *validationError) checkProtocolModes(protocolModes string) types.VDBProt func (v *validationError) checkValidatorList(validators string, allowEmpty bool) ([]types.VDBValidator, []string) { if validators == "" && !allowEmpty { - v.add("validators", "list of validators is must not be empty") + v.add("validators", "list of validators must not be empty") return nil, nil } validatorsSlice := splitParameters(validators, ',') diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index e5583477c..1c4157290 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -2,10 +2,8 @@ package handlers import ( "errors" - "fmt" "math" "net/http" - "reflect" "github.com/gobitfly/beaconchain/pkg/api/enums" types "github.com/gobitfly/beaconchain/pkg/api/types" @@ -254,20 +252,7 @@ func (h *HandlerService) InternalGetUserInfo(w http.ResponseWriter, r *http.Requ // Dashboards func (h *HandlerService) InternalGetUserDashboards(w http.ResponseWriter, r *http.Request) { - userId, err := GetUserIdByContext(r) - if err != nil { - handleErr(w, r, err) - return - } - data, err := h.dai.GetUserDashboards(r.Context(), userId) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiDataResponse[types.UserDashboardsData]{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetUserDashboards(w, r) } // -------------------------------------- @@ -333,1274 +318,147 @@ func (h *HandlerService) InternalPutAccountDashboardTransactionsSettings(w http. // Validator Dashboards func (h *HandlerService) InternalPostValidatorDashboards(w http.ResponseWriter, r *http.Request) { - var v validationError - userId, err := GetUserIdByContext(r) - if err != nil { - handleErr(w, r, err) - return - } - req := struct { - Name string `json:"name"` - Network intOrString `json:"network"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkNameNotEmpty(req.Name) - chainId := v.checkNetwork(req.Network) - if v.hasErrors() { - handleErr(w, r, v) - return - } + h.PublicPostValidatorDashboards(w, r) +} - userInfo, err := h.dai.GetUserInfo(r.Context(), userId) - if err != nil { - handleErr(w, r, err) - return - } - dashboardCount, err := h.dai.GetUserValidatorDashboardCount(r.Context(), userId, true) - if err != nil { - handleErr(w, r, err) - return - } - if dashboardCount >= userInfo.PremiumPerks.ValidatorDasboards && !isUserAdmin(userInfo) { - returnConflict(w, r, errors.New("maximum number of validator dashboards reached")) - return - } +func (h *HandlerService) InternalGetValidatorDashboard(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboard(w, r) +} - data, err := h.dai.CreateValidatorDashboard(r.Context(), userId, name, chainId) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiDataResponse[types.VDBPostReturnData]{ - Data: *data, - } - returnCreated(w, r, response) +func (h *HandlerService) InternalDeleteValidatorDashboard(w http.ResponseWriter, r *http.Request) { + h.PublicDeleteValidatorDashboard(w, r) } -func (h *HandlerService) InternalGetValidatorDashboard(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardIdParam := mux.Vars(r)["dashboard_id"] - dashboardId, err := h.handleDashboardId(r.Context(), dashboardIdParam) - if err != nil { - handleErr(w, r, err) - return - } +func (h *HandlerService) InternalPutValidatorDashboardName(w http.ResponseWriter, r *http.Request) { + h.PublicPutValidatorDashboardName(w, r) +} - q := r.URL.Query() - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } +func (h *HandlerService) InternalPostValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { + h.PublicPostValidatorDashboardGroups(w, r) +} - // set name depending on dashboard id - var name string - if reInteger.MatchString(dashboardIdParam) { - name, err = h.dai.GetValidatorDashboardName(r.Context(), dashboardId.Id) - } else if reValidatorDashboardPublicId.MatchString(dashboardIdParam) { - var publicIdInfo *types.VDBPublicId - publicIdInfo, err = h.dai.GetValidatorDashboardPublicId(r.Context(), types.VDBIdPublic(dashboardIdParam)) - name = publicIdInfo.Name - } - if err != nil { - handleErr(w, r, err) - return - } +func (h *HandlerService) InternalPutValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { + h.PublicPutValidatorDashboardGroups(w, r) +} - // add premium chart perk info for shared dashboards - premiumPerks, err := h.getDashboardPremiumPerks(r.Context(), *dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - data, err := h.dai.GetValidatorDashboardOverview(r.Context(), *dashboardId, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - data.ChartHistorySeconds = premiumPerks.ChartHistorySeconds - data.Name = name +func (h *HandlerService) InternalDeleteValidatorDashboardGroup(w http.ResponseWriter, r *http.Request) { + h.PublicDeleteValidatorDashboardGroup(w, r) +} - response := types.InternalGetValidatorDashboardResponse{ - Data: *data, - } +func (h *HandlerService) InternalPostValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { + h.PublicPostValidatorDashboardValidators(w, r) +} - returnOk(w, r, response) +func (h *HandlerService) InternalGetValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardValidators(w, r) } -func (h *HandlerService) InternalDeleteValidatorDashboard(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - if v.hasErrors() { - handleErr(w, r, v) - return - } - err := h.dai.RemoveValidatorDashboard(r.Context(), dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - returnNoContent(w, r) +func (h *HandlerService) InternalDeleteValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { + h.PublicDeleteValidatorDashboardValidators(w, r) +} + +func (h *HandlerService) InternalPostValidatorDashboardPublicIds(w http.ResponseWriter, r *http.Request) { + h.PublicPostValidatorDashboardPublicIds(w, r) +} + +func (h *HandlerService) InternalPutValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { + h.PublicPutValidatorDashboardPublicId(w, r) +} + +func (h *HandlerService) InternalDeleteValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { + h.PublicDeleteValidatorDashboardPublicId(w, r) } func (h *HandlerService) InternalPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - IsArchived bool `json:"is_archived"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - if v.hasErrors() { - handleErr(w, r, v) - return - } + h.PublicPutValidatorDashboardArchiving(w, r) +} - // check conditions for changing archival status - dashboardInfo, err := h.dai.GetValidatorDashboardInfo(r.Context(), dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - if dashboardInfo.IsArchived == req.IsArchived { - // nothing to do - returnOk(w, r, types.ApiDataResponse[types.VDBPostArchivingReturnData]{ - Data: types.VDBPostArchivingReturnData{Id: uint64(dashboardId), IsArchived: req.IsArchived}, - }) - return - } +func (h *HandlerService) InternalGetValidatorDashboardSlotViz(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardSlotViz(w, r) +} - userId, err := GetUserIdByContext(r) - if err != nil { - handleErr(w, r, err) - return - } - dashboardCount, err := h.dai.GetUserValidatorDashboardCount(r.Context(), userId, !req.IsArchived) - if err != nil { - handleErr(w, r, err) - return - } +func (h *HandlerService) InternalGetValidatorDashboardSummary(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardSummary(w, r) +} - userInfo, err := h.dai.GetUserInfo(r.Context(), userId) - if err != nil { - handleErr(w, r, err) - return - } - if !isUserAdmin(userInfo) { - if req.IsArchived { - if dashboardCount >= maxArchivedDashboardsCount { - returnConflict(w, r, errors.New("maximum number of archived validator dashboards reached")) - return - } - } else { - if dashboardCount >= userInfo.PremiumPerks.ValidatorDasboards { - returnConflict(w, r, errors.New("maximum number of active validator dashboards reached")) - return - } - if dashboardInfo.GroupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard { - returnConflict(w, r, errors.New("maximum number of groups in dashboards reached")) - return - } - if dashboardInfo.ValidatorCount >= userInfo.PremiumPerks.ValidatorsPerDashboard { - returnConflict(w, r, errors.New("maximum number of validators in dashboards reached")) - return - } - } - } +func (h *HandlerService) InternalGetValidatorDashboardGroupSummary(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardGroupSummary(w, r) +} - data, err := h.dai.UpdateValidatorDashboardArchiving(r.Context(), dashboardId, req.IsArchived) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiDataResponse[types.VDBPostArchivingReturnData]{ - Data: *data, - } - returnOk(w, r, response) +func (h *HandlerService) InternalGetValidatorDashboardSummaryChart(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardSummaryChart(w, r) } -func (h *HandlerService) InternalPutValidatorDashboardName(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - Name string `json:"name"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkNameNotEmpty(req.Name) - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, err := h.dai.UpdateValidatorDashboardName(r.Context(), dashboardId, name) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiDataResponse[types.VDBPostReturnData]{ - Data: *data, - } - returnOk(w, r, response) +func (h *HandlerService) InternalGetValidatorDashboardSummaryValidators(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardSummaryValidators(w, r) } -func (h *HandlerService) InternalPostValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - Name string `json:"name"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkNameNotEmpty(req.Name) - if v.hasErrors() { - handleErr(w, r, v) - return - } - ctx := r.Context() - // check if user has reached the maximum number of groups - userId, err := GetUserIdByContext(r) - if err != nil { - handleErr(w, r, err) - return - } - userInfo, err := h.dai.GetUserInfo(ctx, userId) - if err != nil { - handleErr(w, r, err) - return - } - groupCount, err := h.dai.GetValidatorDashboardGroupCount(ctx, dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - if groupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard && !isUserAdmin(userInfo) { - returnConflict(w, r, errors.New("maximum number of validator dashboard groups reached")) - return - } +func (h *HandlerService) InternalGetValidatorDashboardRewards(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardRewards(w, r) +} - data, err := h.dai.CreateValidatorDashboardGroup(ctx, dashboardId, name) - if err != nil { - handleErr(w, r, err) - return - } +func (h *HandlerService) InternalGetValidatorDashboardGroupRewards(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardGroupRewards(w, r) +} - response := types.ApiResponse{ - Data: data, - } +func (h *HandlerService) InternalGetValidatorDashboardRewardsChart(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardRewardsChart(w, r) +} - returnCreated(w, r, response) +func (h *HandlerService) InternalGetValidatorDashboardDuties(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardDuties(w, r) } -func (h *HandlerService) InternalPutValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) - groupId := v.checkExistingGroupId(vars["group_id"]) - req := struct { - Name string `json:"name"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkNameNotEmpty(req.Name) - if v.hasErrors() { - handleErr(w, r, v) - return - } - groupExists, err := h.dai.GetValidatorDashboardGroupExists(r.Context(), dashboardId, groupId) - if err != nil { - handleErr(w, r, err) - return - } - if !groupExists { - returnNotFound(w, r, errors.New("group not found")) - return - } - data, err := h.dai.UpdateValidatorDashboardGroup(r.Context(), dashboardId, groupId, name) - if err != nil { - handleErr(w, r, err) - return - } +func (h *HandlerService) InternalGetValidatorDashboardBlocks(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardBlocks(w, r) +} - response := types.ApiResponse{ - Data: data, - } +func (h *HandlerService) InternalGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardHeatmap(w, r) +} - returnOk(w, r, response) -} - -func (h *HandlerService) InternalDeleteValidatorDashboardGroup(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - groupId := v.checkExistingGroupId(vars["group_id"]) - if v.hasErrors() { - handleErr(w, r, v) - return - } - if groupId == types.DefaultGroupId { - returnBadRequest(w, r, errors.New("cannot delete default group")) - return - } - groupExists, err := h.dai.GetValidatorDashboardGroupExists(r.Context(), dashboardId, groupId) - if err != nil { - handleErr(w, r, err) - return - } - if !groupExists { - returnNotFound(w, r, errors.New("group not found")) - return - } - err = h.dai.RemoveValidatorDashboardGroup(r.Context(), dashboardId, groupId) - if err != nil { - handleErr(w, r, err) - return - } - - returnNoContent(w, r) -} - -func (h *HandlerService) InternalPostValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - GroupId uint64 `json:"group_id,omitempty"` - Validators []intOrString `json:"validators,omitempty"` - DepositAddress string `json:"deposit_address,omitempty"` - WithdrawalAddress string `json:"withdrawal_address,omitempty"` - Graffiti string `json:"graffiti,omitempty"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - if v.hasErrors() { - handleErr(w, r, v) - return - } - // check if exactly one of validators, deposit_address, withdrawal_address, graffiti is set - fields := []interface{}{req.Validators, req.DepositAddress, req.WithdrawalAddress, req.Graffiti} - var count int - for _, set := range fields { - if !reflect.ValueOf(set).IsZero() { - count++ - } - } - if count != 1 { - v.add("request body", "exactly one of `validators`, `deposit_address`, `withdrawal_address`, `graffiti` must be set. please check the API documentation for more information") - } - if v.hasErrors() { - handleErr(w, r, v) - return - } - - groupId := req.GroupId - ctx := r.Context() - groupExists, err := h.dai.GetValidatorDashboardGroupExists(ctx, dashboardId, groupId) - if err != nil { - handleErr(w, r, err) - return - } - if !groupExists { - returnNotFound(w, r, errors.New("group not found")) - return - } - userId, err := GetUserIdByContext(r) - if err != nil { - handleErr(w, r, err) - return - } - userInfo, err := h.dai.GetUserInfo(ctx, userId) - if err != nil { - handleErr(w, r, err) - return - } - limit := userInfo.PremiumPerks.ValidatorsPerDashboard - if req.Validators == nil && !userInfo.PremiumPerks.BulkAdding && !isUserAdmin(userInfo) { - returnConflict(w, r, errors.New("bulk adding not allowed with current subscription plan")) - return - } - var data []types.VDBPostValidatorsData - var dataErr error - switch { - case req.Validators != nil: - indices, pubkeys := v.checkValidators(req.Validators, forbidEmpty) - if v.hasErrors() { - handleErr(w, r, v) - return - } - validators, err := h.dai.GetValidatorsFromSlices(indices, pubkeys) - if err != nil { - handleErr(w, r, err) - return - } - // check if adding more validators than allowed - existingValidatorCount, err := h.dai.GetValidatorDashboardExistingValidatorCount(ctx, dashboardId, validators) - if err != nil { - handleErr(w, r, err) - return - } - if uint64(len(validators)) > existingValidatorCount+limit { - returnConflict(w, r, fmt.Errorf("adding more validators than allowed, limit is %v new validators", limit)) - return - } - data, dataErr = h.dai.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validators) - - case req.DepositAddress != "": - depositAddress := v.checkRegex(reEthereumAddress, req.DepositAddress, "deposit_address") - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, dataErr = h.dai.AddValidatorDashboardValidatorsByDepositAddress(ctx, dashboardId, groupId, depositAddress, limit) - - case req.WithdrawalAddress != "": - withdrawalAddress := v.checkRegex(reWithdrawalCredential, req.WithdrawalAddress, "withdrawal_address") - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, dataErr = h.dai.AddValidatorDashboardValidatorsByWithdrawalAddress(ctx, dashboardId, groupId, withdrawalAddress, limit) - - case req.Graffiti != "": - graffiti := v.checkRegex(reNonEmpty, req.Graffiti, "graffiti") - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, dataErr = h.dai.AddValidatorDashboardValidatorsByGraffiti(ctx, dashboardId, groupId, graffiti, limit) - } - - if dataErr != nil { - handleErr(w, r, dataErr) - return - } - response := types.ApiResponse{ - Data: data, - } - - returnCreated(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - groupId := v.checkGroupId(q.Get("group_id"), allowEmpty) - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBManageValidatorsColumn](&v, q.Get("sort")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, paging, err := h.dai.GetValidatorDashboardValidators(r.Context(), *dashboardId, groupId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardValidatorsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalDeleteValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - var indices []uint64 - var publicKeys []string - if validatorsParam := r.URL.Query().Get("validators"); validatorsParam != "" { - indices, publicKeys = v.checkValidatorList(validatorsParam, allowEmpty) - if v.hasErrors() { - handleErr(w, r, v) - return - } - } - validators, err := h.dai.GetValidatorsFromSlices(indices, publicKeys) - if err != nil { - handleErr(w, r, err) - return - } - err = h.dai.RemoveValidatorDashboardValidators(r.Context(), dashboardId, validators) - if err != nil { - handleErr(w, r, err) - return - } - - returnNoContent(w, r) -} - -func (h *HandlerService) InternalPostValidatorDashboardPublicIds(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - Name string `json:"name,omitempty"` - ShareSettings struct { - ShareGroups bool `json:"share_groups"` - } `json:"share_settings"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkName(req.Name, 0) - if v.hasErrors() { - handleErr(w, r, v) - return - } - publicIdCount, err := h.dai.GetValidatorDashboardPublicIdCount(r.Context(), dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - if publicIdCount >= 1 { - returnConflict(w, r, errors.New("cannot create more than one public id")) - return - } - - data, err := h.dai.CreateValidatorDashboardPublicId(r.Context(), dashboardId, name, req.ShareSettings.ShareGroups) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiResponse{ - Data: data, - } - - returnCreated(w, r, response) -} - -func (h *HandlerService) InternalPutValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - Name string `json:"name,omitempty"` - ShareSettings struct { - ShareGroups bool `json:"share_groups"` - } `json:"share_settings"` - }{} - if err := v.checkBody(&req, r); err != nil { - handleErr(w, r, err) - return - } - name := v.checkName(req.Name, 0) - publicDashboardId := v.checkValidatorDashboardPublicId(vars["public_id"]) - if v.hasErrors() { - handleErr(w, r, v) - return - } - fetchedId, err := h.dai.GetValidatorDashboardIdByPublicId(r.Context(), publicDashboardId) - if err != nil { - handleErr(w, r, err) - return - } - if *fetchedId != dashboardId { - handleErr(w, r, newNotFoundErr("public id %v not found", publicDashboardId)) - return - } - - data, err := h.dai.UpdateValidatorDashboardPublicId(r.Context(), publicDashboardId, name, req.ShareSettings.ShareGroups) - if err != nil { - handleErr(w, r, err) - return - } - response := types.ApiResponse{ - Data: data, - } - - returnOk(w, r, response) -} - -func (h *HandlerService) InternalDeleteValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) - publicDashboardId := v.checkValidatorDashboardPublicId(vars["public_id"]) - if v.hasErrors() { - handleErr(w, r, v) - return - } - fetchedId, err := h.dai.GetValidatorDashboardIdByPublicId(r.Context(), publicDashboardId) - if err != nil { - handleErr(w, r, err) - return - } - if *fetchedId != dashboardId { - handleErr(w, r, newNotFoundErr("public id %v not found", publicDashboardId)) - return - } - - err = h.dai.RemoveValidatorDashboardPublicId(r.Context(), publicDashboardId) - if err != nil { - handleErr(w, r, err) - return - } - - returnNoContent(w, r) -} - -func (h *HandlerService) InternalGetValidatorDashboardSlotViz(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - - groupIds := v.checkExistingGroupIdList(r.URL.Query().Get("group_ids")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - data, err := h.dai.GetValidatorDashboardSlotViz(r.Context(), *dashboardId, groupIds) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardSlotVizResponse{ - Data: data, - } - - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardSummary(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBSummaryColumn](&v, q.Get("sort")) - protocolModes := v.checkProtocolModes(q.Get("modes")) - - period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") - // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h - allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} - v.checkEnumIsAllowed(period, allowedPeriods, "period") - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardSummary(r.Context(), *dashboardId, period, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardSummaryResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardGroupSummary(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - q := r.URL.Query() - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - if err != nil { - handleErr(w, r, err) - return - } - groupId := v.checkGroupId(vars["group_id"], forbidEmpty) - period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period") - // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h - allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} - v.checkEnumIsAllowed(period, allowedPeriods, "period") - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardGroupSummary(r.Context(), *dashboardId, groupId, period, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardGroupSummaryResponse{ - Data: *data, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardSummaryChart(w http.ResponseWriter, r *http.Request) { - var v validationError - ctx := r.Context() - dashboardId, err := h.handleDashboardId(ctx, mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - groupIds := v.checkGroupIdList(q.Get("group_ids")) - efficiencyType := checkEnum[enums.VDBSummaryChartEfficiencyType](&v, q.Get("efficiency_type"), "efficiency_type") - - aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") - chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(ctx, dashboardId, aggregation) - if err != nil { - handleErr(w, r, err) - return - } - afterTs, beforeTs := v.checkTimestamps(r, chartLimits) - if v.hasErrors() { - handleErr(w, r, v) - return - } - if afterTs < chartLimits.MinAllowedTs || beforeTs < chartLimits.MinAllowedTs { - returnConflict(w, r, fmt.Errorf("requested time range is too old, minimum timestamp for dashboard owner's premium subscription for this aggregation is %v", chartLimits.MinAllowedTs)) - return - } - - data, err := h.dai.GetValidatorDashboardSummaryChart(ctx, *dashboardId, groupIds, efficiencyType, aggregation, afterTs, beforeTs) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardSummaryChartResponse{ - Data: *data, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardSummaryValidators(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - groupId := v.checkGroupId(r.URL.Query().Get("group_id"), allowEmpty) - q := r.URL.Query() - duty := checkEnum[enums.ValidatorDuty](&v, q.Get("duty"), "duty") - period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") - // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h - allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} - v.checkEnumIsAllowed(period, allowedPeriods, "period") - if v.hasErrors() { - handleErr(w, r, v) - return - } - - // get indices based on duty - var indices interface{} - duties := enums.ValidatorDuties - switch duty { - case duties.None: - indices, err = h.dai.GetValidatorDashboardSummaryValidators(r.Context(), *dashboardId, groupId) - case duties.Sync: - indices, err = h.dai.GetValidatorDashboardSyncSummaryValidators(r.Context(), *dashboardId, groupId, period) - case duties.Slashed: - indices, err = h.dai.GetValidatorDashboardSlashingsSummaryValidators(r.Context(), *dashboardId, groupId, period) - case duties.Proposal: - indices, err = h.dai.GetValidatorDashboardProposalSummaryValidators(r.Context(), *dashboardId, groupId, period) - } - if err != nil { - handleErr(w, r, err) - return - } - // map indices to response format - data, err := mapVDBIndices(indices) - if err != nil { - handleErr(w, r, err) - return - } - - response := types.InternalGetValidatorDashboardSummaryValidatorsResponse{ - Data: data, - } - - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardRewards(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBRewardsColumn](&v, q.Get("sort")) - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardRewards(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardRewardsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardGroupRewards(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - groupId := v.checkGroupId(vars["group_id"], forbidEmpty) - epoch := v.checkUint(vars["epoch"], "epoch") - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardGroupRewards(r.Context(), *dashboardId, groupId, epoch, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardGroupRewardsResponse{ - Data: *data, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardRewardsChart(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardRewardsChart(r.Context(), *dashboardId, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardRewardsChartResponse{ - Data: *data, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardDuties(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - groupId := v.checkGroupId(q.Get("group_id"), allowEmpty) - epoch := v.checkUint(vars["epoch"], "epoch") - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBDutiesColumn](&v, q.Get("sort")) - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardDuties(r.Context(), *dashboardId, epoch, groupId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardDutiesResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardBlocks(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBBlocksColumn](&v, q.Get("sort")) - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardBlocks(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardBlocksResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - q := r.URL.Query() - protocolModes := v.checkProtocolModes(q.Get("modes")) - aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") - chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(r.Context(), dashboardId, aggregation) - if err != nil { - handleErr(w, r, err) - return - } - afterTs, beforeTs := v.checkTimestamps(r, chartLimits) - if v.hasErrors() { - handleErr(w, r, v) - return - } - if afterTs < chartLimits.MinAllowedTs || beforeTs < chartLimits.MinAllowedTs { - returnConflict(w, r, fmt.Errorf("requested time range is too old, minimum timestamp for dashboard owner's premium subscription for this aggregation is %v", chartLimits.MinAllowedTs)) - return - } - - data, err := h.dai.GetValidatorDashboardHeatmap(r.Context(), *dashboardId, protocolModes, aggregation, afterTs, beforeTs) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardHeatmapResponse{ - Data: *data, - } - returnOk(w, r, response) -} - -func (h *HandlerService) InternalGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - groupId := v.checkExistingGroupId(vars["group_id"]) - requestedTimestamp := v.checkUint(vars["timestamp"], "timestamp") - protocolModes := v.checkProtocolModes(r.URL.Query().Get("modes")) - aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") - if v.hasErrors() { - handleErr(w, r, v) - return - } - chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(r.Context(), dashboardId, aggregation) - if err != nil { - handleErr(w, r, err) - return - } - if requestedTimestamp < chartLimits.MinAllowedTs || requestedTimestamp > chartLimits.LatestExportedTs { - handleErr(w, r, newConflictErr("requested timestamp is outside of allowed chart history for dashboard owner's premium subscription")) - return - } - - data, err := h.dai.GetValidatorDashboardGroupHeatmap(r.Context(), *dashboardId, groupId, protocolModes, aggregation, requestedTimestamp) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardGroupHeatmapResponse{ - Data: *data, - } - returnOk(w, r, response) +func (h *HandlerService) InternalGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) { + h.PublicGetValidatorDashboardGroupHeatmap(w, r) } func (h *HandlerService) InternalGetValidatorDashboardExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(r.URL.Query()) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardElDeposits(r.Context(), *dashboardId, pagingParams.cursor, pagingParams.search, pagingParams.limit) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardExecutionLayerDepositsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardExecutionLayerDeposits(w, r) } func (h *HandlerService) InternalGetValidatorDashboardConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) { - var v validationError - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(r.URL.Query()) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardClDeposits(r.Context(), *dashboardId, pagingParams.cursor, pagingParams.search, pagingParams.limit) - if err != nil { - handleErr(w, r, err) - return - } - - response := types.InternalGetValidatorDashboardConsensusLayerDepositsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardConsensusLayerDeposits(w, r) } func (h *HandlerService) InternalGetValidatorDashboardTotalConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) { - var err error - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - data, err := h.dai.GetValidatorDashboardTotalClDeposits(r.Context(), *dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - - response := types.InternalGetValidatorDashboardTotalConsensusDepositsResponse{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardTotalConsensusLayerDeposits(w, r) } func (h *HandlerService) InternalGetValidatorDashboardTotalExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) { - var err error - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - data, err := h.dai.GetValidatorDashboardTotalElDeposits(r.Context(), *dashboardId) - if err != nil { - handleErr(w, r, err) - return - } - - response := types.InternalGetValidatorDashboardTotalExecutionDepositsResponse{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardTotalExecutionLayerDeposits(w, r) } func (h *HandlerService) InternalGetValidatorDashboardWithdrawals(w http.ResponseWriter, r *http.Request) { - var v validationError - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBWithdrawalsColumn](&v, q.Get("sort")) - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardWithdrawals(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardWithdrawalsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardWithdrawals(w, r) } func (h *HandlerService) InternalGetValidatorDashboardTotalWithdrawals(w http.ResponseWriter, r *http.Request) { - var v validationError - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(q) - protocolModes := v.checkProtocolModes(q.Get("modes")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardTotalWithdrawals(r.Context(), *dashboardId, pagingParams.search, protocolModes) - if err != nil { - handleErr(w, r, err) - return - } - - response := types.InternalGetValidatorDashboardTotalWithdrawalsResponse{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardTotalWithdrawals(w, r) } func (h *HandlerService) InternalGetValidatorDashboardRocketPool(w http.ResponseWriter, r *http.Request) { - var v validationError - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBRocketPoolColumn](&v, q.Get("sort")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardRocketPool(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardRocketPoolResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardRocketPool(w, r) } func (h *HandlerService) InternalGetValidatorDashboardTotalRocketPool(w http.ResponseWriter, r *http.Request) { - var v validationError - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - pagingParams := v.checkPagingParams(q) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardTotalRocketPool(r.Context(), *dashboardId, pagingParams.search) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardTotalRocketPoolResponse{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardTotalRocketPool(w, r) } func (h *HandlerService) InternalGetValidatorDashboardNodeRocketPool(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - // support ENS names ? - nodeAddress := v.checkAddress(vars["node_address"]) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, err := h.dai.GetValidatorDashboardNodeRocketPool(r.Context(), *dashboardId, nodeAddress) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardNodeRocketPoolResponse{ - Data: *data, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardNodeRocketPool(w, r) } func (h *HandlerService) InternalGetValidatorDashboardRocketPoolMinipools(w http.ResponseWriter, r *http.Request) { - var v validationError - vars := mux.Vars(r) - q := r.URL.Query() - dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) - if err != nil { - handleErr(w, r, err) - return - } - // support ENS names ? - nodeAddress := v.checkAddress(vars["node_address"]) - pagingParams := v.checkPagingParams(q) - sort := checkSort[enums.VDBRocketPoolMinipoolsColumn](&v, q.Get("sort")) - if v.hasErrors() { - handleErr(w, r, v) - return - } - - data, paging, err := h.dai.GetValidatorDashboardRocketPoolMinipools(r.Context(), *dashboardId, nodeAddress, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) - if err != nil { - handleErr(w, r, err) - return - } - response := types.InternalGetValidatorDashboardRocketPoolMinipoolsResponse{ - Data: data, - Paging: *paging, - } - returnOk(w, r, response) + h.PublicGetValidatorDashboardRocketPoolMinipools(w, r) } // -------------------------------------- diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go index 2251677a4..3e80c2769 100644 --- a/backend/pkg/api/handlers/public.go +++ b/backend/pkg/api/handlers/public.go @@ -8,6 +8,7 @@ import ( "reflect" "time" + "github.com/gobitfly/beaconchain/pkg/api/enums" "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gorilla/mux" ) @@ -112,43 +113,281 @@ func (h *HandlerService) PublicPutAccountDashboardTransactionsSettings(w http.Re } func (h *HandlerService) PublicPostValidatorDashboards(w http.ResponseWriter, r *http.Request) { - returnCreated(w, r, nil) + var v validationError + userId, err := GetUserIdByContext(r) + if err != nil { + handleErr(w, r, err) + return + } + + type request struct { + Name string `json:"name"` + Network intOrString `json:"network" swaggertype:"string" enums:"ethereum,gnosis"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkNameNotEmpty(req.Name) + chainId := v.checkNetwork(req.Network) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + userInfo, err := h.dai.GetUserInfo(r.Context(), userId) + if err != nil { + handleErr(w, r, err) + return + } + dashboardCount, err := h.dai.GetUserValidatorDashboardCount(r.Context(), userId, true) + if err != nil { + handleErr(w, r, err) + return + } + if dashboardCount >= userInfo.PremiumPerks.ValidatorDashboards && !isUserAdmin(userInfo) { + returnConflict(w, r, errors.New("maximum number of validator dashboards reached")) + return + } + + data, err := h.dai.CreateValidatorDashboard(r.Context(), userId, name, chainId) + if err != nil { + handleErr(w, r, err) + return + } + response := types.ApiDataResponse[types.VDBPostReturnData]{ + Data: *data, + } + returnCreated(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboard(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardIdParam := mux.Vars(r)["dashboard_id"] + dashboardId, err := h.handleDashboardId(r.Context(), dashboardIdParam) + if err != nil { + handleErr(w, r, err) + return + } + + q := r.URL.Query() + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + // set name depending on dashboard id + var name string + if reInteger.MatchString(dashboardIdParam) { + name, err = h.dai.GetValidatorDashboardName(r.Context(), dashboardId.Id) + } else if reValidatorDashboardPublicId.MatchString(dashboardIdParam) { + var publicIdInfo *types.VDBPublicId + publicIdInfo, err = h.dai.GetValidatorDashboardPublicId(r.Context(), types.VDBIdPublic(dashboardIdParam)) + name = publicIdInfo.Name + } + if err != nil { + handleErr(w, r, err) + return + } + + // add premium chart perk info for shared dashboards + premiumPerks, err := h.getDashboardPremiumPerks(r.Context(), *dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + data, err := h.dai.GetValidatorDashboardOverview(r.Context(), *dashboardId, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + data.ChartHistorySeconds = premiumPerks.ChartHistorySeconds + data.Name = name + + response := types.GetValidatorDashboardResponse{ + Data: *data, + } + + returnOk(w, r, response) } func (h *HandlerService) PublicDeleteValidatorDashboard(w http.ResponseWriter, r *http.Request) { + var v validationError + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + if v.hasErrors() { + handleErr(w, r, v) + return + } + err := h.dai.RemoveValidatorDashboard(r.Context(), dashboardId) + if err != nil { + handleErr(w, r, err) + return + } returnNoContent(w, r) } -func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) { - returnNoContent(w, r) +func (h *HandlerService) PublicPutValidatorDashboardName(w http.ResponseWriter, r *http.Request) { + var v validationError + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + type request struct { + Name string `json:"name"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkNameNotEmpty(req.Name) + if v.hasErrors() { + handleErr(w, r, v) + return + } + data, err := h.dai.UpdateValidatorDashboardName(r.Context(), dashboardId, name) + if err != nil { + handleErr(w, r, err) + return + } + response := types.ApiDataResponse[types.VDBPostReturnData]{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicPostValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { - returnCreated(w, r, nil) + var v validationError + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + type request struct { + Name string `json:"name"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkNameNotEmpty(req.Name) + if v.hasErrors() { + handleErr(w, r, v) + return + } + ctx := r.Context() + // check if user has reached the maximum number of groups + userId, err := GetUserIdByContext(r) + if err != nil { + handleErr(w, r, err) + return + } + userInfo, err := h.dai.GetUserInfo(ctx, userId) + if err != nil { + handleErr(w, r, err) + return + } + groupCount, err := h.dai.GetValidatorDashboardGroupCount(ctx, dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + if groupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard && !isUserAdmin(userInfo) { + returnConflict(w, r, errors.New("maximum number of validator dashboard groups reached")) + return + } + + data, err := h.dai.CreateValidatorDashboardGroup(ctx, dashboardId, name) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.ApiDataResponse[types.VDBPostCreateGroupData]{ + Data: *data, + } + + returnCreated(w, r, response) } func (h *HandlerService) PublicPutValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) { - returnCreated(w, r, nil) + var v validationError + vars := mux.Vars(r) + dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) + groupId := v.checkExistingGroupId(vars["group_id"]) + type request struct { + Name string `json:"name"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkNameNotEmpty(req.Name) + if v.hasErrors() { + handleErr(w, r, v) + return + } + groupExists, err := h.dai.GetValidatorDashboardGroupExists(r.Context(), dashboardId, groupId) + if err != nil { + handleErr(w, r, err) + return + } + if !groupExists { + returnNotFound(w, r, errors.New("group not found")) + return + } + data, err := h.dai.UpdateValidatorDashboardGroup(r.Context(), dashboardId, groupId, name) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.ApiDataResponse[types.VDBPostCreateGroupData]{ + Data: *data, + } + + returnOk(w, r, response) } func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWriter, r *http.Request) { + var v validationError + vars := mux.Vars(r) + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + groupId := v.checkExistingGroupId(vars["group_id"]) + if v.hasErrors() { + handleErr(w, r, v) + return + } + if groupId == types.DefaultGroupId { + returnBadRequest(w, r, errors.New("cannot delete default group")) + return + } + groupExists, err := h.dai.GetValidatorDashboardGroupExists(r.Context(), dashboardId, groupId) + if err != nil { + handleErr(w, r, err) + return + } + if !groupExists { + returnNotFound(w, r, errors.New("group not found")) + return + } + err = h.dai.RemoveValidatorDashboardGroup(r.Context(), dashboardId, groupId) + if err != nil { + handleErr(w, r, err) + return + } + returnNoContent(w, r) } func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { var v validationError dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) - req := struct { - GroupId uint64 `json:"group_id,omitempty"` + type request struct { + GroupId uint64 `json:"group_id,omitempty" x-nullable:"true"` Validators []intOrString `json:"validators,omitempty"` DepositAddress string `json:"deposit_address,omitempty"` WithdrawalAddress string `json:"withdrawal_address,omitempty"` Graffiti string `json:"graffiti,omitempty"` - }{} + } + var req request if err := v.checkBody(&req, r); err != nil { handleErr(w, r, err) return @@ -173,8 +412,8 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW return } - ctx := r.Context() groupId := req.GroupId + ctx := r.Context() groupExists, err := h.dai.GetValidatorDashboardGroupExists(ctx, dashboardId, groupId) if err != nil { handleErr(w, r, err) @@ -254,7 +493,7 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW handleErr(w, r, dataErr) return } - response := types.ApiResponse{ + response := types.ApiDataResponse[[]types.VDBPostValidatorsData]{ Data: data, } @@ -262,7 +501,30 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW } func (h *HandlerService) PublicGetValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + groupId := v.checkGroupId(q.Get("group_id"), allowEmpty) + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBManageValidatorsColumn](&v, q.Get("sort")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + data, paging, err := h.dai.GetValidatorDashboardValidators(r.Context(), *dashboardId, groupId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardValidatorsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) { @@ -270,12 +532,17 @@ func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.Respons dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) var indices []uint64 var publicKeys []string - if validatorsParam := r.URL.Query().Get("validators"); validatorsParam != "" { - indices, publicKeys = v.checkValidatorList(validatorsParam, allowEmpty) - if v.hasErrors() { - handleErr(w, r, v) - return - } + req := struct { + Validators []intOrString `json:"validators"` + }{} + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + indices, publicKeys = v.checkValidators(req.Validators, false) + if v.hasErrors() { + handleErr(w, r, v) + return } validators, err := h.dai.GetValidatorsFromSlices(indices, publicKeys) if err != nil { @@ -292,87 +559,846 @@ func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.Respons } func (h *HandlerService) PublicPostValidatorDashboardPublicIds(w http.ResponseWriter, r *http.Request) { - returnCreated(w, r, nil) -} - -func (h *HandlerService) PublicPutValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) -} + var v validationError + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + type request struct { + Name string `json:"name,omitempty"` + ShareSettings struct { + ShareGroups bool `json:"share_groups"` + } `json:"share_settings"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkName(req.Name, 0) + if v.hasErrors() { + handleErr(w, r, v) + return + } + publicIdCount, err := h.dai.GetValidatorDashboardPublicIdCount(r.Context(), dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + if publicIdCount >= 1 { + returnConflict(w, r, errors.New("cannot create more than one public id")) + return + } -func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { - returnNoContent(w, r) -} + data, err := h.dai.CreateValidatorDashboardPublicId(r.Context(), dashboardId, name, req.ShareSettings.ShareGroups) + if err != nil { + handleErr(w, r, err) + return + } + response := types.ApiResponse{ + Data: data, + } -func (h *HandlerService) PublicGetValidatorDashboardSlotViz(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + returnCreated(w, r, response) +} + +func (h *HandlerService) PublicPutValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { + var v validationError + vars := mux.Vars(r) + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + type request struct { + Name string `json:"name,omitempty"` + ShareSettings struct { + ShareGroups bool `json:"share_groups"` + } `json:"share_settings"` + } + var req request + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + name := v.checkName(req.Name, 0) + publicDashboardId := v.checkValidatorDashboardPublicId(vars["public_id"]) + if v.hasErrors() { + handleErr(w, r, v) + return + } + fetchedId, err := h.dai.GetValidatorDashboardIdByPublicId(r.Context(), publicDashboardId) + if err != nil { + handleErr(w, r, err) + return + } + if *fetchedId != dashboardId { + handleErr(w, r, newNotFoundErr("public id %v not found", publicDashboardId)) + return + } + + data, err := h.dai.UpdateValidatorDashboardPublicId(r.Context(), publicDashboardId, name, req.ShareSettings.ShareGroups) + if err != nil { + handleErr(w, r, err) + return + } + response := types.ApiResponse{ + Data: data, + } + + returnOk(w, r, response) +} + +func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) { + var v validationError + vars := mux.Vars(r) + dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) + publicDashboardId := v.checkValidatorDashboardPublicId(vars["public_id"]) + if v.hasErrors() { + handleErr(w, r, v) + return + } + fetchedId, err := h.dai.GetValidatorDashboardIdByPublicId(r.Context(), publicDashboardId) + if err != nil { + handleErr(w, r, err) + return + } + if *fetchedId != dashboardId { + handleErr(w, r, newNotFoundErr("public id %v not found", publicDashboardId)) + return + } + + err = h.dai.RemoveValidatorDashboardPublicId(r.Context(), publicDashboardId) + if err != nil { + handleErr(w, r, err) + return + } + + returnNoContent(w, r) +} + +func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) { + var v validationError + dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"]) + req := struct { + IsArchived bool `json:"is_archived"` + }{} + if err := v.checkBody(&req, r); err != nil { + handleErr(w, r, err) + return + } + if v.hasErrors() { + handleErr(w, r, v) + return + } + + // check conditions for changing archival status + dashboardInfo, err := h.dai.GetValidatorDashboardInfo(r.Context(), dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + if dashboardInfo.IsArchived == req.IsArchived { + // nothing to do + returnOk(w, r, types.ApiDataResponse[types.VDBPostArchivingReturnData]{ + Data: types.VDBPostArchivingReturnData{Id: uint64(dashboardId), IsArchived: req.IsArchived}, + }) + return + } + + userId, err := GetUserIdByContext(r) + if err != nil { + handleErr(w, r, err) + return + } + dashboardCount, err := h.dai.GetUserValidatorDashboardCount(r.Context(), userId, !req.IsArchived) + if err != nil { + handleErr(w, r, err) + return + } + + userInfo, err := h.dai.GetUserInfo(r.Context(), userId) + if err != nil { + handleErr(w, r, err) + return + } + if !isUserAdmin(userInfo) { + if req.IsArchived { + if dashboardCount >= MaxArchivedDashboardsCount { + returnConflict(w, r, errors.New("maximum number of archived validator dashboards reached")) + return + } + } else { + if dashboardCount >= userInfo.PremiumPerks.ValidatorDashboards { + returnConflict(w, r, errors.New("maximum number of active validator dashboards reached")) + return + } + if dashboardInfo.GroupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard { + returnConflict(w, r, errors.New("maximum number of groups in dashboards reached")) + return + } + if dashboardInfo.ValidatorCount >= userInfo.PremiumPerks.ValidatorsPerDashboard { + returnConflict(w, r, errors.New("maximum number of validators in dashboards reached")) + return + } + } + } + + var archivedReason *enums.VDBArchivedReason + if req.IsArchived { + archivedReason = &enums.VDBArchivedReasons.User + } + + data, err := h.dai.UpdateValidatorDashboardArchiving(r.Context(), dashboardId, archivedReason) + if err != nil { + handleErr(w, r, err) + return + } + response := types.ApiDataResponse[types.VDBPostArchivingReturnData]{ + Data: *data, + } + returnOk(w, r, response) +} + +func (h *HandlerService) PublicGetValidatorDashboardSlotViz(w http.ResponseWriter, r *http.Request) { + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + + groupIds := v.checkExistingGroupIdList(r.URL.Query().Get("group_ids")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + data, err := h.dai.GetValidatorDashboardSlotViz(r.Context(), *dashboardId, groupIds) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardSlotVizResponse{ + Data: data, + } + + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBSummaryColumn](&v, q.Get("sort")) + protocolModes := v.checkProtocolModes(q.Get("modes")) + + period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") + // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h + allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} + v.checkEnumIsAllowed(period, allowedPeriods, "period") + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardSummary(r.Context(), *dashboardId, period, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardSummaryResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + q := r.URL.Query() + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + if err != nil { + handleErr(w, r, err) + return + } + groupId := v.checkGroupId(vars["group_id"], forbidEmpty) + period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period") + // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h + allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} + v.checkEnumIsAllowed(period, allowedPeriods, "period") + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardGroupSummary(r.Context(), *dashboardId, groupId, period, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardGroupSummaryResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardSummaryChart(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + ctx := r.Context() + dashboardId, err := h.handleDashboardId(ctx, mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + groupIds := v.checkGroupIdList(q.Get("group_ids")) + efficiencyType := checkEnum[enums.VDBSummaryChartEfficiencyType](&v, q.Get("efficiency_type"), "efficiency_type") + + aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") + chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(ctx, dashboardId, aggregation) + if err != nil { + handleErr(w, r, err) + return + } + afterTs, beforeTs := v.checkTimestamps(r, chartLimits) + if v.hasErrors() { + handleErr(w, r, v) + return + } + if afterTs < chartLimits.MinAllowedTs || beforeTs < chartLimits.MinAllowedTs { + returnConflict(w, r, fmt.Errorf("requested time range is too old, minimum timestamp for dashboard owner's premium subscription for this aggregation is %v", chartLimits.MinAllowedTs)) + return + } + + data, err := h.dai.GetValidatorDashboardSummaryChart(ctx, *dashboardId, groupIds, efficiencyType, aggregation, afterTs, beforeTs) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardSummaryChartResponse{ + Data: *data, + } + returnOk(w, r, response) +} + +func (h *HandlerService) PublicGetValidatorDashboardSummaryValidators(w http.ResponseWriter, r *http.Request) { + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + groupId := v.checkGroupId(r.URL.Query().Get("group_id"), allowEmpty) + q := r.URL.Query() + duty := checkEnum[enums.ValidatorDuty](&v, q.Get("duty"), "duty") + period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period") + // allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h + allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h} + v.checkEnumIsAllowed(period, allowedPeriods, "period") + if v.hasErrors() { + handleErr(w, r, v) + return + } + + // get indices based on duty + var indices interface{} + duties := enums.ValidatorDuties + switch duty { + case duties.None: + indices, err = h.dai.GetValidatorDashboardSummaryValidators(r.Context(), *dashboardId, groupId) + case duties.Sync: + indices, err = h.dai.GetValidatorDashboardSyncSummaryValidators(r.Context(), *dashboardId, groupId, period) + case duties.Slashed: + indices, err = h.dai.GetValidatorDashboardSlashingsSummaryValidators(r.Context(), *dashboardId, groupId, period) + case duties.Proposal: + indices, err = h.dai.GetValidatorDashboardProposalSummaryValidators(r.Context(), *dashboardId, groupId, period) + } + if err != nil { + handleErr(w, r, err) + return + } + // map indices to response format + data, err := mapVDBIndices(indices) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.GetValidatorDashboardSummaryValidatorsResponse{ + Data: data, + } + + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardRewards(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBRewardsColumn](&v, q.Get("sort")) + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardRewards(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardRewardsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardGroupRewards(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + groupId := v.checkGroupId(vars["group_id"], forbidEmpty) + epoch := v.checkUint(vars["epoch"], "epoch") + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardGroupRewards(r.Context(), *dashboardId, groupId, epoch, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardGroupRewardsResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardRewardsChart(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardRewardsChart(r.Context(), *dashboardId, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardRewardsChartResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardDuties(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + groupId := v.checkGroupId(q.Get("group_id"), allowEmpty) + epoch := v.checkUint(vars["epoch"], "epoch") + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBDutiesColumn](&v, q.Get("sort")) + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardDuties(r.Context(), *dashboardId, epoch, groupId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardDutiesResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardBlocks(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBBlocksColumn](&v, q.Get("sort")) + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardBlocks(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardBlocksResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + q := r.URL.Query() + protocolModes := v.checkProtocolModes(q.Get("modes")) + aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") + chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(r.Context(), dashboardId, aggregation) + if err != nil { + handleErr(w, r, err) + return + } + afterTs, beforeTs := v.checkTimestamps(r, chartLimits) + if v.hasErrors() { + handleErr(w, r, v) + return + } + if afterTs < chartLimits.MinAllowedTs || beforeTs < chartLimits.MinAllowedTs { + returnConflict(w, r, fmt.Errorf("requested time range is too old, minimum timestamp for dashboard owner's premium subscription for this aggregation is %v", chartLimits.MinAllowedTs)) + return + } + + data, err := h.dai.GetValidatorDashboardHeatmap(r.Context(), *dashboardId, protocolModes, aggregation, afterTs, beforeTs) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardHeatmapResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + groupId := v.checkExistingGroupId(vars["group_id"]) + requestedTimestamp := v.checkUint(vars["timestamp"], "timestamp") + protocolModes := v.checkProtocolModes(r.URL.Query().Get("modes")) + aggregation := checkEnum[enums.ChartAggregation](&v, r.URL.Query().Get("aggregation"), "aggregation") + if v.hasErrors() { + handleErr(w, r, v) + return + } + chartLimits, err := h.getCurrentChartTimeLimitsForDashboard(r.Context(), dashboardId, aggregation) + if err != nil { + handleErr(w, r, err) + return + } + if requestedTimestamp < chartLimits.MinAllowedTs || requestedTimestamp > chartLimits.LatestExportedTs { + handleErr(w, r, newConflictErr("requested timestamp is outside of allowed chart history for dashboard owner's premium subscription")) + return + } + + data, err := h.dai.GetValidatorDashboardGroupHeatmap(r.Context(), *dashboardId, groupId, protocolModes, aggregation, requestedTimestamp) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardGroupHeatmapResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(r.URL.Query()) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardElDeposits(r.Context(), *dashboardId, pagingParams.cursor, pagingParams.limit) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardExecutionLayerDepositsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(r.URL.Query()) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardClDeposits(r.Context(), *dashboardId, pagingParams.cursor, pagingParams.limit) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.GetValidatorDashboardConsensusLayerDepositsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) +} + +func (h *HandlerService) PublicGetValidatorDashboardTotalConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) { + var err error + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + data, err := h.dai.GetValidatorDashboardTotalClDeposits(r.Context(), *dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.GetValidatorDashboardTotalConsensusDepositsResponse{ + Data: *data, + } + returnOk(w, r, response) +} + +func (h *HandlerService) PublicGetValidatorDashboardTotalExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) { + var err error + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + data, err := h.dai.GetValidatorDashboardTotalElDeposits(r.Context(), *dashboardId) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.GetValidatorDashboardTotalExecutionDepositsResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardWithdrawals(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBWithdrawalsColumn](&v, q.Get("sort")) + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardWithdrawals(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardWithdrawalsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) +} + +func (h *HandlerService) PublicGetValidatorDashboardTotalWithdrawals(w http.ResponseWriter, r *http.Request) { + var v validationError + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(q) + protocolModes := v.checkProtocolModes(q.Get("modes")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardTotalWithdrawals(r.Context(), *dashboardId, pagingParams.search, protocolModes) + if err != nil { + handleErr(w, r, err) + return + } + + response := types.GetValidatorDashboardTotalWithdrawalsResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardRocketPool(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBRocketPoolColumn](&v, q.Get("sort")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardRocketPool(r.Context(), *dashboardId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardRocketPoolResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardTotalRocketPool(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + pagingParams := v.checkPagingParams(q) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardTotalRocketPool(r.Context(), *dashboardId, pagingParams.search) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardTotalRocketPoolResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardNodeRocketPool(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + // support ENS names ? + nodeAddress := v.checkAddress(vars["node_address"]) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, err := h.dai.GetValidatorDashboardNodeRocketPool(r.Context(), *dashboardId, nodeAddress) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardNodeRocketPoolResponse{ + Data: *data, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetValidatorDashboardRocketPoolMinipools(w http.ResponseWriter, r *http.Request) { - returnOk(w, r, nil) + var v validationError + vars := mux.Vars(r) + q := r.URL.Query() + dashboardId, err := h.handleDashboardId(r.Context(), vars["dashboard_id"]) + if err != nil { + handleErr(w, r, err) + return + } + // support ENS names ? + nodeAddress := v.checkAddress(vars["node_address"]) + pagingParams := v.checkPagingParams(q) + sort := checkSort[enums.VDBRocketPoolMinipoolsColumn](&v, q.Get("sort")) + if v.hasErrors() { + handleErr(w, r, v) + return + } + + data, paging, err := h.dai.GetValidatorDashboardRocketPoolMinipools(r.Context(), *dashboardId, nodeAddress, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) + if err != nil { + handleErr(w, r, err) + return + } + response := types.GetValidatorDashboardRocketPoolMinipoolsResponse{ + Data: data, + Paging: *paging, + } + returnOk(w, r, response) } func (h *HandlerService) PublicGetNetworkValidators(w http.ResponseWriter, r *http.Request) { diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go index e3ce14420..5603e4856 100644 --- a/backend/pkg/api/router.go +++ b/backend/pkg/api/router.go @@ -265,19 +265,19 @@ func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, inte endpoints := []endpoint{ {http.MethodGet, "/{dashboard_id}", hs.PublicGetValidatorDashboard, hs.InternalGetValidatorDashboard}, - {http.MethodPut, "/{dashboard_id}/name", nil, hs.InternalPutValidatorDashboardName}, + {http.MethodPut, "/{dashboard_id}/name", hs.PublicPutValidatorDashboardName, hs.InternalPutValidatorDashboardName}, {http.MethodPost, "/{dashboard_id}/groups", hs.PublicPostValidatorDashboardGroups, hs.InternalPostValidatorDashboardGroups}, {http.MethodPut, "/{dashboard_id}/groups/{group_id}", hs.PublicPutValidatorDashboardGroups, hs.InternalPutValidatorDashboardGroups}, {http.MethodDelete, "/{dashboard_id}/groups/{group_id}", hs.PublicDeleteValidatorDashboardGroup, hs.InternalDeleteValidatorDashboardGroup}, {http.MethodPost, "/{dashboard_id}/validators", hs.PublicPostValidatorDashboardValidators, hs.InternalPostValidatorDashboardValidators}, {http.MethodGet, "/{dashboard_id}/validators", hs.PublicGetValidatorDashboardValidators, hs.InternalGetValidatorDashboardValidators}, - {http.MethodDelete, "/{dashboard_id}/validators", hs.PublicDeleteValidatorDashboardValidators, hs.InternalDeleteValidatorDashboardValidators}, + {http.MethodPost, "/{dashboard_id}/validators/bulk-deletions", hs.PublicDeleteValidatorDashboardValidators, hs.InternalDeleteValidatorDashboardValidators}, {http.MethodPost, "/{dashboard_id}/public-ids", hs.PublicPostValidatorDashboardPublicIds, hs.InternalPostValidatorDashboardPublicIds}, {http.MethodPut, "/{dashboard_id}/public-ids/{public_id}", hs.PublicPutValidatorDashboardPublicId, hs.InternalPutValidatorDashboardPublicId}, {http.MethodDelete, "/{dashboard_id}/public-ids/{public_id}", hs.PublicDeleteValidatorDashboardPublicId, hs.InternalDeleteValidatorDashboardPublicId}, {http.MethodGet, "/{dashboard_id}/slot-viz", hs.PublicGetValidatorDashboardSlotViz, hs.InternalGetValidatorDashboardSlotViz}, {http.MethodGet, "/{dashboard_id}/summary", hs.PublicGetValidatorDashboardSummary, hs.InternalGetValidatorDashboardSummary}, - {http.MethodGet, "/{dashboard_id}/summary/validators", nil, hs.InternalGetValidatorDashboardSummaryValidators}, + {http.MethodGet, "/{dashboard_id}/summary/validators", hs.PublicGetValidatorDashboardSummaryValidators, hs.InternalGetValidatorDashboardSummaryValidators}, {http.MethodGet, "/{dashboard_id}/groups/{group_id}/summary", hs.PublicGetValidatorDashboardGroupSummary, hs.InternalGetValidatorDashboardGroupSummary}, {http.MethodGet, "/{dashboard_id}/summary-chart", hs.PublicGetValidatorDashboardSummaryChart, hs.InternalGetValidatorDashboardSummaryChart}, {http.MethodGet, "/{dashboard_id}/rewards", hs.PublicGetValidatorDashboardRewards, hs.InternalGetValidatorDashboardRewards}, @@ -289,10 +289,10 @@ func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, inte {http.MethodGet, "/{dashboard_id}/groups/{group_id}/heatmap/{timestamp}", hs.PublicGetValidatorDashboardGroupHeatmap, hs.InternalGetValidatorDashboardGroupHeatmap}, {http.MethodGet, "/{dashboard_id}/execution-layer-deposits", hs.PublicGetValidatorDashboardExecutionLayerDeposits, hs.InternalGetValidatorDashboardExecutionLayerDeposits}, {http.MethodGet, "/{dashboard_id}/consensus-layer-deposits", hs.PublicGetValidatorDashboardConsensusLayerDeposits, hs.InternalGetValidatorDashboardConsensusLayerDeposits}, - {http.MethodGet, "/{dashboard_id}/total-execution-layer-deposits", nil, hs.InternalGetValidatorDashboardTotalExecutionLayerDeposits}, - {http.MethodGet, "/{dashboard_id}/total-consensus-layer-deposits", nil, hs.InternalGetValidatorDashboardTotalConsensusLayerDeposits}, + {http.MethodGet, "/{dashboard_id}/total-execution-layer-deposits", hs.PublicGetValidatorDashboardTotalExecutionLayerDeposits, hs.InternalGetValidatorDashboardTotalExecutionLayerDeposits}, + {http.MethodGet, "/{dashboard_id}/total-consensus-layer-deposits", hs.PublicGetValidatorDashboardTotalConsensusLayerDeposits, hs.InternalGetValidatorDashboardTotalConsensusLayerDeposits}, {http.MethodGet, "/{dashboard_id}/withdrawals", hs.PublicGetValidatorDashboardWithdrawals, hs.InternalGetValidatorDashboardWithdrawals}, - {http.MethodGet, "/{dashboard_id}/total-withdrawals", nil, hs.InternalGetValidatorDashboardTotalWithdrawals}, + {http.MethodGet, "/{dashboard_id}/total-withdrawals", hs.PublicGetValidatorDashboardTotalWithdrawals, hs.InternalGetValidatorDashboardTotalWithdrawals}, {http.MethodGet, "/{dashboard_id}/rocket-pool", hs.PublicGetValidatorDashboardRocketPool, hs.InternalGetValidatorDashboardRocketPool}, {http.MethodGet, "/{dashboard_id}/total-rocket-pool", hs.PublicGetValidatorDashboardTotalRocketPool, hs.InternalGetValidatorDashboardTotalRocketPool}, {http.MethodGet, "/{dashboard_id}/rocket-pool/{node_address}", hs.PublicGetValidatorDashboardNodeRocketPool, hs.InternalGetValidatorDashboardNodeRocketPool}, diff --git a/backend/pkg/api/services/service.go b/backend/pkg/api/services/service.go index 2e009e85c..8108d9687 100644 --- a/backend/pkg/api/services/service.go +++ b/backend/pkg/api/services/service.go @@ -1,6 +1,8 @@ package services import ( + "sync" + "github.com/go-redis/redis/v8" "github.com/gobitfly/beaconchain/pkg/commons/db" "github.com/gobitfly/beaconchain/pkg/commons/log" @@ -35,12 +37,18 @@ func NewServices(readerDb, writerDb, alloyReader, alloyWriter, clickhouseReader } func (s *Services) InitServices() { - go s.startSlotVizDataService() - go s.startIndexMappingService() - go s.startEfficiencyDataService() - go s.startEmailSenderService() + wg := &sync.WaitGroup{} + log.Infof("initializing services...") + wg.Add(4) + go s.startSlotVizDataService(wg) + go s.startIndexMappingService(wg) + go s.startEfficiencyDataService(wg) + go s.startEmailSenderService(wg) log.Infof("initializing prices...") price.Init(utils.Config.Chain.ClConfig.DepositChainID, utils.Config.Eth1ErigonEndpoint, utils.Config.Frontend.ClCurrency, utils.Config.Frontend.ElCurrency) log.Infof("...prices initialized") + + wg.Wait() + log.Infof("...services initialized") } diff --git a/backend/pkg/api/services/service_average_network_efficiency.go b/backend/pkg/api/services/service_average_network_efficiency.go index 725ff0511..083b06dc2 100644 --- a/backend/pkg/api/services/service_average_network_efficiency.go +++ b/backend/pkg/api/services/service_average_network_efficiency.go @@ -22,7 +22,8 @@ import ( var currentEfficiencyInfo atomic.Pointer[EfficiencyData] -func (s *Services) startEfficiencyDataService() { +func (s *Services) startEfficiencyDataService(wg *sync.WaitGroup) { + o := sync.Once{} for { startTime := time.Now() delay := time.Duration(utils.Config.Chain.ClConfig.SlotsPerEpoch*utils.Config.Chain.ClConfig.SecondsPerSlot) * time.Second @@ -36,6 +37,9 @@ func (s *Services) startEfficiencyDataService() { } else { log.Infof("=== average network efficiency data updated in %s", time.Since(startTime)) r(constants.Success, map[string]string{"took": time.Since(startTime).String()}) + o.Do(func() { + wg.Done() + }) } utils.ConstantTimeDelay(startTime, delay) } diff --git a/backend/pkg/api/services/service_email_sender.go b/backend/pkg/api/services/service_email_sender.go index 4bf0bee69..fa8cf61e2 100644 --- a/backend/pkg/api/services/service_email_sender.go +++ b/backend/pkg/api/services/service_email_sender.go @@ -1,6 +1,7 @@ package services import ( + "sync" "time" "github.com/gobitfly/beaconchain/pkg/commons/log" @@ -29,7 +30,8 @@ var Queue []QueuedEmail // collects & queues mails, sends in batches regularly (possibly aggregating multiple messasages to the same user to avoid spam?) // TODO ratelimiting // TODO send via SMTP/mailgun/others? -func (s *Services) startEmailSenderService() { +func (s *Services) startEmailSenderService(wg *sync.WaitGroup) { + o := sync.Once{} for { startTime := time.Now() // lock mutex @@ -44,6 +46,9 @@ func (s *Services) startEmailSenderService() { }*/ } log.Infof("=== message sending done in %s", time.Since(startTime)) + o.Do(func() { + wg.Done() + }) utils.ConstantTimeDelay(startTime, 30*time.Second) } } diff --git a/backend/pkg/api/services/service_slot_viz.go b/backend/pkg/api/services/service_slot_viz.go index e26145445..87c10cebc 100644 --- a/backend/pkg/api/services/service_slot_viz.go +++ b/backend/pkg/api/services/service_slot_viz.go @@ -27,7 +27,8 @@ import ( var currentDutiesInfo atomic.Pointer[SyncData] -func (s *Services) startSlotVizDataService() { +func (s *Services) startSlotVizDataService(wg *sync.WaitGroup) { + o := sync.Once{} for { startTime := time.Now() delay := time.Duration(utils.Config.Chain.ClConfig.SecondsPerSlot) * time.Second @@ -40,6 +41,9 @@ func (s *Services) startSlotVizDataService() { } log.Infof("=== slotviz data updated in %s", time.Since(startTime)) r(constants.Success, map[string]string{"took": time.Since(startTime).String()}) + o.Do(func() { + wg.Done() + }) utils.ConstantTimeDelay(startTime, delay) } } @@ -203,6 +207,9 @@ func (s *Services) updateSlotVizData() error { if duty.Slot > dutiesInfo.LatestSlot { dutiesInfo.LatestSlot = duty.Slot } + if duty.Status == 1 && duty.Slot > dutiesInfo.LatestProposedSlot { + dutiesInfo.LatestProposedSlot = duty.Slot + } dutiesInfo.SlotStatus[duty.Slot] = duty.Status dutiesInfo.SlotBlock[duty.Slot] = duty.Block if duty.Status == 1 { // 1: Proposed @@ -292,6 +299,7 @@ func (s *Services) GetCurrentDutiesInfo() (*SyncData, error) { func (s *Services) initDutiesInfo() *SyncData { dutiesInfo := SyncData{} dutiesInfo.LatestSlot = uint64(0) + dutiesInfo.LatestProposedSlot = uint64(0) dutiesInfo.SlotStatus = make(map[uint64]int8) dutiesInfo.SlotBlock = make(map[uint64]uint64) dutiesInfo.SlotSyncParticipated = make(map[uint64]map[constypes.ValidatorIndex]bool) @@ -318,6 +326,7 @@ func (s *Services) copyAndCleanDutiesInfo() *SyncData { dutiesInfo := &SyncData{ LatestSlot: p.LatestSlot, + LatestProposedSlot: p.LatestProposedSlot, SlotStatus: make(map[uint64]int8, len(p.SlotStatus)), SlotBlock: make(map[uint64]uint64, len(p.SlotBlock)), SlotSyncParticipated: make(map[uint64]map[constypes.ValidatorIndex]bool, len(p.SlotSyncParticipated)), @@ -451,6 +460,7 @@ func (s *Services) getMaxValidatorDutiesInfoSlot() uint64 { type SyncData struct { LatestSlot uint64 + LatestProposedSlot uint64 SlotStatus map[uint64]int8 // slot -> status SlotBlock map[uint64]uint64 // slot -> block SlotSyncParticipated map[uint64]map[uint64]bool // slot -> validatorindex -> participated diff --git a/backend/pkg/api/services/service_validator_mapping.go b/backend/pkg/api/services/service_validator_mapping.go index 374c6906a..187c41a14 100644 --- a/backend/pkg/api/services/service_validator_mapping.go +++ b/backend/pkg/api/services/service_validator_mapping.go @@ -5,6 +5,7 @@ import ( "context" "encoding/gob" "fmt" + "sync" "sync/atomic" "time" @@ -33,8 +34,9 @@ var _cachedRedisValidatorMapping = new(types.RedisCachedValidatorsMapping) var lastEpochUpdate = uint64(0) -func (s *Services) startIndexMappingService() { +func (s *Services) startIndexMappingService(wg *sync.WaitGroup) { var err error + o := sync.Once{} for { startTime := time.Now() delay := time.Duration(utils.Config.Chain.ClConfig.SecondsPerSlot) * time.Second @@ -53,6 +55,9 @@ func (s *Services) startIndexMappingService() { log.Infof("=== validator mapping updated in %s", time.Since(startTime)) r(constants.Success, map[string]string{"took": time.Since(startTime).String(), "latest_epoch": fmt.Sprintf("%d", lastEpochUpdate)}) lastEpochUpdate = latestEpoch + o.Do(func() { + wg.Done() + }) } utils.ConstantTimeDelay(startTime, delay) } diff --git a/backend/pkg/api/types/archiver.go b/backend/pkg/api/types/archiver.go new file mode 100644 index 000000000..ac1666e4a --- /dev/null +++ b/backend/pkg/api/types/archiver.go @@ -0,0 +1,15 @@ +package types + +import "github.com/gobitfly/beaconchain/pkg/api/enums" + +type ArchiverDashboard struct { + DashboardId uint64 + IsArchived bool + GroupCount uint64 + ValidatorCount uint64 +} + +type ArchiverDashboardArchiveReason struct { + DashboardId uint64 + ArchivedReason enums.VDBArchivedReason +} diff --git a/backend/pkg/api/types/slot_viz.go b/backend/pkg/api/types/slot_viz.go index c8d32c1e6..7f1fd3cb0 100644 --- a/backend/pkg/api/types/slot_viz.go +++ b/backend/pkg/api/types/slot_viz.go @@ -42,4 +42,4 @@ type SlotVizEpoch struct { Slots []VDBSlotVizSlot `json:"slots,omitempty" faker:"slice_len=32"` // only on dashboard page } -type InternalGetValidatorDashboardSlotVizResponse ApiDataResponse[[]SlotVizEpoch] +type GetValidatorDashboardSlotVizResponse ApiDataResponse[[]SlotVizEpoch] diff --git a/backend/pkg/api/types/user.go b/backend/pkg/api/types/user.go index 083184653..177159ad7 100644 --- a/backend/pkg/api/types/user.go +++ b/backend/pkg/api/types/user.go @@ -117,7 +117,7 @@ type ExtraDashboardValidatorsPremiumAddon struct { type PremiumPerks struct { AdFree bool `json:"ad_free"` // note that this is somhow redunant, since there is already ApiPerks.NoAds - ValidatorDasboards uint64 `json:"validator_dashboards"` + ValidatorDashboards uint64 `json:"validator_dashboards"` ValidatorsPerDashboard uint64 `json:"validators_per_dashboard"` ValidatorGroupsPerDashboard uint64 `json:"validator_groups_per_dashboard"` ShareCustomDashboards bool `json:"share_custom_dashboards"` diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go index c24781631..cef626e57 100644 --- a/backend/pkg/api/types/validator_dashboard.go +++ b/backend/pkg/api/types/validator_dashboard.go @@ -37,7 +37,7 @@ type VDBOverviewData struct { Balances VDBOverviewBalances `json:"balances"` } -type InternalGetValidatorDashboardResponse ApiDataResponse[VDBOverviewData] +type GetValidatorDashboardResponse ApiDataResponse[VDBOverviewData] type VDBPostArchivingReturnData struct { Id uint64 `db:"id" json:"id"` @@ -68,7 +68,7 @@ type VDBSummaryTableRow struct { Proposals StatusCount `json:"proposals"` Reward ClElValue[decimal.Decimal] `json:"reward" faker:"cl_el_eth"` } -type InternalGetValidatorDashboardSummaryResponse ApiPagingResponse[VDBSummaryTableRow] +type GetValidatorDashboardSummaryResponse ApiPagingResponse[VDBSummaryTableRow] type VDBGroupSummaryColumnItem struct { StatusCount StatusCount `json:"status_count"` @@ -108,9 +108,9 @@ type VDBGroupSummaryData struct { Collateral float64 `json:"collateral"` } `json:"rocket_pool,omitempty"` } -type InternalGetValidatorDashboardGroupSummaryResponse ApiDataResponse[VDBGroupSummaryData] +type GetValidatorDashboardGroupSummaryResponse ApiDataResponse[VDBGroupSummaryData] -type InternalGetValidatorDashboardSummaryChartResponse ApiDataResponse[ChartData[int, float64]] // line chart, series id is group id +type GetValidatorDashboardSummaryChartResponse ApiDataResponse[ChartData[int, float64]] // line chart, series id is group id // ------------------------------------------------------------ // Summary Validators @@ -123,7 +123,7 @@ type VDBSummaryValidatorsData struct { Validators []VDBSummaryValidator `json:"validators"` } -type InternalGetValidatorDashboardSummaryValidatorsResponse ApiDataResponse[[]VDBSummaryValidatorsData] +type GetValidatorDashboardSummaryValidatorsResponse ApiDataResponse[[]VDBSummaryValidatorsData] // ------------------------------------------------------------ // Rewards Tab @@ -141,7 +141,7 @@ type VDBRewardsTableRow struct { Reward ClElValue[decimal.Decimal] `json:"reward"` } -type InternalGetValidatorDashboardRewardsResponse ApiPagingResponse[VDBRewardsTableRow] +type GetValidatorDashboardRewardsResponse ApiPagingResponse[VDBRewardsTableRow] type VDBGroupRewardsDetails struct { StatusCount StatusCount `json:"status_count"` @@ -161,9 +161,9 @@ type VDBGroupRewardsData struct { ProposalClSyncIncReward decimal.Decimal `json:"proposal_cl_sync_inc_reward"` ProposalClSlashingIncReward decimal.Decimal `json:"proposal_cl_slashing_inc_reward"` } -type InternalGetValidatorDashboardGroupRewardsResponse ApiDataResponse[VDBGroupRewardsData] +type GetValidatorDashboardGroupRewardsResponse ApiDataResponse[VDBGroupRewardsData] -type InternalGetValidatorDashboardRewardsChartResponse ApiDataResponse[ChartData[int, decimal.Decimal]] // bar chart, series id is group id, property is 'el' or 'cl' +type GetValidatorDashboardRewardsChartResponse ApiDataResponse[ChartData[int, decimal.Decimal]] // bar chart, series id is group id, property is 'el' or 'cl' // Duties Modal @@ -171,7 +171,7 @@ type VDBEpochDutiesTableRow struct { Validator uint64 `json:"validator"` Duties ValidatorHistoryDuties `json:"duties"` } -type InternalGetValidatorDashboardDutiesResponse ApiPagingResponse[VDBEpochDutiesTableRow] +type GetValidatorDashboardDutiesResponse ApiPagingResponse[VDBEpochDutiesTableRow] // ------------------------------------------------------------ // Blocks Tab @@ -186,7 +186,7 @@ type VDBBlocksTableRow struct { Reward *ClElValue[decimal.Decimal] `json:"reward,omitempty"` Graffiti *string `json:"graffiti,omitempty"` } -type InternalGetValidatorDashboardBlocksResponse ApiPagingResponse[VDBBlocksTableRow] +type GetValidatorDashboardBlocksResponse ApiPagingResponse[VDBBlocksTableRow] // ------------------------------------------------------------ // Heatmap Tab @@ -209,7 +209,7 @@ type VDBHeatmap struct { Data []VDBHeatmapCell `json:"data"` Aggregation string `json:"aggregation" tstype:"'epoch' | 'hourly' | 'daily' | 'weekly'" faker:"oneof: epoch, hourly, daily, weekly"` } -type InternalGetValidatorDashboardHeatmapResponse ApiDataResponse[VDBHeatmap] +type GetValidatorDashboardHeatmapResponse ApiDataResponse[VDBHeatmap] type VDBHeatmapTooltipData struct { Timestamp int64 `json:"timestamp"` @@ -224,7 +224,7 @@ type VDBHeatmapTooltipData struct { AttestationIncome decimal.Decimal `json:"attestation_income"` AttestationEfficiency float64 `json:"attestation_efficiency"` } -type InternalGetValidatorDashboardGroupHeatmapResponse ApiDataResponse[VDBHeatmapTooltipData] +type GetValidatorDashboardGroupHeatmapResponse ApiDataResponse[VDBHeatmapTooltipData] // ------------------------------------------------------------ // Deposits Tab @@ -241,7 +241,7 @@ type VDBExecutionDepositsTableRow struct { Amount decimal.Decimal `json:"amount"` Valid bool `json:"valid"` } -type InternalGetValidatorDashboardExecutionLayerDepositsResponse ApiPagingResponse[VDBExecutionDepositsTableRow] +type GetValidatorDashboardExecutionLayerDepositsResponse ApiPagingResponse[VDBExecutionDepositsTableRow] type VDBConsensusDepositsTableRow struct { PublicKey PubKey `json:"public_key"` @@ -253,19 +253,19 @@ type VDBConsensusDepositsTableRow struct { Amount decimal.Decimal `json:"amount"` Signature Hash `json:"signature"` } -type InternalGetValidatorDashboardConsensusLayerDepositsResponse ApiPagingResponse[VDBConsensusDepositsTableRow] +type GetValidatorDashboardConsensusLayerDepositsResponse ApiPagingResponse[VDBConsensusDepositsTableRow] type VDBTotalExecutionDepositsData struct { TotalAmount decimal.Decimal `json:"total_amount"` } -type InternalGetValidatorDashboardTotalExecutionDepositsResponse ApiDataResponse[VDBTotalExecutionDepositsData] +type GetValidatorDashboardTotalExecutionDepositsResponse ApiDataResponse[VDBTotalExecutionDepositsData] type VDBTotalConsensusDepositsData struct { TotalAmount decimal.Decimal `json:"total_amount"` } -type InternalGetValidatorDashboardTotalConsensusDepositsResponse ApiDataResponse[VDBTotalConsensusDepositsData] +type GetValidatorDashboardTotalConsensusDepositsResponse ApiDataResponse[VDBTotalConsensusDepositsData] // ------------------------------------------------------------ // Withdrawals Tab @@ -278,13 +278,13 @@ type VDBWithdrawalsTableRow struct { Amount decimal.Decimal `json:"amount"` IsMissingEstimate bool `json:"is_missing_estimate"` } -type InternalGetValidatorDashboardWithdrawalsResponse ApiPagingResponse[VDBWithdrawalsTableRow] +type GetValidatorDashboardWithdrawalsResponse ApiPagingResponse[VDBWithdrawalsTableRow] type VDBTotalWithdrawalsData struct { TotalAmount decimal.Decimal `json:"total_amount"` } -type InternalGetValidatorDashboardTotalWithdrawalsResponse ApiDataResponse[VDBTotalWithdrawalsData] +type GetValidatorDashboardTotalWithdrawalsResponse ApiDataResponse[VDBTotalWithdrawalsData] // ------------------------------------------------------------ // Rocket Pool Tab @@ -315,9 +315,9 @@ type VDBRocketPoolTableRow struct { Unclaimed decimal.Decimal `json:"unclaimed"` } `json:"smoothing_pool"` } -type InternalGetValidatorDashboardRocketPoolResponse ApiPagingResponse[VDBRocketPoolTableRow] +type GetValidatorDashboardRocketPoolResponse ApiPagingResponse[VDBRocketPoolTableRow] -type InternalGetValidatorDashboardTotalRocketPoolResponse ApiDataResponse[VDBRocketPoolTableRow] +type GetValidatorDashboardTotalRocketPoolResponse ApiDataResponse[VDBRocketPoolTableRow] type VDBNodeRocketPoolData struct { Timezone string `json:"timezone"` @@ -329,7 +329,7 @@ type VDBNodeRocketPoolData struct { } `json:"rpl_stake"` } -type InternalGetValidatorDashboardNodeRocketPoolResponse ApiDataResponse[VDBNodeRocketPoolData] +type GetValidatorDashboardNodeRocketPoolResponse ApiDataResponse[VDBNodeRocketPoolData] type VDBRocketPoolMinipoolsTableRow struct { Node Address `json:"node"` @@ -342,7 +342,7 @@ type VDBRocketPoolMinipoolsTableRow struct { CreatedTimestamp int64 `json:"created_timestamp"` Penalties uint64 `json:"penalties"` } -type InternalGetValidatorDashboardRocketPoolMinipoolsResponse ApiPagingResponse[VDBRocketPoolMinipoolsTableRow] +type GetValidatorDashboardRocketPoolMinipoolsResponse ApiPagingResponse[VDBRocketPoolMinipoolsTableRow] // ------------------------------------------------------------ // Manage Modal @@ -356,7 +356,7 @@ type VDBManageValidatorsTableRow struct { WithdrawalCredential Hash `json:"withdrawal_credential"` } -type InternalGetValidatorDashboardValidatorsResponse ApiPagingResponse[VDBManageValidatorsTableRow] +type GetValidatorDashboardValidatorsResponse ApiPagingResponse[VDBManageValidatorsTableRow] // ------------------------------------------------------------ // Misc. diff --git a/backend/pkg/archiver/archiver.go b/backend/pkg/archiver/archiver.go new file mode 100644 index 000000000..6072ef20e --- /dev/null +++ b/backend/pkg/archiver/archiver.go @@ -0,0 +1,133 @@ +package archiver + +import ( + "context" + "slices" + "time" + + dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access" + "github.com/gobitfly/beaconchain/pkg/api/enums" + "github.com/gobitfly/beaconchain/pkg/api/handlers" + t "github.com/gobitfly/beaconchain/pkg/api/types" + "github.com/gobitfly/beaconchain/pkg/commons/log" + "github.com/gobitfly/beaconchain/pkg/commons/utils" +) + +type Archiver struct { + das *dataaccess.DataAccessService +} + +func NewArchiver(d *dataaccess.DataAccessService) (*Archiver, error) { + archiver := &Archiver{ + das: d, + } + return archiver, nil +} + +func (a *Archiver) Start() { + for { + err := a.updateArchivedStatus() + if err != nil { + log.Error(err, "failed updating dashboard archive status", 0) + } + time.Sleep(utils.Day) + } +} + +func (a *Archiver) updateArchivedStatus() error { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) + defer cancel() + + var dashboardsToBeArchived []t.ArchiverDashboardArchiveReason + var dashboardsToBeDeleted []uint64 + + // Get all dashboards for all users + userDashboards, err := a.das.GetValidatorDashboardsCountInfo(ctx) + if err != nil { + return err + } + + for userId, dashboards := range userDashboards { + // TODO: For better performance there should exist a method to get all user info at once + userInfo, err := a.das.GetUserInfo(ctx, userId) + if err != nil { + return err + } + + if userInfo.UserGroup == t.UserGroupAdmin { + // Don't archive or delete anything for admins + continue + } + + var archivedDashboards []uint64 + var activeDashboards []uint64 + + // Check if the active user dashboard exceeds the maximum number of groups, or validators + for _, dashboardInfo := range dashboards { + if dashboardInfo.IsArchived { + archivedDashboards = append(archivedDashboards, dashboardInfo.DashboardId) + } else { + if dashboardInfo.GroupCount > userInfo.PremiumPerks.ValidatorGroupsPerDashboard { + dashboardsToBeArchived = append(dashboardsToBeArchived, t.ArchiverDashboardArchiveReason{DashboardId: dashboardInfo.DashboardId, ArchivedReason: enums.VDBArchivedReasons.Groups}) + } else if dashboardInfo.ValidatorCount > userInfo.PremiumPerks.ValidatorsPerDashboard { + dashboardsToBeArchived = append(dashboardsToBeArchived, t.ArchiverDashboardArchiveReason{DashboardId: dashboardInfo.DashboardId, ArchivedReason: enums.VDBArchivedReasons.Validators}) + } else { + activeDashboards = append(activeDashboards, dashboardInfo.DashboardId) + } + } + } + + // Check if the user still exceeds the maximum number of active dashboards + dashboardLimit := int(userInfo.PremiumPerks.ValidatorDashboards) + if len(activeDashboards) > dashboardLimit { + slices.Sort(activeDashboards) + for id := 0; id < len(activeDashboards)-dashboardLimit; id++ { + dashboardsToBeArchived = append(dashboardsToBeArchived, t.ArchiverDashboardArchiveReason{DashboardId: activeDashboards[id], ArchivedReason: enums.VDBArchivedReasons.Dashboards}) + } + } + + // Check if the user exceeds the maximum number of archived dashboards + archivedLimit := handlers.MaxArchivedDashboardsCount + if len(archivedDashboards)+len(dashboardsToBeArchived) > archivedLimit { + dashboardsToBeDeletedForUser := archivedDashboards + for _, dashboard := range dashboardsToBeArchived { + dashboardsToBeDeletedForUser = append(dashboardsToBeDeletedForUser, dashboard.DashboardId) + } + slices.Sort(dashboardsToBeDeletedForUser) + dashboardsToBeDeletedForUser = dashboardsToBeDeletedForUser[:len(dashboardsToBeDeletedForUser)-archivedLimit] + dashboardsToBeDeleted = append(dashboardsToBeDeleted, dashboardsToBeDeletedForUser...) + } + } + + // Remove dashboards that should be deleted from the to be archived list + dashboardsToBeDeletedMap := utils.SliceToMap(dashboardsToBeDeleted) + n := len(dashboardsToBeArchived) + for i := 0; i < n; { + if _, ok := dashboardsToBeDeletedMap[dashboardsToBeArchived[i].DashboardId]; ok { + // Remove the element by shifting the last element to the current index + dashboardsToBeArchived[i] = dashboardsToBeArchived[n-1] + n-- + } else { + i++ + } + } + dashboardsToBeArchived = dashboardsToBeArchived[:n] + + // Archive dashboards + if len(dashboardsToBeArchived) > 0 { + err = a.das.UpdateValidatorDashboardsArchiving(ctx, dashboardsToBeArchived) + if err != nil { + return err + } + } + + // Delete dashboards + if len(dashboardsToBeDeleted) > 0 { + err = a.das.RemoveValidatorDashboards(ctx, dashboardsToBeDeleted) + if err != nil { + return err + } + } + + return nil +} diff --git a/backend/pkg/commons/db/db.go b/backend/pkg/commons/db/db.go index ee046b57d..bc1489514 100644 --- a/backend/pkg/commons/db/db.go +++ b/backend/pkg/commons/db/db.go @@ -117,7 +117,7 @@ func MustInitDB(writer *types.DatabaseConfig, reader *types.DatabaseConfig, driv } } - log.Infof("connecting to %s database %s:%s/%s as writer with %d/%d max open/idle connections", databaseBrand, writer.Host, writer.Port, writer.Name, writer.MaxOpenConns, writer.MaxIdleConns) + log.Debugf("connecting to %s database %s:%s/%s as writer with %d/%d max open/idle connections", databaseBrand, writer.Host, writer.Port, writer.Name, writer.MaxOpenConns, writer.MaxIdleConns) dbConnWriter, err := sqlx.Open(driverName, fmt.Sprintf("%s://%s:%s@%s/%s?%s", databaseBrand, writer.Username, writer.Password, net.JoinHostPort(writer.Host, writer.Port), writer.Name, sslParam)) if err != nil { log.Fatal(err, "error getting Connection Writer database", 0) @@ -147,7 +147,7 @@ func MustInitDB(writer *types.DatabaseConfig, reader *types.DatabaseConfig, driv } } - log.Infof("connecting to %s database %s:%s/%s as reader with %d/%d max open/idle connections", databaseBrand, reader.Host, reader.Port, reader.Name, reader.MaxOpenConns, reader.MaxIdleConns) + log.Debugf("connecting to %s database %s:%s/%s as reader with %d/%d max open/idle connections", databaseBrand, reader.Host, reader.Port, reader.Name, reader.MaxOpenConns, reader.MaxIdleConns) dbConnReader, err := sqlx.Open(driverName, fmt.Sprintf("%s://%s:%s@%s/%s?%s", databaseBrand, reader.Username, reader.Password, net.JoinHostPort(reader.Host, reader.Port), reader.Name, sslParam)) if err != nil { log.Fatal(err, "error getting Connection Reader database", 0) diff --git a/backend/pkg/commons/metrics/metrics.go b/backend/pkg/commons/metrics/metrics.go index 7e65d28fd..2470124bb 100644 --- a/backend/pkg/commons/metrics/metrics.go +++ b/backend/pkg/commons/metrics/metrics.go @@ -129,7 +129,7 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) { } // Serve serves prometheus metrics on the given address under /metrics -func Serve(addr string, servePprof bool) error { +func Serve(addr string, servePprof bool, enableExtraPprof bool) error { router := http.NewServeMux() router.Handle("/metrics", promhttp.Handler()) router.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -147,9 +147,12 @@ func Serve(addr string, servePprof bool) error { if servePprof { log.Printf("serving pprof on %v/debug/pprof/", addr) - // enable some more aggressive pprof - runtime.SetBlockProfileRate(1) - runtime.SetMutexProfileFraction(1) + if enableExtraPprof { + // enables some extra pprof endpoints + runtime.SetCPUProfileRate(1) + runtime.SetBlockProfileRate(1) + runtime.SetMutexProfileFraction(1) + } router.HandleFunc("/debug/pprof/", pprof.Index) router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) router.HandleFunc("/debug/pprof/profile", pprof.Profile) diff --git a/backend/pkg/commons/types/config.go b/backend/pkg/commons/types/config.go index a9e7dbf59..5518cb131 100644 --- a/backend/pkg/commons/types/config.go +++ b/backend/pkg/commons/types/config.go @@ -271,9 +271,10 @@ type Config struct { MainCurrency string `yaml:"mainCurrency" envconfig:"FRONTEND_MAIN_CURRENCY"` } `yaml:"frontend"` Metrics struct { - Enabled bool `yaml:"enabled" envconfig:"METRICS_ENABLED"` - Address string `yaml:"address" envconfig:"METRICS_ADDRESS"` - Pprof bool `yaml:"pprof" envconfig:"METRICS_PPROF"` + Enabled bool `yaml:"enabled" envconfig:"METRICS_ENABLED"` + Address string `yaml:"address" envconfig:"METRICS_ADDRESS"` + Pprof bool `yaml:"pprof" envconfig:"METRICS_PPROF"` + PprofExtra bool `yaml:"pprofExtra" envconfig:"METRICS_PPROF_EXTRA"` } `yaml:"metrics"` Notifications struct { UserDBNotifications bool `yaml:"userDbNotifications" envconfig:"USERDB_NOTIFICATIONS_ENABLED"` diff --git a/backend/pkg/commons/utils/config.go b/backend/pkg/commons/utils/config.go index 04e813e62..1cc3179eb 100644 --- a/backend/pkg/commons/utils/config.go +++ b/backend/pkg/commons/utils/config.go @@ -478,9 +478,22 @@ func setCLConfig(cfg *types.Config) error { cfg.Chain.ClConfig = *chainConfig } - // Set log level based on environment variable - if strings.ToLower(os.Getenv("LOG_LEVEL")) == "debug" { + // rewrite to match to allow trace as well + switch strings.ToLower(os.Getenv("LOG_LEVEL")) { + case "trace": + logrus.SetLevel(logrus.TraceLevel) + case "debug": logrus.SetLevel(logrus.DebugLevel) + case "info": + logrus.SetLevel(logrus.InfoLevel) + case "warn": + logrus.SetLevel(logrus.WarnLevel) + case "error": + logrus.SetLevel(logrus.ErrorLevel) + case "fatal": + logrus.SetLevel(logrus.FatalLevel) + case "panic": + logrus.SetLevel(logrus.PanicLevel) } return nil diff --git a/backend/pkg/monitoring/services/clickhouse_epoch.go b/backend/pkg/monitoring/services/clickhouse_epoch.go index ea3ee344f..8df5ed8dc 100644 --- a/backend/pkg/monitoring/services/clickhouse_epoch.go +++ b/backend/pkg/monitoring/services/clickhouse_epoch.go @@ -46,7 +46,7 @@ func (s *ServiceClickhouseEpoch) runChecks() { } log.Tracef("checking clickhouse epoch") // context with deadline - ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second) defer cancel() var t time.Time err := db.ClickHouseReader.GetContext(ctx, &t, "SELECT MAX(epoch_timestamp) FROM validator_dashboard_data_epoch") diff --git a/backend/pkg/monitoring/services/clickhouse_rollings.go b/backend/pkg/monitoring/services/clickhouse_rollings.go index 52fbd9f6b..259bf5a44 100644 --- a/backend/pkg/monitoring/services/clickhouse_rollings.go +++ b/backend/pkg/monitoring/services/clickhouse_rollings.go @@ -8,6 +8,7 @@ import ( "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" ) @@ -64,32 +65,37 @@ func (s *ServiceClickhouseRollings) runChecks() { } log.Tracef("checking clickhouse rolling %s", rolling) // context with deadline - ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second) defer cancel() - var delta uint64 - err := db.ClickHouseReader.GetContext(ctx, &delta, fmt.Sprintf(` + var tsEpochTable time.Time + err := db.ClickHouseReader.GetContext(ctx, &tsEpochTable, ` SELECT - coalesce(( - SELECT - max(epoch) - FROM holesky.validator_dashboard_data_epoch - WHERE - epoch_timestamp = ( - SELECT - max(epoch_timestamp) - FROM holesky.validator_dashboard_data_epoch)) - MAX(epoch_end), 255) AS delta - FROM - holesky.validator_dashboard_data_rolling_%s - WHERE - validator_index = 0`, rolling)) + max(epoch_timestamp) + FROM holesky.validator_dashboard_data_epoch`, + ) if err != nil { r(constants.Failure, map[string]string{"error": err.Error()}) return } + var epochRollingTable uint64 + err = db.ClickHouseReader.GetContext(ctx, &epochRollingTable, fmt.Sprintf(` + SELECT + max(epoch_end) + FROM holesky.validator_dashboard_data_rolling_%s`, + rolling, + ), + ) + if err != nil { + r(constants.Failure, map[string]string{"error": err.Error()}) + return + } + // convert to timestamp + tsRollingTable := utils.EpochToTime(epochRollingTable) + threshold := 30 * time.Minute + delta := tsEpochTable.Sub(tsRollingTable) // check if delta is out of bounds - threshold := 4 - md := map[string]string{"delta": fmt.Sprintf("%d", delta), "threshold": fmt.Sprintf("%d", threshold)} - if delta > uint64(threshold) { + md := map[string]string{"delta": delta.String(), "threshold": threshold.String()} + if delta > threshold { md["error"] = fmt.Sprintf("delta is over threshold %d", threshold) r(constants.Failure, md) return diff --git a/backend/pkg/monitoring/services/db_connections.go b/backend/pkg/monitoring/services/db_connections.go index 582618633..e0fe180b8 100644 --- a/backend/pkg/monitoring/services/db_connections.go +++ b/backend/pkg/monitoring/services/db_connections.go @@ -87,7 +87,7 @@ func (s *ServerDbConnections) checkDBConnections() { defer wg.Done() log.Tracef("checking db connection for %s", entry.ID) // context with deadline - ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second) defer cancel() r := NewStatusReport(entry.ID, constants.Default, 10*time.Second) switch edb := entry.DB.(type) { diff --git a/backend/pkg/monitoring/services/timeout_detector.go b/backend/pkg/monitoring/services/timeout_detector.go index a485c28d0..720bb80dc 100644 --- a/backend/pkg/monitoring/services/timeout_detector.go +++ b/backend/pkg/monitoring/services/timeout_detector.go @@ -97,7 +97,7 @@ func (s *ServiceTimeoutDetector) runChecks() { where status = 'running' and timeouts_at < now() ORDER BY event_id ASC, inserted_at DESC` // context with deadline - ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + ctx, cancel := context.WithTimeout(s.ctx, 15*time.Second) defer cancel() var victims []struct { EventID string `db:"event_id"` diff --git a/frontend/assets/css/prime.scss b/frontend/assets/css/prime.scss index 7d69b0607..1e80ebaa3 100644 --- a/frontend/assets/css/prime.scss +++ b/frontend/assets/css/prime.scss @@ -597,6 +597,7 @@ div.p-accordion { align-items: center; gap: var(--padding); border: none; + justify-content: flex-start; .p-accordionheader-toggle-icon { display: none; } @@ -627,3 +628,17 @@ div.p-accordion { } } } + + +/** + * TODO: remove the .p-overflow-hidden and .p-overlay-mask class when PrimeVue is updated. + * This is quick-fix for shifting display issues. + **/ + .p-overflow-hidden { + overflow: hidden !important; /* Block scroll */ + border-right: solid 5px transparent !important; +} + +.p-overlay-mask { + background: var(--container-background); +} diff --git a/frontend/components/bc/searchbar/SearchbarMain.vue b/frontend/components/bc/searchbar/SearchbarMain.vue index d19c6292b..587c513df 100644 --- a/frontend/components/bc/searchbar/SearchbarMain.vue +++ b/frontend/components/bc/searchbar/SearchbarMain.vue @@ -845,6 +845,7 @@ function informationIfHiddenResults(): string { :class="[barShape, colorTheme]" type="text" :placeholder="t(SearchbarPurposeInfo[barPurpose].placeHolder)" + autocomplete="off" @keyup="(e) => handleKeyPressInTextField(e.key)" @focus="globalState.showDropdown = true" > diff --git a/frontend/components/dashboard/DashboardShareCodeModal.vue b/frontend/components/dashboard/DashboardShareCodeModal.vue index 64a8e51d5..1f0e5e1bd 100644 --- a/frontend/components/dashboard/DashboardShareCodeModal.vue +++ b/frontend/components/dashboard/DashboardShareCodeModal.vue @@ -34,9 +34,7 @@ const isShared = computed(() => isSharedKey(sharedKey.value)) const path = computed(() => { const newRoute = router.resolve({ name: 'dashboard-id', - query: { - validators: fromBase64Url(sharedKey.value ?? ''), - }, + params: { id: sharedKey.value }, }) return url.origin + newRoute.fullPath }) diff --git a/frontend/components/dashboard/ValidatorEpochDutiesModal.vue b/frontend/components/dashboard/ValidatorEpochDutiesModal.vue index 3d54c7e37..cba3e9356 100644 --- a/frontend/components/dashboard/ValidatorEpochDutiesModal.vue +++ b/frontend/components/dashboard/ValidatorEpochDutiesModal.vue @@ -2,7 +2,7 @@ import type { DataTableSortEvent } from 'primevue/datatable' import type { DashboardKey } from '~/types/dashboard' import type { Cursor } from '~/types/datatable' -import type { InternalGetValidatorDashboardDutiesResponse } from '~/types/api/validator_dashboard' +import type { GetValidatorDashboardDutiesResponse } from '~/types/api/validator_dashboard' import type { ValidatorHistoryDuties } from '~/types/api/common' import type { PathValues } from '~/types/customFetch' import { API_PATH } from '~/types/customFetch' @@ -46,7 +46,7 @@ const { 500, ) -const data = ref() +const data = ref() const onSort = (sort: DataTableSortEvent) => { setQuery(setQuerySort(sort, query?.value)) @@ -70,7 +70,7 @@ const loadData = async () => { if (props.value?.dashboardKey) { isLoading.value = !data.value const testQ = JSON.stringify(query.value) - const result = await fetch( + const result = await fetch( API_PATH.DASHBOARD_VALIDATOR_EPOCH_DUTY, { query: { diff --git a/frontend/components/dashboard/ValidatorManagementModal.vue b/frontend/components/dashboard/ValidatorManagementModal.vue index e93e59b08..f64946de6 100644 --- a/frontend/components/dashboard/ValidatorManagementModal.vue +++ b/frontend/components/dashboard/ValidatorManagementModal.vue @@ -13,7 +13,7 @@ import { } from '#components' import { useValidatorDashboardOverviewStore } from '~/stores/dashboard/useValidatorDashboardOverviewStore' import type { - InternalGetValidatorDashboardValidatorsResponse, + GetValidatorDashboardValidatorsResponse, VDBManageValidatorsTableRow, VDBPostValidatorsData, } from '~/types/api/validator_dashboard' @@ -72,7 +72,7 @@ const { value: query, } = useDebounceValue(initialQuery, 500) -const data = ref() +const data = ref() const selected = ref() const searchBar = ref() const hasNoOpenDialogs = ref(true) @@ -152,10 +152,10 @@ const removeValidators = async (validators?: NumberOrString[]) => { } await fetch( - API_PATH.DASHBOARD_VALIDATOR_MANAGEMENT, + API_PATH.DASHBOARD_VALIDATOR_MANAGEMENT_DELETE, { - method: 'DELETE', - query: { validators: validators.join(',') }, + body: JSON.stringify({ validators }), + method: 'POST', }, { dashboardKey: dashboardKey.value }, ) @@ -265,7 +265,7 @@ watch(selectedGroup, (value) => { const loadData = async () => { if (dashboardKey.value) { const testQ = JSON.stringify(query.value) - const result = await fetch( + const result = await fetch( API_PATH.DASHBOARD_VALIDATOR_MANAGEMENT, undefined, { dashboardKey: dashboardKey.value }, diff --git a/frontend/components/dashboard/chart/RewardsChart.vue b/frontend/components/dashboard/chart/RewardsChart.vue index 6f8fcb194..5b0925655 100644 --- a/frontend/components/dashboard/chart/RewardsChart.vue +++ b/frontend/components/dashboard/chart/RewardsChart.vue @@ -22,7 +22,7 @@ import { getRewardChartColors, getRewardsChartLineColor, } from '~/utils/colors' -import { type InternalGetValidatorDashboardRewardsChartResponse } from '~/types/api/validator_dashboard' +import { type GetValidatorDashboardRewardsChartResponse } from '~/types/api/validator_dashboard' import { type ChartData } from '~/types/api/common' import { type RewardChartGroupData, @@ -73,7 +73,7 @@ useAsyncData( return } isLoading.value = true - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_VALIDATOR_REWARDS_CHART, undefined, { dashboardKey: dashboardKey.value }, diff --git a/frontend/components/dashboard/chart/SummaryChart.vue b/frontend/components/dashboard/chart/SummaryChart.vue index 7f255c095..927468a3b 100644 --- a/frontend/components/dashboard/chart/SummaryChart.vue +++ b/frontend/components/dashboard/chart/SummaryChart.vue @@ -20,7 +20,7 @@ import { getChartTooltipBackgroundColor, getSummaryChartGroupColors, } from '~/utils/colors' -import { type InternalGetValidatorDashboardSummaryChartResponse } from '~/types/api/validator_dashboard' +import { type GetValidatorDashboardSummaryChartResponse } from '~/types/api/validator_dashboard' import { getGroupLabel } from '~/utils/dashboard/group' import { formatTsToTime } from '~/utils/format' import { API_PATH } from '~/types/customFetch' @@ -173,7 +173,7 @@ const loadData = async () => { isLoading.value = true const newSeries: SeriesObject[] = [] try { - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_SUMMARY_CHART, { query: { diff --git a/frontend/components/dashboard/validator/subset/ValidatorSubsetModal.vue b/frontend/components/dashboard/validator/subset/ValidatorSubsetModal.vue index 6817b10d6..539f9f71b 100644 --- a/frontend/components/dashboard/validator/subset/ValidatorSubsetModal.vue +++ b/frontend/components/dashboard/validator/subset/ValidatorSubsetModal.vue @@ -14,7 +14,7 @@ import type { import { sortSummaryValidators } from '~/utils/dashboard/validator' import { API_PATH } from '~/types/customFetch' import { - type InternalGetValidatorDashboardSummaryValidatorsResponse, + type GetValidatorDashboardSummaryValidatorsResponse, type VDBGroupSummaryData, type VDBSummaryTableRow, type VDBSummaryValidator, @@ -84,7 +84,7 @@ watch( } const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_VALIDATOR_INDICES, { query: { diff --git a/frontend/pages/playground.vue b/frontend/pages/playground.vue index 8bf23d5cc..f8895143d 100644 --- a/frontend/pages/playground.vue +++ b/frontend/pages/playground.vue @@ -9,7 +9,7 @@ import { } from '#components' import { useLatestStateStore } from '~/stores/useLatestStateStore' import { - type InternalGetValidatorDashboardSlotVizResponse, + type GetValidatorDashboardSlotVizResponse, type SlotVizEpoch, } from '~/types/api/slot_viz' import { formatNumber } from '~/utils/format' @@ -28,7 +28,7 @@ const { refreshOverview } = useValidatorDashboardOverviewStore() await Promise.all([ useAsyncData('latest_state', () => refreshLatestState()), useAsyncData('test_slot_viz_data', async () => { - const res = await $fetch( + const res = await $fetch( './mock/dashboard/slotViz.json', ) slotVizData.value = res.data @@ -39,7 +39,7 @@ await Promise.all([ ]) onMounted(async () => { - const res = await $fetch( + const res = await $fetch( './mock/dashboard/slotViz.json', ) slotVizData.value = res.data diff --git a/frontend/stores/dashboard/useValidatorDashboardBlocksStore.ts b/frontend/stores/dashboard/useValidatorDashboardBlocksStore.ts index 430745036..552496899 100644 --- a/frontend/stores/dashboard/useValidatorDashboardBlocksStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardBlocksStore.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import type { InternalGetValidatorDashboardBlocksResponse } from '~/types/api/validator_dashboard' +import type { GetValidatorDashboardBlocksResponse } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' import { API_PATH } from '~/types/customFetch' @@ -7,7 +7,7 @@ import { API_PATH } from '~/types/customFetch' const validatorDashboardBlocksStore = defineStore( 'validator_dashboard_blocks_store', () => { - const data = ref() + const data = ref() const query = ref() return { @@ -41,7 +41,7 @@ export function useValidatorDashboardBlocksStore() { } isLoading.value = true storedQuery.value = query - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_VALIDATOR_BLOCKS, undefined, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorDashboardClDepositsStore.ts b/frontend/stores/dashboard/useValidatorDashboardClDepositsStore.ts index aa65b151f..27390c344 100644 --- a/frontend/stores/dashboard/useValidatorDashboardClDepositsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardClDepositsStore.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardConsensusLayerDepositsResponse, - InternalGetValidatorDashboardTotalConsensusDepositsResponse, + GetValidatorDashboardConsensusLayerDepositsResponse, + GetValidatorDashboardTotalConsensusDepositsResponse, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' @@ -11,7 +11,7 @@ const validatorDashboardClDepositsStore = defineStore( 'validator_dashboard_cl_deposits_store', () => { const data - = ref() + = ref() const total = ref() const query = ref() @@ -50,7 +50,7 @@ export function useValidatorDashboardClDepositsStore() { storedQuery.value = query isLoadingDeposits.value = true const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_CL_DEPOSITS, undefined, { dashboardKey }, @@ -74,7 +74,7 @@ export function useValidatorDashboardClDepositsStore() { } isLoadingTotal.value = true const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_CL_DEPOSITS_TOTAL, undefined, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorDashboardElDepositsStore.ts b/frontend/stores/dashboard/useValidatorDashboardElDepositsStore.ts index fde440ebd..5326e5cca 100644 --- a/frontend/stores/dashboard/useValidatorDashboardElDepositsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardElDepositsStore.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardExecutionLayerDepositsResponse, - InternalGetValidatorDashboardTotalExecutionDepositsResponse, + GetValidatorDashboardExecutionLayerDepositsResponse, + GetValidatorDashboardTotalExecutionDepositsResponse, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' @@ -11,7 +11,7 @@ const validatorDashboardElDepositsStore = defineStore( 'validator_dashboard_el_deposits_store', () => { const data - = ref() + = ref() const total = ref() const query = ref() @@ -50,7 +50,7 @@ export function useValidatorDashboardElDepositsStore() { storedQuery.value = query isLoadingDeposits.value = true const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_EL_DEPOSITS, undefined, { dashboardKey }, @@ -74,7 +74,7 @@ export function useValidatorDashboardElDepositsStore() { } isLoadingTotal.value = true const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_EL_DEPOSITS_TOTAL, undefined, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorDashboardOverviewStore.ts b/frontend/stores/dashboard/useValidatorDashboardOverviewStore.ts index a94480747..54ed02295 100644 --- a/frontend/stores/dashboard/useValidatorDashboardOverviewStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardOverviewStore.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { useAllValidatorDashboardRewardsDetailsStore } from './useValidatorDashboardRewardsDetailsStore' import type { - InternalGetValidatorDashboardResponse, + GetValidatorDashboardResponse, VDBOverviewData, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' @@ -26,7 +26,7 @@ export function useValidatorDashboardOverviewStore() { return } try { - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_OVERVIEW, undefined, { dashboardKey: key }, diff --git a/frontend/stores/dashboard/useValidatorDashboardRewardsDetailsStore.ts b/frontend/stores/dashboard/useValidatorDashboardRewardsDetailsStore.ts index 2a6fdc4bf..95fa3cdc8 100644 --- a/frontend/stores/dashboard/useValidatorDashboardRewardsDetailsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardRewardsDetailsStore.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardGroupRewardsResponse, + GetValidatorDashboardGroupRewardsResponse, VDBGroupRewardsData, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' @@ -32,7 +32,7 @@ export const useValidatorDashboardRewardsDetailsStore = ( if (data.value[getKey()]) { return data.value[getKey()] } - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_VALIDATOR_REWARDS_DETAILS, undefined, { diff --git a/frontend/stores/dashboard/useValidatorDashboardRewardsStore.ts b/frontend/stores/dashboard/useValidatorDashboardRewardsStore.ts index 94640bd07..506e96389 100644 --- a/frontend/stores/dashboard/useValidatorDashboardRewardsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardRewardsStore.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import type { InternalGetValidatorDashboardRewardsResponse } from '~/types/api/validator_dashboard' +import type { GetValidatorDashboardRewardsResponse } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' import { DAHSHBOARDS_NEXT_EPOCH_ID } from '~/types/dashboard' @@ -8,7 +8,7 @@ import { API_PATH } from '~/types/customFetch' const validatorDashboardRewardsStore = defineStore( 'validator_dashboard_rewards_store', () => { - const data = ref() + const data = ref() const query = ref() return { @@ -44,7 +44,7 @@ export function useValidatorDashboardRewardsStore() { } isLoading.value = true storedQuery.value = query - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_VALIDATOR_REWARDS, undefined, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorDashboardSummaryDetailsStore.ts b/frontend/stores/dashboard/useValidatorDashboardSummaryDetailsStore.ts index aab2b2533..3a908dc38 100644 --- a/frontend/stores/dashboard/useValidatorDashboardSummaryDetailsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardSummaryDetailsStore.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardGroupSummaryResponse, + GetValidatorDashboardGroupSummaryResponse, VDBGroupSummaryData, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' @@ -42,7 +42,7 @@ export function useValidatorDashboardSummaryDetailsStore( data.value = {} storeTimeFrame.value = timeFrame } - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_SUMMARY_DETAILS, { query: { period: timeFrame } }, { diff --git a/frontend/stores/dashboard/useValidatorDashboardSummaryStore.ts b/frontend/stores/dashboard/useValidatorDashboardSummaryStore.ts index 1d0c44502..81ea17a75 100644 --- a/frontend/stores/dashboard/useValidatorDashboardSummaryStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardSummaryStore.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import type { InternalGetValidatorDashboardSummaryResponse } from '~/types/api/validator_dashboard' +import type { GetValidatorDashboardSummaryResponse } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' import { API_PATH } from '~/types/customFetch' @@ -8,7 +8,7 @@ import type { SummaryTimeFrame } from '~/types/dashboard/summary' const validatorDashboardSummaryStore = defineStore( 'validator_dashboard_sumary_store', () => { - const data = ref() + const data = ref() const query = ref() return { @@ -45,7 +45,7 @@ export function useValidatorDashboardSummaryStore() { isLoading.value = true storedQuery.value = query - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_SUMMARY, { query: { period: timeFrame } }, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorDashboardWithdrawalsStore.ts b/frontend/stores/dashboard/useValidatorDashboardWithdrawalsStore.ts index 98df02667..f76b3d90a 100644 --- a/frontend/stores/dashboard/useValidatorDashboardWithdrawalsStore.ts +++ b/frontend/stores/dashboard/useValidatorDashboardWithdrawalsStore.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardTotalWithdrawalsResponse, - InternalGetValidatorDashboardWithdrawalsResponse, + GetValidatorDashboardTotalWithdrawalsResponse, + GetValidatorDashboardWithdrawalsResponse, } from '~/types/api/validator_dashboard' import type { DashboardKey } from '~/types/dashboard' import type { TableQueryParams } from '~/types/datatable' @@ -10,7 +10,7 @@ import { API_PATH } from '~/types/customFetch' const validatorDashboardWithdrawalsStore = defineStore( 'validator_dashboard_withdrawals', () => { - const data = ref() + const data = ref() const total = ref() const query = ref() @@ -49,7 +49,7 @@ export function useValidatorDashboardWithdrawalsStore() { storedQuery.value = query isLoadingWithdrawals.value = true - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_VALIDATOR_WITHDRAWALS, undefined, { dashboardKey }, @@ -74,7 +74,7 @@ export function useValidatorDashboardWithdrawalsStore() { isLoadingTotal.value = true const res - = await fetch( + = await fetch( API_PATH.DASHBOARD_VALIDATOR_TOTAL_WITHDRAWALS, undefined, { dashboardKey }, diff --git a/frontend/stores/dashboard/useValidatorSlotVizStore.ts b/frontend/stores/dashboard/useValidatorSlotVizStore.ts index c1e825ea4..2d3f76ee2 100644 --- a/frontend/stores/dashboard/useValidatorSlotVizStore.ts +++ b/frontend/stores/dashboard/useValidatorSlotVizStore.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import type { - InternalGetValidatorDashboardSlotVizResponse, + GetValidatorDashboardSlotVizResponse, SlotVizEpoch, } from '~/types/api/slot_viz' import type { DashboardKey } from '~/types/dashboard' @@ -22,7 +22,7 @@ export function useValidatorSlotVizStore() { if (groups?.length) { query = { group_ids: groups.join(',') } } - const res = await fetch( + const res = await fetch( API_PATH.DASHBOARD_SLOTVIZ, { headers: {}, diff --git a/frontend/types/api/slot_viz.ts b/frontend/types/api/slot_viz.ts index 3bc44e3c1..7d9bbc4f1 100644 --- a/frontend/types/api/slot_viz.ts +++ b/frontend/types/api/slot_viz.ts @@ -46,4 +46,4 @@ export interface SlotVizEpoch { progress?: number /* float64 */; // only on landing page slots?: VDBSlotVizSlot[]; // only on dashboard page } -export type InternalGetValidatorDashboardSlotVizResponse = ApiDataResponse; +export type GetValidatorDashboardSlotVizResponse = ApiDataResponse; diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts index 4158ecc0c..af31ad96f 100644 --- a/frontend/types/api/validator_dashboard.ts +++ b/frontend/types/api/validator_dashboard.ts @@ -36,7 +36,7 @@ export interface VDBOverviewData { chart_history_seconds: ChartHistorySeconds; balances: VDBOverviewBalances; } -export type InternalGetValidatorDashboardResponse = ApiDataResponse; +export type GetValidatorDashboardResponse = ApiDataResponse; export interface VDBPostArchivingReturnData { id: number /* uint64 */; is_archived: boolean; @@ -61,7 +61,7 @@ export interface VDBSummaryTableRow { proposals: StatusCount; reward: ClElValue; } -export type InternalGetValidatorDashboardSummaryResponse = ApiPagingResponse; +export type GetValidatorDashboardSummaryResponse = ApiPagingResponse; export interface VDBGroupSummaryColumnItem { status_count: StatusCount; validators?: number /* uint64 */[]; @@ -94,8 +94,8 @@ export interface VDBGroupSummaryData { collateral: number /* float64 */; }; } -export type InternalGetValidatorDashboardGroupSummaryResponse = ApiDataResponse; -export type InternalGetValidatorDashboardSummaryChartResponse = ApiDataResponse>; // line chart, series id is group id +export type GetValidatorDashboardGroupSummaryResponse = ApiDataResponse; +export type GetValidatorDashboardSummaryChartResponse = ApiDataResponse>; // line chart, series id is group id /** * ------------------------------------------------------------ * Summary Validators @@ -108,7 +108,7 @@ export interface VDBSummaryValidatorsData { category: 'deposited' | 'online' | 'offline' | 'slashing' | 'slashed' | 'exited' | 'withdrawn' | 'pending' | 'exiting' | 'withdrawing' | 'sync_current' | 'sync_upcoming' | 'sync_past' | 'has_slashed' | 'got_slashed' | 'proposal_proposed' | 'proposal_missed'; validators: VDBSummaryValidator[]; } -export type InternalGetValidatorDashboardSummaryValidatorsResponse = ApiDataResponse; +export type GetValidatorDashboardSummaryValidatorsResponse = ApiDataResponse; /** * ------------------------------------------------------------ * Rewards Tab @@ -125,7 +125,7 @@ export interface VDBRewardsTableRow { group_id: number /* int64 */; reward: ClElValue; } -export type InternalGetValidatorDashboardRewardsResponse = ApiPagingResponse; +export type GetValidatorDashboardRewardsResponse = ApiPagingResponse; export interface VDBGroupRewardsDetails { status_count: StatusCount; income: string /* decimal.Decimal */; @@ -143,13 +143,13 @@ export interface VDBGroupRewardsData { proposal_cl_sync_inc_reward: string /* decimal.Decimal */; proposal_cl_slashing_inc_reward: string /* decimal.Decimal */; } -export type InternalGetValidatorDashboardGroupRewardsResponse = ApiDataResponse; -export type InternalGetValidatorDashboardRewardsChartResponse = ApiDataResponse>; // bar chart, series id is group id, property is 'el' or 'cl' +export type GetValidatorDashboardGroupRewardsResponse = ApiDataResponse; +export type GetValidatorDashboardRewardsChartResponse = ApiDataResponse>; // bar chart, series id is group id, property is 'el' or 'cl' export interface VDBEpochDutiesTableRow { validator: number /* uint64 */; duties: ValidatorHistoryDuties; } -export type InternalGetValidatorDashboardDutiesResponse = ApiPagingResponse; +export type GetValidatorDashboardDutiesResponse = ApiPagingResponse; /** * ------------------------------------------------------------ * Blocks Tab @@ -165,7 +165,7 @@ export interface VDBBlocksTableRow { reward?: ClElValue; graffiti?: string; } -export type InternalGetValidatorDashboardBlocksResponse = ApiPagingResponse; +export type GetValidatorDashboardBlocksResponse = ApiPagingResponse; export interface VDBHeatmapEvents { proposal: boolean; slash: boolean; @@ -183,7 +183,7 @@ export interface VDBHeatmap { data: VDBHeatmapCell[]; aggregation: 'epoch' | 'hourly' | 'daily' | 'weekly'; } -export type InternalGetValidatorDashboardHeatmapResponse = ApiDataResponse; +export type GetValidatorDashboardHeatmapResponse = ApiDataResponse; export interface VDBHeatmapTooltipData { timestamp: number /* int64 */; proposers: StatusCount; @@ -195,7 +195,7 @@ export interface VDBHeatmapTooltipData { attestation_income: string /* decimal.Decimal */; attestation_efficiency: number /* float64 */; } -export type InternalGetValidatorDashboardGroupHeatmapResponse = ApiDataResponse; +export type GetValidatorDashboardGroupHeatmapResponse = ApiDataResponse; /** * ------------------------------------------------------------ * Deposits Tab @@ -213,7 +213,7 @@ export interface VDBExecutionDepositsTableRow { amount: string /* decimal.Decimal */; valid: boolean; } -export type InternalGetValidatorDashboardExecutionLayerDepositsResponse = ApiPagingResponse; +export type GetValidatorDashboardExecutionLayerDepositsResponse = ApiPagingResponse; export interface VDBConsensusDepositsTableRow { public_key: PubKey; index: number /* uint64 */; @@ -224,15 +224,15 @@ export interface VDBConsensusDepositsTableRow { amount: string /* decimal.Decimal */; signature: Hash; } -export type InternalGetValidatorDashboardConsensusLayerDepositsResponse = ApiPagingResponse; +export type GetValidatorDashboardConsensusLayerDepositsResponse = ApiPagingResponse; export interface VDBTotalExecutionDepositsData { total_amount: string /* decimal.Decimal */; } -export type InternalGetValidatorDashboardTotalExecutionDepositsResponse = ApiDataResponse; +export type GetValidatorDashboardTotalExecutionDepositsResponse = ApiDataResponse; export interface VDBTotalConsensusDepositsData { total_amount: string /* decimal.Decimal */; } -export type InternalGetValidatorDashboardTotalConsensusDepositsResponse = ApiDataResponse; +export type GetValidatorDashboardTotalConsensusDepositsResponse = ApiDataResponse; /** * ------------------------------------------------------------ * Withdrawals Tab @@ -246,11 +246,11 @@ export interface VDBWithdrawalsTableRow { amount: string /* decimal.Decimal */; is_missing_estimate: boolean; } -export type InternalGetValidatorDashboardWithdrawalsResponse = ApiPagingResponse; +export type GetValidatorDashboardWithdrawalsResponse = ApiPagingResponse; export interface VDBTotalWithdrawalsData { total_amount: string /* decimal.Decimal */; } -export type InternalGetValidatorDashboardTotalWithdrawalsResponse = ApiDataResponse; +export type GetValidatorDashboardTotalWithdrawalsResponse = ApiDataResponse; /** * ------------------------------------------------------------ * Rocket Pool Tab @@ -282,8 +282,8 @@ export interface VDBRocketPoolTableRow { unclaimed: string /* decimal.Decimal */; }; } -export type InternalGetValidatorDashboardRocketPoolResponse = ApiPagingResponse; -export type InternalGetValidatorDashboardTotalRocketPoolResponse = ApiDataResponse; +export type GetValidatorDashboardRocketPoolResponse = ApiPagingResponse; +export type GetValidatorDashboardTotalRocketPoolResponse = ApiDataResponse; export interface VDBNodeRocketPoolData { timezone: string; refund_balance: string /* decimal.Decimal */; @@ -293,7 +293,7 @@ export interface VDBNodeRocketPoolData { max: string /* decimal.Decimal */; }; } -export type InternalGetValidatorDashboardNodeRocketPoolResponse = ApiDataResponse; +export type GetValidatorDashboardNodeRocketPoolResponse = ApiDataResponse; export interface VDBRocketPoolMinipoolsTableRow { node: Address; validator_index: number /* uint64 */; @@ -305,7 +305,7 @@ export interface VDBRocketPoolMinipoolsTableRow { created_timestamp: number /* int64 */; penalties: number /* uint64 */; } -export type InternalGetValidatorDashboardRocketPoolMinipoolsResponse = ApiPagingResponse; +export type GetValidatorDashboardRocketPoolMinipoolsResponse = ApiPagingResponse; /** * ------------------------------------------------------------ * Manage Modal @@ -319,7 +319,7 @@ export interface VDBManageValidatorsTableRow { queue_position?: number /* uint64 */; withdrawal_credential: Hash; } -export type InternalGetValidatorDashboardValidatorsResponse = ApiPagingResponse; +export type GetValidatorDashboardValidatorsResponse = ApiPagingResponse; /** * ------------------------------------------------------------ * Misc. diff --git a/frontend/types/customFetch.ts b/frontend/types/customFetch.ts index ee27339ce..dcc3ec474 100644 --- a/frontend/types/customFetch.ts +++ b/frontend/types/customFetch.ts @@ -30,6 +30,7 @@ export enum API_PATH { DASHBOARD_VALIDATOR_GROUPS = '/validator-dashboards/groups', DASHBOARD_VALIDATOR_INDICES = '/validator-dashboards/indices', DASHBOARD_VALIDATOR_MANAGEMENT = '/validator-dashboards/validators', + DASHBOARD_VALIDATOR_MANAGEMENT_DELETE = '/validator-dashboards/validators/bulk-deletions', DASHBOARD_VALIDATOR_REWARDS = '/dashboard/validatorRewards', DASHBOARD_VALIDATOR_REWARDS_CHART = '/dashboard/validatorRewardsChart', DASHBOARD_VALIDATOR_REWARDS_DETAILS = '/dashboard/validatorRewardsDetails', @@ -223,6 +224,12 @@ export const mapping: Record = { mock: false, path: 'validator-dashboards/{dashboard_id}/validators', }, + [API_PATH.DASHBOARD_VALIDATOR_MANAGEMENT_DELETE]: { + getPath: values => + `/validator-dashboards/${values?.dashboardKey}/validators/bulk-deletions`, + mock: false, + path: 'validator-dashboards/{dashboard_id}/validators/bulk-deletions', + }, [API_PATH.DASHBOARD_VALIDATOR_REWARDS]: { getPath: values => `/validator-dashboards/${values?.dashboardKey}/rewards`,