diff --git a/internal/api/model/compare.go b/internal/api/model/compare.go new file mode 100644 index 0000000000..0673757a96 --- /dev/null +++ b/internal/api/model/compare.go @@ -0,0 +1,145 @@ +package model + +import ( + "time" + + "github.com/0chain/common/core/currency" + "github.com/0chain/gosdk/core/common" +) + +type Blobber struct { + ID string `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DelegateWallet string `json:"delegate_wallet"` + NumDelegates int `json:"num_delegates"` + ServiceCharge float64 `json:"service_charge"` + TotalStake currency.Coin `json:"total_stake"` + Downtime uint64 `json:"downtime"` + LastHealthCheck common.Timestamp `json:"last_health_check"` + IsKilled bool `json:"is_killed"` + IsShutdown bool `json:"is_shutdown"` + BaseURL string `json:"base_url" gorm:"uniqueIndex"` + ReadPrice currency.Coin `json:"read_price"` + WritePrice currency.Coin `json:"write_price"` + Capacity int64 `json:"capacity"` + Allocated int64 `json:"allocated"` + SavedData int64 `json:"saved_data"` + ReadData int64 `json:"read_data"` + NotAvailable bool `json:"not_available"` + IsRestricted bool `json:"is_restricted"` + OffersTotal currency.Coin `json:"offers_total"` + TotalServiceCharge currency.Coin `json:"total_service_charge"` + ChallengesPassed uint64 `json:"challenges_passed"` + ChallengesCompleted uint64 `json:"challenges_completed"` + OpenChallenges uint64 `json:"open_challenges"` + TotalBlockRewards currency.Coin `json:"total_block_rewards"` + TotalStorageIncome currency.Coin `json:"total_storage_income"` + TotalReadIncome currency.Coin `json:"total_read_income"` + TotalSlashedStake currency.Coin `json:"total_slashed_stake"` + CreationRound int64 `json:"creation_round"` +} +type Miner struct { + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DelegateWallet string `json:"delegate_wallet"` + NumDelegates int `json:"num_delegates"` + ServiceCharge float64 `json:"service_charge"` + TotalStake int64 `json:"total_stake"` + Downtime int64 `json:"downtime"` + LastHealthCheck int64 `json:"last_health_check"` + IsKilled bool `json:"is_killed"` + IsShutdown bool `json:"is_shutdown"` + N2NHost string `json:"n2n_host"` + Host string `json:"host"` + Port int64 `json:"port"` + Path string `json:"path"` + PublicKey string `json:"public_key"` + ShortName string `json:"short_name"` + BuildTag string `json:"build_tag"` + Delete bool `json:"delete"` + Fees currency.Coin `json:"fees"` + Active bool `json:"active"` + BlocksFinalised int64 `json:"blocks_finalised"` + CreationRound int64 `json:"creation_round" gorm:"index:idx_miner_creation_round"` +} + +type Sharder struct { + ID string `json:"id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + DelegateWallet string `json:"delegate_wallet"` + NumDelegates string `json:"num_delegates"` + ServiceCharge float64 `json:"service_charge"` + TotalStake string `json:"total_stake"` + Downtime uint64 `json:"downtime"` + LastHealthCheck common.Timestamp `json:"last_health_check"` + IsKilled bool `json:"is_killed"` + IsShutdown bool `json:"is_shutdown"` + N2NHost string `json:"n2n_host"` + Host string `json:"host"` + Port int `json:"port"` + Path string `json:"path"` + PublicKey string `json:"public_key"` + ShortName string `json:"short_name"` + BuildTag string `json:"build_tag"` + Delete bool `json:"delete"` + Fees currency.Coin `json:"fees"` + Active bool `json:"active"` + CreationRound int64 `json:"creation_round" gorm:"index:idx_sharder_creation_round"` +} + +type Validator struct { + ID string `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DelegateWallet string `json:"delegate_wallet"` + NumDelegates int `json:"num_delegates"` + ServiceCharge float64 `json:"service_charge"` + TotalStake currency.Coin `json:"total_stake"` + Downtime uint64 `json:"downtime"` + LastHealthCheck common.Timestamp `json:"last_health_check"` + IsKilled bool `json:"is_killed"` + IsShutdown bool `json:"is_shutdown"` + Url string `json:"base_url"` + PublicKey string `json:"public_key"` + CreationRound int64 `json:"creation_round"` +} +type Authorizer struct { + ID string `gorm:"primaryKey" json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DelegateWallet string `json:"delegate_wallet"` + NumDelegates int `json:"num_delegates"` + ServiceCharge float64 `json:"service_charge"` + TotalStake currency.Coin `json:"total_stake"` + Downtime uint64 `json:"downtime"` + LastHealthCheck common.Timestamp `json:"last_health_check"` + IsKilled bool `json:"is_killed"` + IsShutdown bool `json:"is_shutdown"` + URL string `json:"url"` + Fee currency.Coin `json:"fee"` + TotalMint currency.Coin `json:"total_mint"` + TotalBurn currency.Coin `json:"total_burn"` + CreationRound int64 `json:"creation_round"` +} +type User struct { + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + UserID string `json:"user_id" gorm:"uniqueIndex"` + TxnHash string `json:"txn_hash"` + Balance currency.Coin `json:"balance"` + Round int64 `json:"round"` + Nonce int64 `json:"nonce"` + MintNonce int64 `json:"mint_nonce"` +} +type ProviderRewards struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ProviderID string `json:"provider_id"` + Rewards currency.Coin `json:"rewards"` + TotalRewards currency.Coin `json:"total_rewards"` + RoundServiceChargeLastUpdated int64 `json:"round_service_charge_last_updated"` +} diff --git a/internal/api/util/client/api_client.go b/internal/api/util/client/api_client.go index 56b7473a9a..a58652e5a6 100644 --- a/internal/api/util/client/api_client.go +++ b/internal/api/util/client/api_client.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "net/url" + "reflect" "strconv" "strings" "time" @@ -60,6 +61,7 @@ const ( PartitionSizeFrequency = "/v1/screst/:sc_address/parition-size-frequency" BlobberPartitionSelectionFrequency = "/v1/screst/:sc_address/blobber-selection-frequency" GetAllChallenges = "/v1/screst/:sc_address/all-challenges" + GetQueryData = "/v1/screst/:sc_address/query-data" ) // Contains all used service providers @@ -2119,3 +2121,73 @@ func (c *APIClient) BurnZcn(t *test.SystemTest, wallet *model.Wallet, address st wallet.IncNonce() return burnZcnTransactionGetConfirmationResponse.Hash } + +func (c *APIClient) QueryDataFromSharder(t *test.SystemTest, tableName string) ([]interface{}, *resty.Response, error) { + t.Log("Querying data from Sharder...") + + extractFields := func(model interface{}) string { + val := reflect.ValueOf(model) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return "" + } + var fieldNames []string + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + jsonTag := field.Tag.Get("json") + if jsonTag != "" && jsonTag != "-" { + tagParts := strings.Split(jsonTag, ",") + fieldNames = append(fieldNames, tagParts[0]) + } else { + fieldNames = append(fieldNames, field.Name) + } + } + return strings.Join(fieldNames, ",") + } + urlBuilder := NewURLBuilder(). + SetPath(GetQueryData). + SetPathVariable("sc_address", StorageSmartContractAddress) + + var data interface{} + var tableEntity interface{} + switch tableName { + case "blobber": + tableEntity = model.Blobber{} + case "miner": + tableEntity = model.Miner{} + case "authorizer": + tableEntity = model.Authorizer{} + case "validator": + tableEntity = model.Validator{} + case "sharder": + tableEntity = model.Sharder{} + case "user": + tableEntity = model.User{} + case "provider_rewards": + tableEntity = model.ProviderRewards{} + + } + urlBuilder.queries.Set("entity", tableName) + urlBuilder.queries.Set("fields", extractFields(tableEntity)) + resp, err := c.executeForAllServiceProviders(t, urlBuilder, &model.ExecutionRequest{ + Dst: &data, + RequiredStatusCode: 200, + Headers: map[string]string{ + "Accept-Encoding": "", + }, + }, HttpGETMethod, SharderServiceProvider) + var result []interface{} + switch v := data.(type) { + case []interface{}: + for _, value := range v { + result = append(result, value) + } + default: + t.Error("Invalid response from Sharder") + } + + return result, resp, err +} diff --git a/internal/api/util/client/zbox_client.go b/internal/api/util/client/zbox_client.go index 04942e1d4a..4420eeb695 100644 --- a/internal/api/util/client/zbox_client.go +++ b/internal/api/util/client/zbox_client.go @@ -1289,57 +1289,72 @@ func (c *ZboxClient) GetGraphBlobberTotalRewards(t *test.SystemTest, blobberId s return &data, resp, err } -func (c *ZboxClient) CreateJwtToken(t *test.SystemTest, headers map[string]string) (*model.ZboxJwtToken, *resty.Response, error) { - t.Logf("creating jwt token for userID [%v] using 0box...", headers["X-App-User-ID"]) - var zboxJwtToken *model.ZboxJwtToken - - urlBuilder := NewURLBuilder() - err := urlBuilder.MustShiftParse(c.zboxEntrypoint) - require.NoError(t, err, "URL parse error") - urlBuilder.SetPath("/v2/jwt/token") - - resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ - Dst: &zboxJwtToken, - Headers: headers, - RequiredStatusCode: 201, - }, HttpPOSTMethod) - - return zboxJwtToken, resp, err -} - -func (c *ZboxClient) RefreshJwtToken(t *test.SystemTest, token string, headers map[string]string) (*model.ZboxJwtToken, *resty.Response, error) { - t.Logf("refreshing jwt token for userID [%v] and token [%v] using 0box...", headers["X-App-User-ID"], token) - var zboxJwtToken *model.ZboxJwtToken - - headers["X-Jwt-Token"] = token +func (c *ZboxClient) QueryDataFrom0box(t *test.SystemTest, tableName string) ([]interface{}, *resty.Response, error) { + t.Logf("Querying data from 0box...") + + extractFields := func(model interface{}) string { + val := reflect.ValueOf(model) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return "" + } + var fieldNames []string + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + jsonTag := field.Tag.Get("json") + if jsonTag != "" && jsonTag != "-" { + tagParts := strings.Split(jsonTag, ",") + fieldNames = append(fieldNames, tagParts[0]) + } else { + fieldNames = append(fieldNames, field.Name) + } + } + return strings.Join(fieldNames, ",") + } urlBuilder := NewURLBuilder() err := urlBuilder.MustShiftParse(c.zboxEntrypoint) require.NoError(t, err, "URL parse error") - urlBuilder.SetPath("/v2/jwt/token") + urlBuilder.SetPath("/v2/queryData") + var data interface{} + var tableEntity interface{} + switch tableName { + case "blobber": + tableEntity = model.Blobber{} + case "miner": + tableEntity = model.Miner{} + case "authorizer": + tableEntity = model.Authorizer{} + case "validator": + tableEntity = model.Validator{} + case "sharder": + tableEntity = model.Sharder{} + case "user": + tableEntity = model.User{} + case "provider_rewards": + tableEntity = model.ProviderRewards{} - resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ - Dst: &zboxJwtToken, - Headers: headers, - RequiredStatusCode: 201, - }, HttpPUTMethod) - - return zboxJwtToken, resp, err -} - -func (c *ZboxClient) GetTransactionsList(t *test.SystemTest, pitId string) (*model.ZboxTransactionsDataResponse, *resty.Response, error) { - t.Logf("Getting transactions data with pitid using 0box...") - var txnData model.ZboxTransactionsDataResponse - urlBuilder := NewURLBuilder() - err := urlBuilder.MustShiftParse(c.zboxEntrypoint) - require.NoError(t, err, "URL parse error") - urlBuilder.SetPath("/v2/transactions") - urlBuilder.queries.Set("pit_id", pitId) + } + urlBuilder.queries.Set("table", tableName) + urlBuilder.queries.Set("fields", extractFields(tableEntity)) resp, err := c.executeForServiceProvider(t, urlBuilder.String(), model.ExecutionRequest{ - Dst: &txnData, + Dst: &data, RequiredStatusCode: 200, }, HttpGETMethod) - return &txnData, resp, err + var result []interface{} + switch v := data.(type) { + case []interface{}: + for _, value := range v { + result = append(result, value) + } + default: + t.Error("Invalid response from Sharder") + } + + return result, resp, err } diff --git a/tests/api_tests/compare_0box_sharder_test.go b/tests/api_tests/compare_0box_sharder_test.go new file mode 100644 index 0000000000..a48d05d10e --- /dev/null +++ b/tests/api_tests/compare_0box_sharder_test.go @@ -0,0 +1,141 @@ +package api_tests + +import ( + "testing" + "time" + + "github.com/0chain/system_test/internal/api/util/test" + "github.com/0chain/system_test/internal/currency" + "github.com/stretchr/testify/require" +) + +func ParseToTimeIfValid(val interface{}) interface{} { + // check if val is a time.Time as a string + valStr, ok := val.(string) + if ok { + res, err := time.Parse(time.RFC3339Nano, valStr) + if err == nil { + return res + } + } + // Parsing failed, v1Str is not a time string + return val +} + +func CompareEntityTables(t *test.SystemTest, entity string, id_sharder string, id_0box string) { + + entityTable_Sharder, resp, err := apiClient.QueryDataFromSharder(t, entity) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode()) + + entityTable_0box, resp, err := zboxClient.QueryDataFrom0box(t, entity) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode()) + + if entity == "user" { + entityTable_SharderCopy := entityTable_Sharder + entityTable_Sharder = entityTable_Sharder[:0] + // t.Logf("entityTable_Sharder: %+v", entityTable_Sharder) + + for i := 0; i < len(entityTable_SharderCopy); i++ { + // t.Logf("entityTable_SharderCopy[i].(map[string]interface{})[\"round\"]: %+v", entityTable_SharderCopy[i].(map[string]interface{})["round"]) + temp_copy := entityTable_SharderCopy[i].(map[string]interface{}) + val := temp_copy["round"] + if val != nil { + switch val.(type) { + case float64: + if val.(float64) != 0 { + entityTable_Sharder = append(entityTable_Sharder, temp_copy) + } + case int: + if val.(int) != 0 { + entityTable_Sharder = append(entityTable_Sharder, temp_copy) + } + case int64: + if val.(int64) != 0 { + entityTable_Sharder = append(entityTable_Sharder, temp_copy) + } + } + } + } + } + require.Equal(t, len(entityTable_Sharder), len(entityTable_0box)) + // t.Logf("entityTable_Sharder: %+v", entityTable_Sharder) + // t.Logf("entityTable_0box: %+v", entityTable_0box) + + var entity_Sharder, entity_0box map[string]map[string]interface{} + entity_Sharder = make(map[string]map[string]interface{}) + entity_0box = make(map[string]map[string]interface{}) + for i := 0; i < len(entityTable_Sharder); i++ { + e_sharder := entityTable_Sharder[i].(map[string]interface{}) + e_0box := entityTable_0box[i].(map[string]interface{}) + entity_Sharder[e_sharder[id_sharder].(string)] = e_sharder + entity_0box[e_0box[id_0box].(string)] = e_0box + } + for k, v := range entity_Sharder { + for k1, val1 := range v { + if val1 != nil && entity_0box[k][k1] != nil { + // how to know if v1 is a time.Time as a stringaf + v1 := ParseToTimeIfValid(val1) + switch v1.(type) { + case float64: + require.InDelta(t, v1.(float64), entity_0box[k][k1].(float64), 0.05*v1.(float64), "Float64 values are not equal for field %s", k1) + case string: + require.Equal(t, v1.(string), entity_0box[k][k1].(string)) + case bool: + require.Equal(t, v1.(bool), entity_0box[k][k1].(bool)) + case int: + require.InDelta(t, v1.(int), entity_0box[k][k1].(int), 0.05*float64(v1.(int))) + case int64: + require.InDelta(t, v1.(int64), entity_0box[k][k1].(int64), 0.05*float64(v1.(int64))) + case uint64: + require.InDelta(t, v1.(uint64), entity_0box[k][k1].(uint64), 0.05*float64(v1.(uint64))) + case time.Time: + t1 := v1.(time.Time) + entity_0box[k][k1] = ParseToTimeIfValid(entity_0box[k][k1]) + t2 := entity_0box[k][k1].(time.Time) + diff := t1.Sub(t2) + require.LessOrEqual(t, diff.Seconds(), 1000.0, "Time difference is more than 1000 seconds") + require.GreaterOrEqual(t, diff.Seconds(), -1000.0, "Time difference is more than -1000 seconds") + case currency.Coin: + t1, err1 := (v1.(currency.Coin)).Int64() + t2, err2 := entity_0box[k][k1].(currency.Coin).Int64() + require.NoError(t, err1) + require.NoError(t, err2) + require.InDelta(t, t1, t2, 0.05*float64(t1)) + + } + + } + } + + } +} + +func TestCompares0boxTablesWithSharder(testSetup *testing.T) { + t := test.NewSystemTest(testSetup) + t.SetSmokeTests("Compare 0box tables with sharder tables") + + t.RunSequentially("Compare Miner tables", func(t *test.SystemTest) { + CompareEntityTables(t, "miner", "ID", "id") + }) + t.RunSequentially("Compare Blobber tables", func(t *test.SystemTest) { + CompareEntityTables(t, "blobber", "ID", "id") + }) + t.RunSequentially("Compare Sharder tables", func(t *test.SystemTest) { + CompareEntityTables(t, "sharder", "ID", "id") + }) + t.RunSequentially("Compare Validator tables", func(t *test.SystemTest) { + CompareEntityTables(t, "validator", "ID", "id") + }) + t.RunSequentially("Compare Authorizer tables", func(t *test.SystemTest) { + CompareEntityTables(t, "authorizer", "ID", "id") + }) + t.RunSequentially("Compare ProviderRewards tables", func(t *test.SystemTest) { + CompareEntityTables(t, "provider_rewards", "provider_id", "provider_id") + }) + t.RunSequentially("Compare User tables", func(t *test.SystemTest) { + CompareEntityTables(t, "user", "txn_hash", "txn_hash") + }) + +}