diff --git a/.golangci.yml b/.golangci.yml index e19b8f45..af356a95 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -83,7 +83,6 @@ linters: - ineffassign - noctx - typecheck - - tparallel - gofumpt - misspell - nilerr @@ -92,7 +91,8 @@ linters: - staticcheck - unused # todo enable after golangci-lint update to 1.63 -# - gosec + # - tparallel + # - gosec # - prealloc # - mnd # - govet diff --git a/Makefile b/Makefile index 13d32415..6abe9f27 100644 --- a/Makefile +++ b/Makefile @@ -60,11 +60,10 @@ generate: go generate ./... test: - ./bin/local-startup.sh; - go test -v -cover -p 1 ./... -count=1 + go test -v -cover ./... -count=1 test-integration: - go test -v -cover -tags=integration ./internal/indexer/db/... + go test -v -cover -tags=integration ./internal/indexer/db/ lint: golangci-lint run @@ -73,4 +72,12 @@ format: gofumpt -l -w . build-swagger: - swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/shared/api,internal/shared/types,internal/v1/api/handlers,internal/v2/api/handlers \ No newline at end of file + swag init --parseDependency --parseInternal -d cmd/staking-api-service,internal/shared/api,internal/shared/types,internal/v1/api/handlers,internal/v2/api/handlers + +# Runs end-to-end tests for API service +test-e2e: + go test -v -tags=e2e -coverprofile=cover.out -coverpkg=./internal/... ./tests/api/... + +# Opens a browser to check code coverage stats. Note that output of test-e2e (cover.out) is required +coverage: + go tool cover -html=cover.out \ No newline at end of file diff --git a/cmd/staking-api-service/main.go b/cmd/staking-api-service/main.go index fea05c76..f5c7d8f4 100644 --- a/cmd/staking-api-service/main.go +++ b/cmd/staking-api-service/main.go @@ -73,7 +73,7 @@ func main() { log.Fatal().Err(err).Msg(fmt.Sprintf("error while loading finality providers file: %s", finalityProvidersPath)) } - err = dbmodel.Setup(ctx, cfg) + err = dbmodel.Setup(ctx, cfg.StakingDb, cfg.ExternalAPIs) if err != nil { log.Fatal().Err(err).Msg("error while setting up staking db model") } diff --git a/internal/indexer/db/client/finality_provider.go b/internal/indexer/db/client/finality_provider.go index 0aa2075a..e6d0cd93 100644 --- a/internal/indexer/db/client/finality_provider.go +++ b/internal/indexer/db/client/finality_provider.go @@ -7,24 +7,6 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -// GetFinalityProviderByPk retrieves a single finality provider by their primary key -func (indexerdbclient *IndexerDatabase) GetFinalityProviderByPk( - ctx context.Context, - fpPk string, -) (*indexerdbmodel.IndexerFinalityProviderDetails, error) { - client := indexerdbclient.Client.Database( - indexerdbclient.DbName, - ).Collection(indexerdbmodel.FinalityProviderDetailsCollection) - - var result indexerdbmodel.IndexerFinalityProviderDetails - err := client.FindOne(ctx, bson.M{"_id": fpPk}).Decode(&result) - if err != nil { - return nil, err - } - - return &result, nil -} - // GetFinalityProviders retrieves finality providers filtered by state func (indexerdbclient *IndexerDatabase) GetFinalityProviders( ctx context.Context, diff --git a/internal/indexer/db/client/interface.go b/internal/indexer/db/client/interface.go index d4e171e4..df3dd0b4 100644 --- a/internal/indexer/db/client/interface.go +++ b/internal/indexer/db/client/interface.go @@ -16,7 +16,6 @@ type IndexerDBClient interface { GetBtcCheckpointParams(ctx context.Context) ([]*indexertypes.BtcCheckpointParams, error) // Finality Providers GetFinalityProviders(ctx context.Context) ([]*indexerdbmodel.IndexerFinalityProviderDetails, error) - GetFinalityProviderByPk(ctx context.Context, fpPk string) (*indexerdbmodel.IndexerFinalityProviderDetails, error) // Staker Delegations GetDelegation(ctx context.Context, stakingTxHashHex string) (*indexerdbmodel.IndexerDelegationDetails, error) GetDelegations(ctx context.Context, stakerPKHex string, stakerBabylonAddress *string, paginationToken string) (*db.DbResultMap[indexerdbmodel.IndexerDelegationDetails], error) diff --git a/internal/indexer/db/client/setup_test.go b/internal/indexer/db/client/setup_test.go index 4403c48a..31eb03b8 100644 --- a/internal/indexer/db/client/setup_test.go +++ b/internal/indexer/db/client/setup_test.go @@ -1,6 +1,5 @@ //go:build integration -// todo comment package indexerdbclient_test import ( diff --git a/internal/indexer/db/model/finality_providers.go b/internal/indexer/db/model/finality_providers.go index 669880d4..d8c6aa98 100644 --- a/internal/indexer/db/model/finality_providers.go +++ b/internal/indexer/db/model/finality_providers.go @@ -1,13 +1,5 @@ package indexerdbmodel -import ( - "encoding/json" - "fmt" - - dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" - "github.com/babylonlabs-io/staking-api-service/internal/shared/types" -) - type FinalityProviderState string const ( @@ -38,33 +30,3 @@ type IndexerFinalityProviderPagination struct { BtcPk string `json:"btc_pk"` Commission string `json:"commission"` } - -func BuildFinalityProviderPaginationToken(f IndexerFinalityProviderDetails) (string, error) { - page := &IndexerFinalityProviderPagination{ - BtcPk: f.BtcPk, - Commission: f.Commission, - } - token, err := dbmodel.GetPaginationToken(page) - if err != nil { - return "", err - } - - return token, nil -} - -func DecodeFinalityProviderPaginationToken(token string) (*IndexerFinalityProviderPagination, error) { - var pagination IndexerFinalityProviderPagination - err := json.Unmarshal([]byte(token), &pagination) - return &pagination, err -} - -func FromStringToFinalityProviderState(s string) (types.FinalityProviderQueryingState, error) { - switch s { - case "active": - return types.FinalityProviderStateActive, nil - case "standby": - return types.FinalityProviderStateStandby, nil - default: - return "", fmt.Errorf("invalid finality provider state: %s", s) - } -} diff --git a/internal/shared/api/handlers/handler/handler.go b/internal/shared/api/handlers/handler/handler.go index 02d3b6d9..fbe9359d 100644 --- a/internal/shared/api/handlers/handler/handler.go +++ b/internal/shared/api/handlers/handler/handler.go @@ -4,10 +4,8 @@ import ( "errors" "fmt" "net/http" - "regexp" "strings" - indexerdbmodel "github.com/babylonlabs-io/staking-api-service/internal/indexer/db/model" "github.com/babylonlabs-io/staking-api-service/internal/shared/bbnclient" "github.com/babylonlabs-io/staking-api-service/internal/shared/config" "github.com/babylonlabs-io/staking-api-service/internal/shared/services/service" @@ -201,29 +199,6 @@ func ParseBtcAddressesQuery( return addresses, nil } -// ParseStateFilterQuery parses the state filter query and returns the state enum -// If the state is not provided, it returns an empty string -func ParseStateFilterQuery( - r *http.Request, queryName string, -) ([]types.DelegationState, *types.Error) { - states := r.URL.Query()[queryName] - if len(states) == 0 { - return nil, nil - } - - var stateEnums []types.DelegationState - for _, state := range states { - stateEnum, err := types.FromStringToDelegationState(state) - if err != nil { - return nil, types.NewErrorWithMsg( - http.StatusBadRequest, types.BadRequest, err.Error(), - ) - } - stateEnums = append(stateEnums, stateEnum) - } - return stateEnums, nil -} - // ParseBooleanQuery parses the boolean query and returns the boolean value // If the boolean is not provided, it returns false // If the boolean is not valid, it returns an error @@ -246,52 +221,3 @@ func ParseBooleanQuery( } return value == "true", nil } - -func ParseFPSearchQuery(r *http.Request, queryName string, isOptional bool) (string, *types.Error) { - // max length of a public key in hex and the max length of a finality provider moniker is 64 - const maxSearchQueryLength = 64 - str := r.URL.Query().Get(queryName) - if str == "" { - if isOptional { - return "", nil - } - return "", types.NewErrorWithMsg( - http.StatusBadRequest, types.BadRequest, queryName+" is required", - ) - } - - if len(str) < 1 || len(str) > maxSearchQueryLength { - return "", types.NewErrorWithMsg( - http.StatusBadRequest, - types.BadRequest, - fmt.Sprintf("search query must be between 1 and %d characters", maxSearchQueryLength), - ) - } - - // check if the search query contains only printable ASCII characters - if !regexp.MustCompile(`^[\x20-\x7E]+$`).MatchString(str) { - return "", types.NewErrorWithMsg( - http.StatusBadRequest, - types.BadRequest, - fmt.Sprintf("%s contains invalid characters", queryName), - ) - } - - return str, nil -} - -func ParseFPStateQuery(r *http.Request, isOptional bool) (types.FinalityProviderQueryingState, *types.Error) { - state := r.URL.Query().Get("state") - if state == "" { - if isOptional { - return "", nil - } - } - stateEnum, err := indexerdbmodel.FromStringToFinalityProviderState(state) - if err != nil { - return "", types.NewErrorWithMsg( - http.StatusBadRequest, types.BadRequest, err.Error(), - ) - } - return stateEnum, nil -} diff --git a/internal/shared/api/server.go b/internal/shared/api/server.go index b2f193a9..1056c5ce 100644 --- a/internal/shared/api/server.go +++ b/internal/shared/api/server.go @@ -3,6 +3,7 @@ package api import ( "context" "fmt" + "net" "net/http" "github.com/babylonlabs-io/staking-api-service/internal/shared/api/handlers" @@ -18,6 +19,7 @@ type Server struct { httpServer *http.Server handlers *handlers.Handlers cfg *config.Config + listener net.Listener } func New( @@ -38,7 +40,6 @@ func New( r.Use(middlewares.ContentLengthMiddleware(cfg)) srv := &http.Server{ - Addr: fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port), WriteTimeout: cfg.Server.WriteTimeout, ReadTimeout: cfg.Server.ReadTimeout, IdleTimeout: cfg.Server.IdleTimeout, @@ -60,6 +61,23 @@ func New( } func (a *Server) Start() error { - log.Info().Msgf("Starting server on %s", a.httpServer.Addr) - return a.httpServer.ListenAndServe() + address := fmt.Sprintf("%s:%d", a.cfg.Server.Host, a.cfg.Server.Port) + + log.Info().Msgf("Starting server on %s", address) + var err error + a.listener, err = net.Listen("tcp", address) + if err != nil { + return err + } + + return a.httpServer.Serve(a.listener) +} + +func (a *Server) Stop() error { + log.Info().Msg("Stopping server") + return a.httpServer.Shutdown(context.TODO()) +} + +func (a *Server) Addr() string { + return a.listener.Addr().String() } diff --git a/internal/shared/db/model/setup.go b/internal/shared/db/model/setup.go index 59b81c7f..fffeb91c 100644 --- a/internal/shared/db/model/setup.go +++ b/internal/shared/db/model/setup.go @@ -71,12 +71,12 @@ var collections = map[string][]index{ }, } -func Setup(ctx context.Context, cfg *config.Config) error { +func Setup(ctx context.Context, stakingDB *config.DbConfig, externalConfig *config.ExternalAPIsConfig) error { credential := options.Credential{ - Username: cfg.StakingDb.Username, - Password: cfg.StakingDb.Password, + Username: stakingDB.Username, + Password: stakingDB.Password, } - clientOps := options.Client().ApplyURI(cfg.StakingDb.Address).SetAuth(credential) + clientOps := options.Client().ApplyURI(stakingDB.Address).SetAuth(credential) client, err := mongo.Connect(ctx, clientOps) if err != nil { return err @@ -87,7 +87,7 @@ func Setup(ctx context.Context, cfg *config.Config) error { defer cancel() // Access a database and create collections. - database := client.Database(cfg.StakingDb.DbName) + database := client.Database(stakingDB.DbName) // Create collections. for collection := range collections { @@ -101,8 +101,8 @@ func Setup(ctx context.Context, cfg *config.Config) error { } // If external APIs are configured, create TTL index for BTC price collection - if cfg.ExternalAPIs != nil { - if err := createTTLIndexes(ctx, database, cfg.ExternalAPIs.CoinMarketCap.CacheTTL); err != nil { + if externalConfig != nil { + if err := createTTLIndexes(ctx, database, externalConfig.CoinMarketCap.CacheTTL); err != nil { log.Error().Err(err).Msg("Failed to create TTL index for BTC price") return err } diff --git a/internal/shared/services/services.go b/internal/shared/services/services.go index acf79453..9db1a4bd 100644 --- a/internal/shared/services/services.go +++ b/internal/shared/services/services.go @@ -17,6 +17,7 @@ type Services struct { } func New(cfg *config.Config, globalParams *types.GlobalParams, finalityProviders []types.FinalityProviderDetails, clients *clients.Clients, dbClients *dbclients.DbClients) (*Services, error) { + // todo remove errors in service constructors (they are always nil) sharedService, err := service.New(cfg, globalParams, finalityProviders, clients, dbClients) if err != nil { return nil, err diff --git a/internal/shared/utils/utils.go b/internal/shared/utils/utils.go deleted file mode 100644 index 1d63bae5..00000000 --- a/internal/shared/utils/utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package utils - -import "encoding/json" - -// Contains checks if a slice contains a specific element. -// It uses type parameters to work with any slice type. -func Contains[T comparable](slice []T, element T) bool { - for _, item := range slice { - if item == element { - return true - } - } - return false -} - -// DeepCopy performs a deep copy of a struct. -func DeepCopy(src, dst interface{}) error { - // Marshal the source object to JSON. - data, err := json.Marshal(src) - if err != nil { - return err - } - - // Unmarshal the JSON data into the destination object. - if err := json.Unmarshal(data, dst); err != nil { - return err - } - - return nil -} diff --git a/internal/v2/api/handlers/address.go b/internal/v2/api/handlers/address.go index 4a54b4b3..4defdce8 100644 --- a/internal/v2/api/handlers/address.go +++ b/internal/v2/api/handlers/address.go @@ -21,7 +21,6 @@ type AddressScreeningResponse struct { // @Param btc_address query string true "BTC address to check" // @Success 200 {object} handler.PublicResponse[AddressScreeningResponse] "Risk of provided address" // @Failure 400 {object} types.Error "Error: Bad Request" -// @Failure 404 {object} types.Error "Error: Not Found" // @Failure 500 {object} types.Error "Error: Internal Server Error" // @Router /address/screening [get] func (h *V2Handler) AddressScreening(request *http.Request) (*handler.Result, *types.Error) { diff --git a/internal/v2/api/handlers/delegation.go b/internal/v2/api/handlers/delegation.go index ff3f33b9..de94eaec 100644 --- a/internal/v2/api/handlers/delegation.go +++ b/internal/v2/api/handlers/delegation.go @@ -54,7 +54,6 @@ func (h *V2Handler) GetDelegations(request *http.Request) (*handler.Result, *typ if err != nil { return nil, err } - bbnAddress, err := handler.ParseBabylonAddressQuery( request, "babylon_address", true, ) diff --git a/internal/v2/api/handlers/finality_provider.go b/internal/v2/api/handlers/finality_provider.go index dbe315dd..367f17b3 100644 --- a/internal/v2/api/handlers/finality_provider.go +++ b/internal/v2/api/handlers/finality_provider.go @@ -15,7 +15,6 @@ import ( // @Produce json // @Tags v2 // @Success 200 {object} handler.PublicResponse[[]v2service.FinalityProviderStatsPublic] "List of finality providers with its stats" -// @Failure 400 {object} types.Error "Invalid parameters or malformed request" // @Failure 404 {object} types.Error "No finality providers found" // @Failure 500 {object} types.Error "Internal server error occurred" // @Router /v2/finality-providers [get] diff --git a/internal/v2/service/delegation.go b/internal/v2/service/delegation.go index f9a4aa9f..758529ad 100644 --- a/internal/v2/service/delegation.go +++ b/internal/v2/service/delegation.go @@ -126,6 +126,7 @@ func (s *V2Service) GetDelegations( ctx, stakerPkHex, stakerBabylonAddress, paginationKey, ) if err != nil { + // todo this statement is not reachable if db.IsNotFoundError(err) { log.Ctx(ctx).Warn().Err(err).Str("stakingTxHashHex", stakerPkHex).Msg("Staking delegations not found") return nil, "", types.NewErrorWithMsg(http.StatusNotFound, types.NotFound, "staking delegation not found, please retry") diff --git a/internal/v2/service/network_test.go b/internal/v2/service/network_test.go index 753c27b6..1e607841 100644 --- a/internal/v2/service/network_test.go +++ b/internal/v2/service/network_test.go @@ -44,7 +44,6 @@ func TestGetNetworkInfo(t *testing.T) { resp, rpcErr := service.GetNetworkInfo(ctx) require.Nil(t, rpcErr) - // todo(Kirill) use Map functional flow once it's added var versions []uint32 for _, param := range resp.Params.Bbn { versions = append(versions, param.Version) diff --git a/internal/v2/service/staker.go b/internal/v2/service/staker.go index 84e206c8..dc5cd0ed 100644 --- a/internal/v2/service/staker.go +++ b/internal/v2/service/staker.go @@ -66,6 +66,7 @@ func (s *V2Service) GetStakerPublicKeysByAddresses( } else if addressType == utils.NativeSegwit { nativeSegwitAddresses = append(nativeSegwitAddresses, addr) } else { + // todo it's not possible (validation done at CheckBtcAddressType) replace with unreachable panic? return nil, types.NewErrorWithMsg( http.StatusBadRequest, types.BadRequest, "unsupported address type", ) diff --git a/tests/api/main_test.go b/tests/api/main_test.go new file mode 100644 index 00000000..2cc1524d --- /dev/null +++ b/tests/api/main_test.go @@ -0,0 +1,297 @@ +//go:build e2e + +package api + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/babylonlabs-io/babylon-staking-indexer/testutil" + "github.com/babylonlabs-io/staking-api-service/internal/shared/api" + "github.com/babylonlabs-io/staking-api-service/internal/shared/config" + dbclient "github.com/babylonlabs-io/staking-api-service/internal/shared/db/client" + dbclients "github.com/babylonlabs-io/staking-api-service/internal/shared/db/clients" + dbmodel "github.com/babylonlabs-io/staking-api-service/internal/shared/db/model" + "github.com/babylonlabs-io/staking-api-service/internal/shared/http/clients" + "github.com/babylonlabs-io/staking-api-service/internal/shared/observability/metrics" + "github.com/babylonlabs-io/staking-api-service/internal/shared/services" + "github.com/babylonlabs-io/staking-api-service/internal/shared/types" + "github.com/babylonlabs-io/staking-api-service/pkg" + "github.com/ory/dockertest" + dc "github.com/ory/dockertest/docker" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" +) + +const requestTimeout = 3 * time.Second + +var apiURL string + +func TestMain(t *testing.M) { + ctx := context.Background() + + db, err := setupDB(ctx) + if err != nil { + log.Fatal().Err(err).Msg("Failed to setup DB") + } + + cfg := &config.Config{ + StakingDb: db.stakingConfig, + IndexerDb: db.indexerConfig, + Server: &config.ServerConfig{ + LogLevel: "info", + MaxContentLength: 4096, + BTCNet: "mainnet", + // these values are not used in tests, but we keep them here so validation pass + Host: "127.0.0.1", + Port: 9999, + HealthCheckInterval: 1, + }, + AddressScreeningConfig: &config.AddressScreeningConfig{ + Enabled: true, + }, + } + + s, err := setupServices(ctx, cfg) + if err != nil { + db.cleanup() + log.Fatal().Err(err).Msg("Failed to setup services") + } + + srv, err := api.New(ctx, cfg, s) + if err != nil { + db.cleanup() + log.Fatal().Err(err).Msg("Failed to initialize api") + } + + metrics.Init(7777) + + go srv.Start() //nolint:errcheck + time.Sleep(time.Second) + _, port, err := net.SplitHostPort(srv.Addr()) + if err != nil { + db.cleanup() + log.Fatal().Err(err).Msg("Failed to parse server address") + } + apiURL = "http://localhost:" + port + defer srv.Stop() //nolint:errcheck + + // running tests + code := t.Run() + db.cleanup() + os.Exit(code) +} + +func setupServices(ctx context.Context, cfg *config.Config) (*services.Services, error) { + err := cfg.Server.Validate() + if err != nil { + return nil, err + } + + dbClients, err := dbclients.New(ctx, cfg) + if err != nil { + return nil, err + } + + fp, err := types.NewFinalityProviders("testdata/finality-providers.json") + if err != nil { + return nil, err + } + + globals, err := types.NewGlobalParams("testdata/global-params.json") + if err != nil { + return nil, err + } + + clients := clients.New(cfg) + return services.New(cfg, globals, fp, clients, dbClients) +} + +type db struct { + stakingConfig *config.DbConfig + indexerConfig *config.DbConfig + cleanup func() +} + +func setupDB(ctx context.Context) (*db, error) { + mongoCfg, cleanup, err := setupMongoContainer() + if err != nil { + return nil, err + } + + stakingConfig := createDbConfig(mongoCfg, "api") + indexerConfig := createDbConfig(mongoCfg, "indexer") + + err = dbmodel.Setup(ctx, stakingConfig, nil) + if err != nil { + cleanup() + return nil, err + } + + err = loadTestdata(ctx, stakingConfig, indexerConfig) + if err != nil { + cleanup() + return nil, err + } + + return &db{ + stakingConfig: stakingConfig, + indexerConfig: indexerConfig, + cleanup: cleanup, + }, nil +} + +func loadTestdata(ctx context.Context, configs ...*config.DbConfig) error { + loadDb := func(cfg *config.DbConfig) error { + client, err := dbclient.NewMongoClient(ctx, cfg) + if err != nil { + return err + } + db := client.Database(cfg.DbName) + + pattern := fmt.Sprintf("testdata/%s/*.json", cfg.DbName) + files, err := filepath.Glob(pattern) + if err != nil { + return err + } + + for _, file := range files { + buff, err := os.ReadFile(file) + if err != nil { + return err + } + + var docs []any + err = bson.UnmarshalExtJSON(buff, true, &docs) + if err != nil { + return err + } + + filename := filepath.Base(file) + collectionName := strings.TrimSuffix(filename, ".json") + coll := db.Collection(collectionName) + + _, err = coll.InsertMany(ctx, docs) + if err != nil { + return err + } + } + + return nil + } + for _, cfg := range configs { + err := loadDb(cfg) + if err != nil { + return fmt.Errorf("failed to load %q db: %w", cfg.DbName, err) + } + } + + return nil +} + +func clientGet(t *testing.T, endpoint string) ([]byte, int) { + require.True(t, strings.HasPrefix(endpoint, "/"), "endpoint must start with /") + url := apiURL + endpoint + + ctx, cancel := context.WithTimeout(context.TODO(), requestTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + require.NoError(t, err) + + resp, err := (&http.Client{}).Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + buff, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + return buff, resp.StatusCode +} + +// setupMongoContainer setups container with mongodb returning db credentials through config.DbConfig, cleanup function +// and an error if any. Cleanup function MUST be called in the end to cleanup docker resources +func setupMongoContainer() (*mongoConfig, func(), error) { + const ( + mongoUsername = "user" + mongoPassword = "password" + // this version corresponds to docker tag for mongodb + // it should be in sync with mongo version used in production + mongoVersion = "7.0.5" + ) + + pool, err := dockertest.NewPool("") + if err != nil { + return nil, nil, err + } + + // generate random string for container name + randomString, err := testutil.RandomAlphaNum(5) + if err != nil { + return nil, nil, err + } + + // there can be only 1 container with the same name, so we add + // random string in the end in case there is still old container running + containerName := "mongo-integration-tests-db-" + randomString + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Name: containerName, + Repository: "mongo", + Tag: mongoVersion, + Env: []string{ + "MONGO_INITDB_ROOT_USERNAME=" + mongoUsername, + "MONGO_INITDB_ROOT_PASSWORD=" + mongoPassword, + }, + }, func(config *dc.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = dc.RestartPolicy{ + Name: "no", + } + }) + if err != nil { + return nil, nil, err + } + + cleanup := func() { + err := pool.Purge(resource) + if err != nil { + // todo change to log fatal + panic(err) + } + } + + // get host port (randomly chosen) that is mapped to mongo port inside container + hostPort := resource.GetPort("27017/tcp") + + return &mongoConfig{ + username: mongoUsername, + password: mongoPassword, + address: fmt.Sprintf("mongodb://localhost:%s/", hostPort), + }, cleanup, nil +} + +type mongoConfig struct { + username string + password string + address string +} + +func createDbConfig(cfg *mongoConfig, dbName string) *config.DbConfig { + return &config.DbConfig{ + Username: cfg.username, + Password: cfg.password, + DbName: dbName, + Address: cfg.address, + MaxPaginationLimit: 2, + LogicalShardCount: pkg.Ptr[int64](10), + } +} diff --git a/tests/api/shared_test.go b/tests/api/shared_test.go new file mode 100644 index 00000000..433c77c1 --- /dev/null +++ b/tests/api/shared_test.go @@ -0,0 +1,117 @@ +//go:build e2e + +package api + +import ( + "fmt" + "net/http" + "strings" + "testing" +) + +func TestV1_StakerPubkeyLookup(t *testing.T) { + t.Parallel() + + cases := []testcase{ + { + testName: "no address provided", + endpoint: "/v1/staker/pubkey-lookup", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"address is required"}`, + }, + { + testName: "invalid address", + endpoint: "/v1/staker/pubkey-lookup?address=invalid_addr", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"can not decode btc address: decoded address is of unknown format"}`, + }, + { + testName: "non existing public key", + endpoint: "/v1/staker/pubkey-lookup?address=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{}}`, + }, + { + testName: "exceeding max addresses", + // generating long query + endpoint: "/v1/staker/pubkey-lookup?" + strings.Repeat("address=addr1&", 100), + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"Maximum 10 address allowed"}`, + }, + } + checkCases(t, cases) + + t.Run("ok", func(t *testing.T) { + addresses := []string{ + "bc1pwwrdfkea9qtp5fg9m630c403vq950s0s36h7pz6435mgafl7auls2cpnvh", // taproot + "bc1qyepnd0hvyxakv3xmh48q5hv4plfsqp8vq9a7uz", // native segwit even, but corresponds to the same doc as above + "bc1qfnqcsx5pk9ct4v4v4e58z8wekkw2lm0fqx9cf5", // native segwit even + "bc1qwrcdqzme084nnkhgjtgerj3dcfyntgz6q80tsq", // native segwit odd + } + + var query string + for _, addr := range addresses { + query += fmt.Sprintf("address=%s&", addr) + } + endpoint := "/v1/staker/pubkey-lookup?" + query + + const expected = ` + {"data":{ + "bc1pwwrdfkea9qtp5fg9m630c403vq950s0s36h7pz6435mgafl7auls2cpnvh":"3faa3aa676b2addc8e4750b65d02f54386e0dbc87d83cdbf3bd02053b0ed0bcf", + "bc1qfnqcsx5pk9ct4v4v4e58z8wekkw2lm0fqx9cf5":"1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1", + "bc1qwrcdqzme084nnkhgjtgerj3dcfyntgz6q80tsq":"c00f83fb8dbed188175c67937c1d62f20eaf04a995b978e72f025c9c980f7a5d", + "bc1qyepnd0hvyxakv3xmh48q5hv4plfsqp8vq9a7uz":"3faa3aa676b2addc8e4750b65d02f54386e0dbc87d83cdbf3bd02053b0ed0bcf" + }} + ` + assertResponse(t, endpoint, http.StatusOK, expected) + }) +} + +func TestV1_StakerDelegationCheck(t *testing.T) { + cases := []testcase{ + { + testName: "no params provided", + endpoint: "/v1/staker/delegation/check", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"address is required"}`, + }, + { + testName: "invalid address", + endpoint: "/v1/staker/delegation/check?address=invalid_addr", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"can not decode btc address: decoded address is of unknown format"}`, + }, + { + testName: "invalid timeframe", + endpoint: "/v1/staker/delegation/check?address=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&timeframe=tomorrow", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid timeframe value"}`, + }, + { + testName: "non existing address with valid timeframe", + endpoint: "/v1/staker/delegation/check?address=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&timeframe=today", + expectedHttpCode: http.StatusOK, + expectedContents: `{"code":0, "data":false}`, + }, + { + testName: "valid but not active delegation", + endpoint: "/v1/staker/delegation/check?address=tb1pxn93chqf33caw2dxs786leqqxwc36r603auj926347tc8n3rrjdssjcf6k", + expectedHttpCode: http.StatusOK, + expectedContents: `{"code":0, "data":false}`, + }, + { + // staking_btc_timestamp happened long time ago which mean it won't be covered by timeframe=today + testName: "valid delegation not covered by chosen timeframe", + endpoint: "/v1/staker/delegation/check?address=tb1pwa22ug5nsz76euemjqg7n9m9thasgtsp5h4q0n5cktn3dczm0hmq9fleyd&timeframe=today", + expectedHttpCode: http.StatusOK, + expectedContents: `{"code":0, "data":false}`, + }, + { + testName: "ok", + endpoint: "/v1/staker/delegation/check?address=bc1p4uscanqkv7r3fc9kf3e9gs3jwe8zvhztxnfx8cmrgmm5p0txx8zsuygt0d", + expectedHttpCode: http.StatusOK, + expectedContents: `{"code":0, "data":true}`, + }, + } + checkCases(t, cases) +} diff --git a/tests/api/testcase_test.go b/tests/api/testcase_test.go new file mode 100644 index 00000000..e29759a4 --- /dev/null +++ b/tests/api/testcase_test.go @@ -0,0 +1,33 @@ +//go:build e2e + +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type testcase struct { + testName string + endpoint string + expectedHttpCode int + expectedContents string +} + +func checkCases(t *testing.T, cases []testcase) { + for _, cs := range cases { + t.Run(cs.testName, func(t *testing.T) { + t.Parallel() + assertResponse(t, cs.endpoint, cs.expectedHttpCode, cs.expectedContents) + }) + } +} + +func assertResponse(t *testing.T, endpoint string, expectedHttpCode int, expectedJSON string) { + t.Helper() + + contents, code := clientGet(t, endpoint) + assert.Equal(t, expectedHttpCode, code) + assert.JSONEqf(t, expectedJSON, string(contents), "received json: %s", contents) +} diff --git a/tests/api/testdata/api/delegations.json b/tests/api/testdata/api/delegations.json new file mode 100644 index 00000000..a8ef5f2f --- /dev/null +++ b/tests/api/testdata/api/delegations.json @@ -0,0 +1,58 @@ +[ + { + "_id": "19caaf9dcf7be81120a503b8e007189ecee53e5912c8fa542b187224ce45000a", + "staker_pk_hex": "21d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e06", + "finality_provider_pk_hex": "32009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769", + "staking_value": { + "$numberLong": "148000" + }, + "state": "active", + "staking_tx": { + "tx_hex": "02000000000102ac8e1a0083ef08b08c32ab200d3aa74c0ba1130162f3f7c19bfc4636356662c90000000000fdffffff2a2ffd89feb0220ea44eab166eb9e2851ba09f24242267e5e74de5e71b03fbe50000000000fdffffff0220420200000000002251209ca9da0c4ef2aa56a794eee20a08e7d10f880204a5d5f5dd45151ed88c76e0290000000000000000496a47626274340021d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e0632009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769fa0001403bfd71bd791ba08739e727497c1b4dfb4f44165bb4347c47d16710b662fb66a4d5c4f6ef358e5971065c0896326fb2700c64f422d5255d84c2fee866db60c1810140d532ab0d0cafca7d758e2cc67c91acdd606400403ca2dcc4423edc6d533f21861f37d6b928330c9fda07dfe6f9c39dbefe784e7e27b90d213d458e330525d2c89e030300", + "output_index": { + "$numberLong": "0" + }, + "start_timestamp": { + "$numberLong": "1716884348" + }, + "start_height": { + "$numberLong": "197535" + }, + "timelock": { + "$numberLong": "64000" + } + }, + "is_overflow": false, + "staker_btc_address": { + "taproot_address": "tb1p6pzrxtcpsqxmmjgxuej3t8qhqkcfyllgchhv20ln26quakue0fws820jz2" + } + }, + { + "_id": "4eccd0df7dd7036bbd0771d5a47b7ab3b1ee396416c2d1c0cbfe3e7482564d14", + "staker_pk_hex": "ce61b3dfb3ab40adf4bd302346ba4ef387ad284abd0d0b53645748aa6d265974", + "finality_provider_pk_hex": "7259aa77dcb96e09d5ae1204bc0648ce6160d8945db4ca12d883aac5e2042c1f", + "staking_value": { + "$numberLong": "100000" + }, + "state": "transitioned", + "staking_tx": { + "tx_hex": "0200000000010622e0cbc5c33e32f78888c64c330547825beea75f38c958f08832572248d8cfd50000000000fdffffff796303c15f2e29ecbdff9c84ee06ae8acf8d9af802587400277af8c59c54277f0000000000fdffffff2ef4590609988f4f453c88f7e14e109d460bd37483f4b1517cb588df5ea29d7d0000000000fdffffff0a8377c33613c8ba7e59be7599900460f86b59bcf624d6b378bca55c4d7a28110000000000fdffffffc0384f58429eb806d568ea427bcfaac28a543adea5fe320326bf9ffec43bf44d0000000000fdffffffb6d365733fefa17cf46250382b2a6846e2310aacd4e2cf44478b4ebb9c1d49860000000000fdffffff03a086010000000000225120c20110cc92cc7b6d4a865f7c3c2cd5fc2c592303ac43c36a26dba73bce3dd6c10000000000000000496a476262743400ce61b3dfb3ab40adf4bd302346ba4ef387ad284abd0d0b53645748aa6d2659747259aa77dcb96e09d5ae1204bc0648ce6160d8945db4ca12d883aac5e2042c1ffa002e280000000000002251201cb4af3bf83851ea71f98373357dfd9dc3f8a2e235f266c89449018e0da736c70140cab60e3e532526f689b95ee63d5fe9d847cd4ed805bec4c28a080b4576d6af870119cab9c477ba3e2178ff9e536c1f01de7656b7436c2a8bb5098f21d573e9e90140919d40620a8befa8105a61ff618130892080514bbb28b5c999848959d265f73ad592658c6a695a644f1b36029bb43c972e2ccbc1ba7dfd8fc239cb4337c06840014092850688253f9658450926ce42aba99f977bf27b110a0711ba40037ba692efac0f4b437df26994c78aad076cbdd9dd9beb2e2bb7c339e73f023008a1042ec23e0140dd46d44a9e8c07bf6d1ce947abb0773aff3f2f08bdb1deb44733912bb6b8529cd4c7fa1c46408fc52a7f60b6da2560877e3e5f716e84519d026eb9b7134bed8a014084e9d5b4b201e9f831a1088b313a5cb61345498cb11c0d12dcaa09562acf9b9c18661a9f7e788b34eb897cbff1229d85c322c964d3294df09e0e0ae7c86552a101403d3212244d4daa4d0feba8338be805a19189d6db2cd145b69e0bbe10d1a3ebf3ab18e9c4cd056b5ea95814c738e1381940889c09416421a0c9df3a547b29c2ad9e030300", + "output_index": { + "$numberLong": "0" + }, + "start_timestamp": { + "$numberLong": "1716884348" + }, + "start_height": { + "$numberLong": "197535" + }, + "timelock": { + "$numberLong": "64000" + } + }, + "is_overflow": false, + "staker_btc_address": { + "taproot_address": "tb1prj627wlc8pg75u0esden2l0anhpl3ghzxhexdjy5fyqcurd8xmrsymn4yq" + } + } +] \ No newline at end of file diff --git a/tests/api/testdata/api/overall_stats.json b/tests/api/testdata/api/overall_stats.json new file mode 100644 index 00000000..a0ecc832 --- /dev/null +++ b/tests/api/testdata/api/overall_stats.json @@ -0,0 +1,81 @@ +[{ + "_id": "0", + "active_tvl": { + "$numberLong": "67990642595" + }, + "total_tvl": { + "$numberLong": "72583507351" + }, + "active_delegations": { + "$numberLong": "417282" + }, + "total_delegations": { + "$numberLong": "478967" + }, + "total_stakers": { + "$numberLong": "343480" + } +}, +{ + "_id": "6", + "active_delegations": -7, + "active_tvl": { + "$numberLong": "-352000" + } +}, +{ + "_id": "8", + "active_delegations": -4, + "active_tvl": { + "$numberLong": "-240000" + } +}, +{ + "_id": "9", + "active_delegations": -1, + "active_tvl": { + "$numberLong": "-100000" + } +}, +{ + "_id": "4", + "active_delegations": -4, + "active_tvl": { + "$numberLong": "-1639000" + } +}, +{ + "_id": "5", + "active_delegations": -4, + "active_tvl": { + "$numberLong": "-200000" + } +}, +{ + "_id": "2", + "active_delegations": -2, + "active_tvl": { + "$numberLong": "-100000" + } +}, +{ + "_id": "1", + "active_delegations": -3, + "active_tvl": { + "$numberLong": "-900000" + } +}, +{ + "_id": "7", + "active_delegations": -2, + "active_tvl": { + "$numberLong": "-550000" + } +}, +{ + "_id": "3", + "active_delegations": -1, + "active_tvl": { + "$numberLong": "-50000" + } +}] \ No newline at end of file diff --git a/tests/api/testdata/api/pk_address_mappings.json b/tests/api/testdata/api/pk_address_mappings.json new file mode 100644 index 00000000..2cebd22c --- /dev/null +++ b/tests/api/testdata/api/pk_address_mappings.json @@ -0,0 +1,20 @@ +[ + { + "_id": "3faa3aa676b2addc8e4750b65d02f54386e0dbc87d83cdbf3bd02053b0ed0bcf", + "taproot": "bc1pwwrdfkea9qtp5fg9m630c403vq950s0s36h7pz6435mgafl7auls2cpnvh", + "native_segwit_even": "bc1qyepnd0hvyxakv3xmh48q5hv4plfsqp8vq9a7uz", + "native_segwit_odd": "bc1qr8fv34wj8lklnu0cz5tzz6szrakngph6ycqv2m" + }, + { + "_id": "1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1", + "taproot": "bc1p4uscanqkv7r3fc9kf3e9gs3jwe8zvhztxnfx8cmrgmm5p0txx8zsuygt0d", + "native_segwit_even": "bc1qfnqcsx5pk9ct4v4v4e58z8wekkw2lm0fqx9cf5", + "native_segwit_odd": "bc1q9yyawfqufdwm6gqsjejad49q8kk7er2v598anl" + }, + { + "_id": "c00f83fb8dbed188175c67937c1d62f20eaf04a995b978e72f025c9c980f7a5d", + "taproot": "bc1p2vv2h0n47l7c6xrdwztsrkr6h85hrjxewdadnh5dyev0gzdn6ytqvwlctr", + "native_segwit_even": "bc1qmfe9hcupvjqpry5u9prwppha3xpx7lulucq9m0", + "native_segwit_odd": "bc1qwrcdqzme084nnkhgjtgerj3dcfyntgz6q80tsq" + } +] \ No newline at end of file diff --git a/tests/api/testdata/finality-providers.json b/tests/api/testdata/finality-providers.json new file mode 100644 index 00000000..1fa3a181 --- /dev/null +++ b/tests/api/testdata/finality-providers.json @@ -0,0 +1,48 @@ +{ + "finality_providers": [ + { + "description": { + "moniker": "Babylon Foundation 2", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": "0.080000000000000000", + "btc_pk": "094f5861be4128861d69ea4b66a5f974943f100f55400bf26f5cce124b4c9af7" + }, + { + "description": { + "moniker": "Babylon Foundation 1", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": "0.060000000000000000", + "btc_pk": "063deb187a4bf11c114cf825a4726e4c2c35fea5c4c44a20ff08a30a752ec7e0" + }, + { + "description": { + "moniker": "Babylon Foundation 3", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": "0.090000000000000000", + "btc_pk": "0d2f9728abc45c0cdeefdd73f52a0e0102470e35fb689fc5bc681959a61b021f" + }, + { + "description": { + "moniker": "Babylon Foundation 0", + "identity": "", + "website": "", + "security_contact": "", + "details": "" + }, + "commission": "0.050000000000000000", + "btc_pk": "03d5a0bb72d71993e435d6c5a70e2aa4db500a62cfaae33c56050deefee64ec0" + } + ] +} \ No newline at end of file diff --git a/tests/api/testdata/global-params.json b/tests/api/testdata/global-params.json new file mode 100644 index 00000000..3af5bbf8 --- /dev/null +++ b/tests/api/testdata/global-params.json @@ -0,0 +1,25 @@ +{ + "versions": [ + { + "version": 0, + "activation_height": 192840, + "staking_cap": 50000000000, + "tag": "01020304", + "covenant_pks": [ + "0381b70c01535f5153a8039c21150c53f3e49a083555b57930103db8a7272ff336", + "02159f46467124f6bbba77060520571ddb07c7e95ff54d8b9958ec0b0d59d86c03", + "039705be04f3a3eb5c3d0dd61e648e06ea8170975744594fe702e8088bcceff375", + "02ce138027bfdfb4dd631e9cecf097082c8a505ab16de36f5e3eb816d105ba7575", + "03e15dba250612e79e22abf28a1828ba5e6bdfaaa6ed2d87462b046994c33fa46f" + ], + "covenant_quorum": 3, + "unbonding_time": 1000, + "unbonding_fee": 20000, + "max_staking_amount": 1000000000, + "min_staking_amount": 1000000, + "max_staking_time": 65000, + "min_staking_time": 64000, + "confirmation_depth": 10 + } + ] +} \ No newline at end of file diff --git a/tests/api/testdata/indexer/btc_delegation_details.json b/tests/api/testdata/indexer/btc_delegation_details.json new file mode 100644 index 00000000..2fd4cf8a --- /dev/null +++ b/tests/api/testdata/indexer/btc_delegation_details.json @@ -0,0 +1,250 @@ +[ + { + "_id": "d3ef0f9fdbc556bcbc9272d168f4aca8e420880ba624f1691b177a36018c736b", + "staking_tx_hex": "02000000000101933cf39b909c9a6b07925ccfadf088559afdac369ac7cec9702e075d3bbeb6090200000000fdffffff0350c30000000000002251201f719f4cacb26c3059abffa5538ee0bbba6d028a9c0a6f754cf46b3f986040f90000000000000000496a4762627434001e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1c20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481fa00f8fa0300000000002251207754ae229380bdacf33b9011e997655dfb042e01a5ea07ce98b2e716e05b7df601405711f80b8bff9d4751680809b3eea627b042ec82fb12bced7d110f9d9e251fda83e3f3d181de19bc6646efc72ea3ae41bc8043adc20db112f687111368dfb77408080300", + "staking_time": { + "$numberLong": "64000" + }, + "staking_amount": { + "$numberLong": "50000" + }, + "staking_output_idx": { + "$numberLong": "0" + }, + "staking_btc_timestamp": { + "$numberLong": "1717595464" + }, + "staker_btc_pk_hex": "1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1", + "finality_provider_btc_pks_hex": [ + "c20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481" + ], + "start_height": { + "$numberLong": "198690" + }, + "end_height": { + "$numberLong": "262690" + }, + "state": "ACTIVE", + "state_history": [ + { + "state": "PENDING", + "bbn_height": { + "$numberLong": "37776" + }, + "bbn_event_type": "EventBTCDelegationCreated" + }, + { + "state": "PENDING", + "bbn_height": { + "$numberLong": "37776" + }, + "bbn_event_type": "EventBTCDelegationInclusionProofReceived" + }, + { + "state": "ACTIVE", + "bbn_height": { + "$numberLong": "37778" + }, + "bbn_event_type": "EventCovenantQuorumReached" + } + ], + "params_version": { + "$numberLong": "1" + }, + "unbonding_time": { + "$numberLong": "1008" + }, + "unbonding_tx": "02000000016b738c01367a171b69f124a60b8820e4a8acf468d17292bcbc56c5db9f0fefd30000000000ffffffff01409c000000000000225120dca9ae0b2aa090ebeba2fd05585ffe35bc210bf38388d1139c4bb9aab511f9d500000000", + "unbonding_start_height": { + "$numberLong": "0" + }, + "unbonding_btc_timestamp": { + "$numberLong": "0" + }, + "covenant_unbonding_signatures": [ + { + "covenant_btc_pk_hex": "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "signature_hex": "37fead58bf43ce795e5813735a6809610cb164855223608f7cc85592acc452e924142f045c0d23920a3c4664d37b173d940e3ed8a097a8bb538a5db2313e7382" + }, + { + "covenant_btc_pk_hex": "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e", + "signature_hex": "c4ff08d4c141d07ab17d87e6948b1ab9e362f074617838c292e59ab56e80cb74a5cc834342cd4f844e2b3f786614f67a2079703747144d3df4ddfdbe2200d221" + }, + { + "covenant_btc_pk_hex": "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "signature_hex": "c0f59419d1219fbba189538f9318e5f59f6a1d40299883feecbb558d0d68c4fe5589fdb0175eae8d83e5c3b5e94485008dfb68f45feed31960d261926ab84050" + }, + { + "covenant_btc_pk_hex": "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "signature_hex": "37e1a55aba54934294ad1583538e91247a64126a165eb75ec6478bf420543e0e38e43e4cc994fac23b68e5022cf53f6c59a2211a90d39fcd2f0d12efec1a6b0c" + }, + { + "covenant_btc_pk_hex": "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "signature_hex": "37a1947a5c0c54950a5bbce276f93b5085d782f4efe449bd55832b212d56c98a6f5fce612b6228600794eb5146ade56fd1a0ceaafad3a4ec3c905a5fc3dc88ad" + }, + { + "covenant_btc_pk_hex": "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "signature_hex": "f9e0ab8ae40dc4c0eac8ceb3fbcaee483220428426bd46b5f542d98c880b8d00b4fb53713dfe752a4e0733cbc806d5e86758092e7ab678970a7763e267947ba9" + }, + { + "covenant_btc_pk_hex": "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "signature_hex": "c39d9e5882102b029b5fe8f9efdd0200b8528744be5b71410b7a8036c98411b817752c6d70de6ffb9c35c5e925174c532308c6595f145287523f28af1d61c987" + } + ], + "btc_delegation_created_bbn_block": { + "height": { + "$numberLong": "37776" + }, + "timestamp": { + "$numberLong": "1736745357" + } + }, + "slashing_tx": { + "spending_height": { + "$numberLong": "0" + }, + "slashing_tx_hex": "", + "slashing_btc_timestamp": { + "$numberLong": "0" + }, + "unbonding_slashing_tx_hex": "", + "unbonding_slashing_btc_timestamp": { + "$numberLong": "0" + } + }, + "staker_babylon_address": "bbn1tpl5jpqkn3n2ah6gugqhwn0cnymayuuqpjyrmv" + }, + { + "_id": "ccc090acb695a2f7fa811785441f1b7c0549b9d8e10e293b14864c632b9db1ae", + "staking_tx_hex": "0200000002542f81fe3d6bfd2b3454b36c72aa4aeb0a11e8dd49d3baf2a67de28f3cc31b350000000000ffffffff333a859fc66d69c70caf346c967d085795c59fad52d649d08a5101de3ec7319b0200000000ffffffff0250c300000000000022512073c9a83a864538dfb8045ce4b27c16dfc90a3f2fa07091ad0f98dc276707e80d3caa00000000000022512034cb1c5c098c71d729a6878fafe40033b11d0f4f8f7922ab51af9783ce231c9b00000000", + "staking_time": { + "$numberLong": "64000" + }, + "staking_amount": { + "$numberLong": "50000" + }, + "staking_output_idx": { + "$numberLong": "0" + }, + "staking_btc_timestamp": { + "$numberLong": "1736620112" + }, + "staker_btc_pk_hex": "f33a3851632079b01be26360bba9f7f49286406d2636a25565f8d83db6ac2a32", + "finality_provider_btc_pks_hex": [ + "d23c2c25e1fcf8fd1c21b9a402c19e2e309e531e45e92fb1e9805b6056b0cc76" + ], + "start_height": { + "$numberLong": "230403" + }, + "end_height": { + "$numberLong": "294403" + }, + "state": "WITHDRAWN", + "state_history": [ + { + "state": "PENDING", + "bbn_height": { + "$numberLong": "26144" + }, + "bbn_event_type": "EventBTCDelegationCreated" + }, + { + "state": "VERIFIED", + "bbn_height": { + "$numberLong": "26149" + }, + "bbn_event_type": "EventCovenantQuorumReached" + }, + { + "state": "ACTIVE", + "bbn_height": { + "$numberLong": "27151" + }, + "bbn_event_type": "EventBTCDelegationInclusionProofReceived" + }, + { + "state": "UNBONDING", + "sub_state": "EARLY_UNBONDING", + "btc_height": { + "$numberLong": "236713" + } + }, + { + "state": "WITHDRAWABLE", + "sub_state": "EARLY_UNBONDING", + "btc_height": { + "$numberLong": "237721" + } + }, + { + "state": "WITHDRAWN", + "sub_state": "EARLY_UNBONDING", + "btc_height": { + "$numberLong": "239741" + } + } + ], + "params_version": { + "$numberLong": "5" + }, + "unbonding_time": { + "$numberLong": "1008" + }, + "unbonding_tx": "0200000001aeb19d2b634c86143b290ee1d8b949057c1b1f44851781faf7a295b6ac90c0cc0000000000ffffffff0180bb000000000000225120610930d8022527f94d1eddef6a076cc4de5803b54ed82fcc3550139200f12cf800000000", + "unbonding_start_height": { + "$numberLong": "236713" + }, + "unbonding_btc_timestamp": { + "$numberLong": "1740396707" + }, + "covenant_unbonding_signatures": [ + { + "covenant_btc_pk_hex": "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "signature_hex": "72363a3fa47605e53bb9b11951527d157bbd5e85f9a4075d273eddf06e363961725d0c4eb88f66bddda6133f3e57206efa89f46dfdd4ccb0ca2df1041bde68b3" + }, + { + "covenant_btc_pk_hex": "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "signature_hex": "7f78eaa375253e3705eb9e0217da8276dfd68662accdc427dcfc6cfb398b0312bee0f3e3824e1f77996d9a11549123617e924a1516ad80c3518f03bdc124cd83" + }, + { + "covenant_btc_pk_hex": "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "signature_hex": "1c3ebfb1094d28a0657bd2b0cfa97ed48744d185d8b292d9a25200ebf7fa84385f410420c1c4464918b7028a12fc777c34e50beec9847ff99559a42e634721dd" + }, + { + "covenant_btc_pk_hex": "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e", + "signature_hex": "3b5ef27d2463d6c4a4a1f0deb4d693650e1546582cfdd4c462099e6d4464db1d0c33e4508adcf1ae972656ad925b11e656c98d75a197cdfe82c4cf793c33e9f6" + }, + { + "covenant_btc_pk_hex": "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "signature_hex": "2544b05f85d714bc61090345db973f7825cfa3e392028cf57a0960f484cf557cb5a541a5062e719ab6db9664a080eab0ebd57f48a2fb5600b601defe5f689915" + }, + { + "covenant_btc_pk_hex": "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "signature_hex": "ebc940b6843cfd2958ac6812f5ea8afaceeab158668757666f9719be3e6c21b2016403417d1a47dfdf4b0a75632fc0325f092e92032cbe5a14f0992d8a09b50c" + } + ], + "btc_delegation_created_bbn_block": { + "height": { + "$numberLong": "26144" + }, + "timestamp": { + "$numberLong": "1736616463" + } + }, + "slashing_tx": { + "spending_height": { + "$numberLong": "0" + }, + "slashing_tx_hex": "", + "slashing_btc_timestamp": { + "$numberLong": "0" + }, + "unbonding_slashing_tx_hex": "", + "unbonding_slashing_btc_timestamp": { + "$numberLong": "0" + } + }, + "sub_state": "EARLY_UNBONDING", + "staker_babylon_address": "bbn1fc9qp3ujx7ylc34pcmwackdwdaexgs02rvpek8" + } +] \ No newline at end of file diff --git a/tests/api/testdata/indexer/finality_provider_details.json b/tests/api/testdata/indexer/finality_provider_details.json new file mode 100644 index 00000000..fa405276 --- /dev/null +++ b/tests/api/testdata/indexer/finality_provider_details.json @@ -0,0 +1,39 @@ +[{ + "_id": "bef341a7adb10213a7ec7825afeb7d57fbfa7b5f7bdf201204fb0ef62fb9cfa6", + "babylon_address": "bbn1hq2ueffyn44u3t67u39yl8jfhs39lfcydnayx3", + "commission": "0.050000000000000000", + "state": "FINALITY_PROVIDER_STATUS_ACTIVE", + "description": { + "moniker": "verse2", + "identity": "", + "website": "https://verse2.io", + "security_contact": "ted@verse2.io", + "details": "" + } +}, + { + "_id": "d23c2c25e1fcf8fd1c21b9a402c19e2e309e531e45e92fb1e9805b6056b0cc76", + "babylon_address": "bbn1ty8c9awrpkqw3jldpcxr942y6yz6tjzg7zqttr", + "commission": "0.100000000000000000", + "state": "FINALITY_PROVIDER_STATUS_ACTIVE", + "description": { + "moniker": "Babylon Foundation 0", + "identity": "", + "website": "https://babylonlabs.io", + "security_contact": "", + "details": "" + } + }, + { + "_id": "e4889630fa8695dae630c41cd9b85ef165ccc2dc5e5935d5a24393a9defee9ef", + "babylon_address": "bbn16mqs7es930802me0lnyl00rq5rejzezgsa5mwc", + "commission": "0.070000000000000000", + "state": "FINALITY_PROVIDER_STATUS_ACTIVE", + "description": { + "moniker": "Babylon Foundation 1", + "identity": "", + "website": "https://babylonlabs.io", + "security_contact": "", + "details": "" + } + }] diff --git a/tests/api/testdata/indexer/global_params.json b/tests/api/testdata/indexer/global_params.json new file mode 100644 index 00000000..e364b0f9 --- /dev/null +++ b/tests/api/testdata/indexer/global_params.json @@ -0,0 +1,424 @@ +[{ + "_id": { + "$oid": "67ab24b88c690f616643f879" + }, + "version": { + "$numberLong": "0" + }, + "type": "CHECKPOINT", + "params": { + "btc_confirmation_depth": { + "$numberLong": "10" + }, + "checkpoint_finalization_timeout": { + "$numberLong": "100" + }, + "checkpoint_tag": "62627435" + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87a" + }, + "version": { + "$numberLong": "0" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "49766ccd9e3cd94343e2040474a77fb37cdfd30530d05f9f1e96ae1e2102c86e", + "76d1ae01f8fb6bf30108731c884cddcf57ef6eef2d9d9559e130894e0e40c62c", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "5000000" + }, + "min_staking_time_blocks": { + "$numberLong": "64000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "76a914010101010101010101010101010101010101010188ac", + "min_slashing_tx_fee_sat": { + "$numberLong": "1000" + }, + "slashing_rate": "0.100000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "2000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1000" + }, + "allow_list_expiration_height": { + "$numberLong": "26120" + }, + "btc_activation_height": { + "$numberLong": "197535" + } + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87b" + }, + "version": { + "$numberLong": "1" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "09585ab55a971a231c945790a0a81df754e5a07263a5c20829931cc24683bbb7", + "76d1ae01f8fb6bf30108731c884cddcf57ef6eef2d9d9559e130894e0e40c62c", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "5000000" + }, + "min_staking_time_blocks": { + "$numberLong": "64000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "76a914010101010101010101010101010101010101010188ac", + "min_slashing_tx_fee_sat": { + "$numberLong": "1000" + }, + "slashing_rate": "0.100000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "10000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1000" + }, + "allow_list_expiration_height": { + "$numberLong": "26120" + }, + "btc_activation_height": { + "$numberLong": "198665" + } + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87c" + }, + "version": { + "$numberLong": "2" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "5000000" + }, + "min_staking_time_blocks": { + "$numberLong": "64000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "76a914010101010101010101010101010101010101010188ac", + "min_slashing_tx_fee_sat": { + "$numberLong": "1000" + }, + "slashing_rate": "0.100000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "10000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1000" + }, + "allow_list_expiration_height": { + "$numberLong": "26120" + }, + "btc_activation_height": { + "$numberLong": "200665" + } + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87d" + }, + "version": { + "$numberLong": "3" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "50000000" + }, + "min_staking_time_blocks": { + "$numberLong": "64000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "76a914010101010101010101010101010101010101010188ac", + "min_slashing_tx_fee_sat": { + "$numberLong": "1000" + }, + "slashing_rate": "0.100000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "5000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1000" + }, + "allow_list_expiration_height": { + "$numberLong": "26120" + }, + "btc_activation_height": { + "$numberLong": "215968" + } + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87e" + }, + "version": { + "$numberLong": "4" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "50000000" + }, + "min_staking_time_blocks": { + "$numberLong": "64000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "76a914010101010101010101010101010101010101010188ac", + "min_slashing_tx_fee_sat": { + "$numberLong": "1000" + }, + "slashing_rate": "0.100000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "5000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1000" + }, + "allow_list_expiration_height": { + "$numberLong": "26120" + }, + "btc_activation_height": { + "$numberLong": "220637" + } + } +}, +{ + "_id": { + "$oid": "67ab24b88c690f616643f87f" + }, + "version": { + "$numberLong": "5" + }, + "type": "STAKING", + "params": { + "covenant_pks": [ + "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "35000000000" + }, + "min_staking_time_blocks": { + "$numberLong": "10000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "00145be12624d08a2b424095d7c07221c33450d14bf1", + "min_slashing_tx_fee_sat": { + "$numberLong": "5000" + }, + "slashing_rate": "0.050000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "2000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1095000" + }, + "allow_list_expiration_height": { + "$numberLong": "26124" + }, + "btc_activation_height": { + "$numberLong": "227174" + } + } +}, +{ + "_id": { + "$oid": "67b47d138c690f616643f8b9" + }, + "type": "STAKING", + "version": { + "$numberLong": "6" + }, + "params": { + "covenant_pks": [ + "fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + "0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + "17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + "113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + "79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + "3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + "d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + "40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + "f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + "covenant_quorum": { + "$numberLong": "6" + }, + "min_staking_value_sat": { + "$numberLong": "50000" + }, + "max_staking_value_sat": { + "$numberLong": "35000000000" + }, + "min_staking_time_blocks": { + "$numberLong": "10000" + }, + "max_staking_time_blocks": { + "$numberLong": "64000" + }, + "slashing_pk_script": "00145be12624d08a2b424095d7c07221c33450d14bf1", + "min_slashing_tx_fee_sat": { + "$numberLong": "6000" + }, + "slashing_rate": "0.050000000000000000", + "unbonding_time_blocks": { + "$numberLong": "1008" + }, + "unbonding_fee_sat": { + "$numberLong": "2000" + }, + "min_commission_rate": "0.030000000000000000", + "delegation_creation_base_gas_fee": { + "$numberLong": "1095000" + }, + "allow_list_expiration_height": { + "$numberLong": "26124" + }, + "btc_activation_height": { + "$numberLong": "235952" + } + } +}] \ No newline at end of file diff --git a/tests/api/testdata/indexer/last_processed_height.json b/tests/api/testdata/indexer/last_processed_height.json new file mode 100644 index 00000000..5a212146 --- /dev/null +++ b/tests/api/testdata/indexer/last_processed_height.json @@ -0,0 +1,10 @@ +[ + { + "_id": { + "$oid": "67ab24ba8c690f616643f880" + }, + "height": { + "$numberLong": "801837" + } + } +] \ No newline at end of file diff --git a/tests/api/v1_test.go b/tests/api/v1_test.go new file mode 100644 index 00000000..be950c4a --- /dev/null +++ b/tests/api/v1_test.go @@ -0,0 +1,165 @@ +//go:build e2e + +package api + +import ( + "net/http" + "testing" +) + +func TestV1_Delegation(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameter", + endpoint: "/v1/delegation", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"staking_tx_hash_hex is required"}`, + }, + { + testName: "invalid staking_tx_hash_hex", + endpoint: "/v1/delegation?staking_tx_hash_hex=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid staking_tx_hash_hex"}`, + }, + { + testName: "non existing staking_tx_hash_hex", + endpoint: "/v1/delegation?staking_tx_hash_hex=035929d276944fe1dd24aad43929d88be8e5c7b935757c59da08ba38df71238c", + expectedHttpCode: http.StatusNotFound, + expectedContents: `{"errorCode":"NOT_FOUND", "message":"staking delegation not found, please retry"}`, + }, + { + testName: "ok", + endpoint: "/v1/delegation?staking_tx_hash_hex=19caaf9dcf7be81120a503b8e007189ecee53e5912c8fa542b187224ce45000a", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{"finality_provider_pk_hex":"32009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769","is_eligible_for_transition":false,"is_overflow":false,"is_slashed":false,"staker_pk_hex":"21d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e06","staking_tx":{"output_index":0,"start_height":197535,"start_timestamp":"2024-05-28T12:19:08+04:00","timelock":64000,"tx_hex":"02000000000102ac8e1a0083ef08b08c32ab200d3aa74c0ba1130162f3f7c19bfc4636356662c90000000000fdffffff2a2ffd89feb0220ea44eab166eb9e2851ba09f24242267e5e74de5e71b03fbe50000000000fdffffff0220420200000000002251209ca9da0c4ef2aa56a794eee20a08e7d10f880204a5d5f5dd45151ed88c76e0290000000000000000496a47626274340021d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e0632009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769fa0001403bfd71bd791ba08739e727497c1b4dfb4f44165bb4347c47d16710b662fb66a4d5c4f6ef358e5971065c0896326fb2700c64f422d5255d84c2fee866db60c1810140d532ab0d0cafca7d758e2cc67c91acdd606400403ca2dcc4423edc6d533f21861f37d6b928330c9fda07dfe6f9c39dbefe784e7e27b90d213d458e330525d2c89e030300"},"staking_tx_hash_hex":"19caaf9dcf7be81120a503b8e007189ecee53e5912c8fa542b187224ce45000a","staking_value":148000,"state":"active"}}`, + }, + } + + checkCases(t, cases) +} + +func TestV1_FinalityProviders(t *testing.T) { + t.Parallel() + + cases := []testcase{ + { + testName: "invalid fp_btc_pk", + endpoint: "/v1/finality-providers?fp_btc_pk=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid fp_btc_pk"}`, + }, + { + testName: "invalid pagination key", + endpoint: "/v1/finality-providers?pagination_key=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid pagination key format"}`, + }, + { + testName: "list all providers", + endpoint: "/v1/finality-providers", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":[{"description":{"moniker":"Babylon Foundation 2","identity":"","website":"","security_contact":"","details":""},"commission":"0.080000000000000000","btc_pk":"094f5861be4128861d69ea4b66a5f974943f100f55400bf26f5cce124b4c9af7","active_tvl":0,"total_tvl":0,"active_delegations":0,"total_delegations":0},{"description":{"moniker":"Babylon Foundation 1","identity":"","website":"","security_contact":"","details":""},"commission":"0.060000000000000000","btc_pk":"063deb187a4bf11c114cf825a4726e4c2c35fea5c4c44a20ff08a30a752ec7e0","active_tvl":0,"total_tvl":0,"active_delegations":0,"total_delegations":0},{"description":{"moniker":"Babylon Foundation 3","identity":"","website":"","security_contact":"","details":""},"commission":"0.090000000000000000","btc_pk":"0d2f9728abc45c0cdeefdd73f52a0e0102470e35fb689fc5bc681959a61b021f","active_tvl":0,"total_tvl":0,"active_delegations":0,"total_delegations":0},{"description":{"moniker":"Babylon Foundation 0","identity":"","website":"","security_contact":"","details":""},"commission":"0.050000000000000000","btc_pk":"03d5a0bb72d71993e435d6c5a70e2aa4db500a62cfaae33c56050deefee64ec0","active_tvl":0,"total_tvl":0,"active_delegations":0,"total_delegations":0}],"pagination":{"next_key":""}}`, + }, + { + testName: "select specific finality provider", + endpoint: "/v1/finality-providers?fp_btc_pk=03d5a0bb72d71993e435d6c5a70e2aa4db500a62cfaae33c56050deefee64ec0", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":[{"active_delegations":0,"active_tvl":0,"btc_pk":"03d5a0bb72d71993e435d6c5a70e2aa4db500a62cfaae33c56050deefee64ec0","commission":"0.050000000000000000","description":{"details":"","identity":"","moniker":"Babylon Foundation 0","security_contact":"","website":""},"total_delegations":0,"total_tvl":0}]}`, + }, + { + testName: "select non existing finality provider", + endpoint: "/v1/finality-providers?fp_btc_pk=88b32b005d5b7e29e6f82998aff023bff7b600c6a1a74ffac984b3aa0579b384", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data": null}`, // todo is it correct? + }, + } + + checkCases(t, cases) +} + +func TestV1_GlobalParams(t *testing.T) { + contents := `{"data":{"versions":[{"activation_height":192840,"cap_height":0,"confirmation_depth":10,"covenant_pks":["0381b70c01535f5153a8039c21150c53f3e49a083555b57930103db8a7272ff336","02159f46467124f6bbba77060520571ddb07c7e95ff54d8b9958ec0b0d59d86c03","039705be04f3a3eb5c3d0dd61e648e06ea8170975744594fe702e8088bcceff375","02ce138027bfdfb4dd631e9cecf097082c8a505ab16de36f5e3eb816d105ba7575","03e15dba250612e79e22abf28a1828ba5e6bdfaaa6ed2d87462b046994c33fa46f"],"covenant_quorum":3,"max_staking_amount":1000000000,"max_staking_time":65000,"min_staking_amount":1000000,"min_staking_time":64000,"staking_cap":50000000000,"tag":"01020304","unbonding_fee":20000,"unbonding_time":1000,"version":0}]}}` + assertResponse(t, "/v1/global-params", http.StatusOK, contents) +} + +func TestV1_UnbondingEligibility(t *testing.T) { + t.Parallel() + + cases := []testcase{ + { + testName: "missing parameter", + endpoint: "/v1/unbonding/eligibility", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"staking_tx_hash_hex is required"}`, + }, + { + testName: "invalid staking_tx_hash_hex", + endpoint: "/v1/unbonding/eligibility?staking_tx_hash_hex=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid staking_tx_hash_hex"}`, + }, + { + testName: "not found", + endpoint: "/v1/unbonding/eligibility?staking_tx_hash_hex=78503b1269fccaf05b00ea53df58eed0a9f614c88dc2170ea7dbcdd76e7cf202", + expectedHttpCode: http.StatusForbidden, + expectedContents: `{"errorCode":"NOT_FOUND", "message":"delegation not found"}`, + }, + { + testName: "ok (not active)", + endpoint: "/v1/unbonding/eligibility?staking_tx_hash_hex=4eccd0df7dd7036bbd0771d5a47b7ab3b1ee396416c2d1c0cbfe3e7482564d14", + expectedHttpCode: http.StatusForbidden, + expectedContents: `{"errorCode":"FORBIDDEN", "message":"delegation state is not active"}`, + }, + { + testName: "ok", + endpoint: "/v1/unbonding/eligibility?staking_tx_hash_hex=19caaf9dcf7be81120a503b8e007189ecee53e5912c8fa542b187224ce45000a", + expectedHttpCode: http.StatusOK, + expectedContents: `null`, // todo is it ok? + }, + } + checkCases(t, cases) +} + +func TestV1_Stats(t *testing.T) { + cases := []testcase{ + { + testName: "ok", + endpoint: "/v1/stats", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{"active_delegations":417254,"active_tvl":67986511595,"pending_tvl":0,"total_delegations":478967,"total_stakers":343480,"total_tvl":72583507351,"unconfirmed_tvl":0}}`, + }, + } + + checkCases(t, cases) +} + +func TestV1_StakerDelegations(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameters", + endpoint: "/v1/staker/delegations", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"staker_btc_pk is required"}`, + }, + { + testName: "invalid staker_btc_pk", + endpoint: "/v1/staker/delegations?staker_btc_pk=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid staker_btc_pk"}`, + }, + { + testName: "non existing staker_btc_pk", + endpoint: "/v1/staker/delegations?staker_btc_pk=8b957fee1fadc87debe176fa8eb77b82c47b1224e26396d24eab0f892b2b1a45", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":[], "pagination":{"next_key":""}}`, + }, + { + testName: "ok", + endpoint: "/v1/staker/delegations?staker_btc_pk=21d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e06", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":[{"staking_tx_hash_hex":"19caaf9dcf7be81120a503b8e007189ecee53e5912c8fa542b187224ce45000a","staker_pk_hex":"21d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e06","finality_provider_pk_hex":"32009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769","state":"active","staking_value":148000,"staking_tx":{"tx_hex":"02000000000102ac8e1a0083ef08b08c32ab200d3aa74c0ba1130162f3f7c19bfc4636356662c90000000000fdffffff2a2ffd89feb0220ea44eab166eb9e2851ba09f24242267e5e74de5e71b03fbe50000000000fdffffff0220420200000000002251209ca9da0c4ef2aa56a794eee20a08e7d10f880204a5d5f5dd45151ed88c76e0290000000000000000496a47626274340021d17b47e1d763f478cba5c414b7adf2778fa4ff6a5ba3d79f08f7a494781e0632009354f274871178dbb4ab7fa789f4a96fea8f0ff5de105b306c046e256769fa0001403bfd71bd791ba08739e727497c1b4dfb4f44165bb4347c47d16710b662fb66a4d5c4f6ef358e5971065c0896326fb2700c64f422d5255d84c2fee866db60c1810140d532ab0d0cafca7d758e2cc67c91acdd606400403ca2dcc4423edc6d533f21861f37d6b928330c9fda07dfe6f9c39dbefe784e7e27b90d213d458e330525d2c89e030300","output_index":0,"start_timestamp":"2024-05-28T12:19:08+04:00","start_height":197535,"timelock":64000},"is_overflow":false,"is_eligible_for_transition":false,"is_slashed":false}],"pagination":{"next_key":""}}`, + }, + } + + checkCases(t, cases) +} diff --git a/tests/api/v2_test.go b/tests/api/v2_test.go new file mode 100644 index 00000000..f379a014 --- /dev/null +++ b/tests/api/v2_test.go @@ -0,0 +1,130 @@ +//go:build e2e + +package api + +import ( + "net/http" + "testing" +) + +func TestV2_FinalityProviders(t *testing.T) { + contents := `{"data":[{"active_delegations":0,"active_tvl":0,"btc_pk":"bef341a7adb10213a7ec7825afeb7d57fbfa7b5f7bdf201204fb0ef62fb9cfa6","commission":"0.050000000000000000","description":{"details":"","identity":"","moniker":"verse2","security_contact":"ted@verse2.io","website":"https://verse2.io"},"state":"FINALITY_PROVIDER_STATUS_ACTIVE"},{"active_delegations":0,"active_tvl":0,"btc_pk":"d23c2c25e1fcf8fd1c21b9a402c19e2e309e531e45e92fb1e9805b6056b0cc76","commission":"0.100000000000000000","description":{"details":"","identity":"","moniker":"Babylon Foundation 0","security_contact":"","website":"https://babylonlabs.io"},"state":"FINALITY_PROVIDER_STATUS_ACTIVE"},{"active_delegations":0,"active_tvl":0,"btc_pk":"e4889630fa8695dae630c41cd9b85ef165ccc2dc5e5935d5a24393a9defee9ef","commission":"0.070000000000000000","description":{"details":"","identity":"","moniker":"Babylon Foundation 1","security_contact":"","website":"https://babylonlabs.io"},"state":"FINALITY_PROVIDER_STATUS_ACTIVE"}],"pagination":{"next_key":""}}` + assertResponse(t, "/v2/finality-providers", http.StatusOK, contents) +} + +func TestV2_NetworkInfo(t *testing.T) { + contents := ` + {"data":{"staking_status":{"is_staking_open":true},"params":{"bbn":[{"version":0,"covenant_pks":["49766ccd9e3cd94343e2040474a77fb37cdfd30530d05f9f1e96ae1e2102c86e","76d1ae01f8fb6bf30108731c884cddcf57ef6eef2d9d9559e130894e0e40c62c","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":5000000,"min_staking_time_blocks":64000,"max_staking_time_blocks":64000,"slashing_pk_script":"76a914010101010101010101010101010101010101010188ac","min_slashing_tx_fee_sat":1000,"slashing_rate":"0.100000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":2000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1000,"allow_list_expiration_height":26120,"btc_activation_height":197535},{"version":1,"covenant_pks":["09585ab55a971a231c945790a0a81df754e5a07263a5c20829931cc24683bbb7","76d1ae01f8fb6bf30108731c884cddcf57ef6eef2d9d9559e130894e0e40c62c","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":5000000,"min_staking_time_blocks":64000,"max_staking_time_blocks":64000,"slashing_pk_script":"76a914010101010101010101010101010101010101010188ac","min_slashing_tx_fee_sat":1000,"slashing_rate":"0.100000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":10000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1000,"allow_list_expiration_height":26120,"btc_activation_height":198665},{"version":2,"covenant_pks":["fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737","0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":5000000,"min_staking_time_blocks":64000,"max_staking_time_blocks":64000,"slashing_pk_script":"76a914010101010101010101010101010101010101010188ac","min_slashing_tx_fee_sat":1000,"slashing_rate":"0.100000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":10000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1000,"allow_list_expiration_height":26120,"btc_activation_height":200665},{"version":3,"covenant_pks":["fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737","0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":50000000,"min_staking_time_blocks":64000,"max_staking_time_blocks":64000,"slashing_pk_script":"76a914010101010101010101010101010101010101010188ac","min_slashing_tx_fee_sat":1000,"slashing_rate":"0.100000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":5000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1000,"allow_list_expiration_height":26120,"btc_activation_height":215968},{"version":4,"covenant_pks":["fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737","0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":50000000,"min_staking_time_blocks":64000,"max_staking_time_blocks":64000,"slashing_pk_script":"76a914010101010101010101010101010101010101010188ac","min_slashing_tx_fee_sat":1000,"slashing_rate":"0.100000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":5000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1000,"allow_list_expiration_height":26120,"btc_activation_height":220637},{"version":5,"covenant_pks":["fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737","0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":35000000000,"min_staking_time_blocks":10000,"max_staking_time_blocks":64000,"slashing_pk_script":"00145be12624d08a2b424095d7c07221c33450d14bf1","min_slashing_tx_fee_sat":5000,"slashing_rate":"0.050000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":2000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1095000,"allow_list_expiration_height":26124,"btc_activation_height":227174},{"version":6,"covenant_pks":["fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737","0aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25","17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e"],"covenant_quorum":6,"min_staking_value_sat":50000,"max_staking_value_sat":35000000000,"min_staking_time_blocks":10000,"max_staking_time_blocks":64000,"slashing_pk_script":"00145be12624d08a2b424095d7c07221c33450d14bf1","min_slashing_tx_fee_sat":6000,"slashing_rate":"0.050000000000000000","unbonding_time_blocks":1008,"unbonding_fee_sat":2000,"min_commission_rate":"0.030000000000000000","max_active_finality_providers":0,"delegation_creation_base_gas_fee":1095000,"allow_list_expiration_height":26124,"btc_activation_height":235952}],"btc":[{"version":0,"btc_confirmation_depth":10}]}}} + ` + assertResponse(t, "/v2/network-info", http.StatusOK, contents) +} + +func TestV2_Prices(t *testing.T) { + contents := `{"errorCode":"INTERNAL_SERVICE_ERROR","message":"Internal service error"}` + assertResponse(t, "/v2/prices", http.StatusInternalServerError, contents) +} + +func TestV2_Stats(t *testing.T) { + contents := `{"data":{"active_tvl":0,"active_delegations":0,"active_finality_providers":3,"total_finality_providers":3,"total_active_tvl":67986511595,"total_active_delegations":417254,"btc_staking_apr":0}}` + assertResponse(t, "/v2/stats", http.StatusOK, contents) +} + +func TestV2_AddressScreening(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameter", + endpoint: "/address/screening", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST","message":"btc_address is required"}`, + }, + { + testName: "invalid btc_address", + endpoint: "/address/screening?btc_address=invalid", + expectedHttpCode: http.StatusInternalServerError, + expectedContents: `{"errorCode":"INTERNAL_SERVICE_ERROR","message":"Internal service error"}`, + }, + } + + checkCases(t, cases) +} + +func TestV2_Delegation(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameter", + endpoint: "/v2/delegation", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"staking_tx_hash_hex is required"}`, + }, + { + testName: "invalid staking_tx_hash_hex", + endpoint: "/v2/delegation?staking_tx_hash_hex=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid staking_tx_hash_hex"}`, + }, + { + testName: "non existing staking_tx_hash_hex", + endpoint: "/v2/delegation?staking_tx_hash_hex=61325403c5a553f7e5a061d314904c02a9ec4202a2616b531335998b4506d43b", + expectedHttpCode: http.StatusNotFound, + expectedContents: `{"errorCode":"NOT_FOUND", "message":"staking delegation not found, please retry"}`, + }, + { + testName: "ok", + endpoint: "/v2/delegation?staking_tx_hash_hex=d3ef0f9fdbc556bcbc9272d168f4aca8e420880ba624f1691b177a36018c736b", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{"params_version":1,"staker_btc_pk_hex":"1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1","finality_provider_btc_pks_hex":["c20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481"],"delegation_staking":{"staking_tx_hash_hex":"d3ef0f9fdbc556bcbc9272d168f4aca8e420880ba624f1691b177a36018c736b","staking_tx_hex":"02000000000101933cf39b909c9a6b07925ccfadf088559afdac369ac7cec9702e075d3bbeb6090200000000fdffffff0350c30000000000002251201f719f4cacb26c3059abffa5538ee0bbba6d028a9c0a6f754cf46b3f986040f90000000000000000496a4762627434001e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1c20acf33c17e5a6c92cced9f1d530cccab7aa3e53400456202f02fac95e9c481fa00f8fa0300000000002251207754ae229380bdacf33b9011e997655dfb042e01a5ea07ce98b2e716e05b7df601405711f80b8bff9d4751680809b3eea627b042ec82fb12bced7d110f9d9e251fda83e3f3d181de19bc6646efc72ea3ae41bc8043adc20db112f687111368dfb77408080300","staking_timelock":64000,"staking_amount":50000,"start_height":198690,"end_height":262690,"bbn_inception_height":37776,"bbn_inception_time":"2025-01-13T09:15:57+04:00","slashing":{"slashing_tx_hex":"","spending_height":0}},"delegation_unbonding":{"unbonding_timelock":1008,"unbonding_tx":"02000000016b738c01367a171b69f124a60b8820e4a8acf468d17292bcbc56c5db9f0fefd30000000000ffffffff01409c000000000000225120dca9ae0b2aa090ebeba2fd05585ffe35bc210bf38388d1139c4bb9aab511f9d500000000","covenant_unbonding_signatures":[{"covenant_btc_pk_hex":"d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36","signature_hex":"37fead58bf43ce795e5813735a6809610cb164855223608f7cc85592acc452e924142f045c0d23920a3c4664d37b173d940e3ed8a097a8bb538a5db2313e7382"},{"covenant_btc_pk_hex":"f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e","signature_hex":"c4ff08d4c141d07ab17d87e6948b1ab9e362f074617838c292e59ab56e80cb74a5cc834342cd4f844e2b3f786614f67a2079703747144d3df4ddfdbe2200d221"},{"covenant_btc_pk_hex":"79a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0","signature_hex":"c0f59419d1219fbba189538f9318e5f59f6a1d40299883feecbb558d0d68c4fe5589fdb0175eae8d83e5c3b5e94485008dfb68f45feed31960d261926ab84050"},{"covenant_btc_pk_hex":"17921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4","signature_hex":"37e1a55aba54934294ad1583538e91247a64126a165eb75ec6478bf420543e0e38e43e4cc994fac23b68e5022cf53f6c59a2211a90d39fcd2f0d12efec1a6b0c"},{"covenant_btc_pk_hex":"113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0","signature_hex":"37a1947a5c0c54950a5bbce276f93b5085d782f4efe449bd55832b212d56c98a6f5fce612b6228600794eb5146ade56fd1a0ceaafad3a4ec3c905a5fc3dc88ad"},{"covenant_btc_pk_hex":"3bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c","signature_hex":"f9e0ab8ae40dc4c0eac8ceb3fbcaee483220428426bd46b5f542d98c880b8d00b4fb53713dfe752a4e0733cbc806d5e86758092e7ab678970a7763e267947ba9"},{"covenant_btc_pk_hex":"40afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df","signature_hex":"c39d9e5882102b029b5fe8f9efdd0200b8528744be5b71410b7a8036c98411b817752c6d70de6ffb9c35c5e925174c532308c6595f145287523f28af1d61c987"}],"slashing":{"unbonding_slashing_tx_hex":"","spending_height":0}},"state":"ACTIVE"}}`, + }, + } + + checkCases(t, cases) +} + +func TestV2_Delegations(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameters", + endpoint: "/v2/delegations", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"staker_pk_hex is required"}`, + }, + { + testName: "invalid staker_pk_hex", + endpoint: "/v2/delegations?staker_pk_hex=invalid", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST", "message":"invalid staker_pk_hex"}`, + }, + { + testName: "non existing staker_pk_hex", + endpoint: "/v2/delegations?staker_pk_hex=5f61c497c58a8961bc3a0d493971773635f928261596944c2f7e9f565114c6f3", + expectedHttpCode: http.StatusOK, // todo is it ok? + expectedContents: `{"data":[],"pagination":{"next_key":""}}`, + }, + } + + checkCases(t, cases) +} + +func TestV2_StakerStats(t *testing.T) { + cases := []testcase{ + { + testName: "missing parameters", + endpoint: "/v2/staker/stats", + expectedHttpCode: http.StatusBadRequest, + expectedContents: `{"errorCode":"BAD_REQUEST","message":"staker_pk_hex is required"}`, + }, + { + testName: "ok", + endpoint: "/v2/staker/stats?staker_pk_hex=1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{"active_tvl":50000,"active_delegations":1,"unbonding_tvl":0,"unbonding_delegations":0,"withdrawable_tvl":0,"withdrawable_delegations":0,"slashed_tvl":0,"slashed_delegations":0}}`, + }, + { + testName: "existing stats and empty babylon_address", + endpoint: "/v2/staker/stats?staker_pk_hex=1e06e1ef408126703ed66447cd6972434396b252a22e843d8295d55ae7a9cfd1&babylon_address=", + expectedHttpCode: http.StatusOK, + expectedContents: `{"data":{"active_tvl":50000,"active_delegations":1,"unbonding_tvl":0,"unbonding_delegations":0,"withdrawable_tvl":0,"withdrawable_delegations":0,"slashed_tvl":0,"slashed_delegations":0}}`, + }, + } + + checkCases(t, cases) +}