diff --git a/cmd/main.go b/cmd/main.go index e48187d..3c21076 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,9 +23,8 @@ func main() { redis.RedisClient = redis.NewRedisClient() service.ConfigureRelayer() - service.StartCollectorServer() wg.Add(1) - go service.StartApiServer() + go service.StartCollectorServer() wg.Wait() } diff --git a/config/settings.go b/config/settings.go index 5dc4b60..d3dfd87 100644 --- a/config/settings.go +++ b/config/settings.go @@ -14,7 +14,6 @@ type Settings struct { RedisPort string RelayerRendezvousPoint string RelayerPrivateKey string - AuthReadToken string SlackReportingUrl string DataMarketAddress string } @@ -27,7 +26,6 @@ func LoadConfig() { RedisPort: getEnv("REDIS_PORT", ""), RelayerRendezvousPoint: getEnv("RELAYER_RENDEZVOUS_POINT", ""), RelayerPrivateKey: getEnv("RELAYER_PRIVATE_KEY", ""), - AuthReadToken: getEnv("AUTH_READ_TOKEN", ""), SlackReportingUrl: getEnv("SLACK_REPORTING_URL", ""), DataMarketAddress: getEnv("DATA_MARKET_ADDRESS", ""), } @@ -51,7 +49,6 @@ func LoadConfig() { log.Fatalf("Missing required environment variables: %v", missingEnvVars) } - checkOptionalEnvVar(config.AuthReadToken, "AUTH_READ_TOKEN") checkOptionalEnvVar(config.SlackReportingUrl, "SLACK_REPORTING_URL") checkOptionalEnvVar(config.RedisHost, "REDIS_HOST") checkOptionalEnvVar(config.RedisPort, "REDIS_PORT") diff --git a/pkgs/service/api.go b/pkgs/service/api.go deleted file mode 100644 index f9c3d50..0000000 --- a/pkgs/service/api.go +++ /dev/null @@ -1,962 +0,0 @@ -package service - -import ( - "Listen/config" - "Listen/pkgs" - "Listen/pkgs/prost" - "Listen/pkgs/redis" - "context" - "encoding/json" - "fmt" - "github.com/cenkalti/backoff/v4" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/google/uuid" - log "github.com/sirupsen/logrus" - "math/big" - "net/http" - "strconv" - "strings" - "sync" -) - -var ( - rewardBasePoints *big.Int - dailySnapshotQuota *big.Int - dataMarketAddress common.Address -) - -const baseExponent = int64(100000000) - -type DailyRewardsRequest struct { - SlotID int `json:"slot_id"` - Day int `json:"day"` - Token string `json:"token"` -} - -type RewardsRequest struct { - SlotID int `json:"slot_id"` - Token string `json:"token"` -} - -type SubmissionsRequest struct { - SlotID int `json:"slot_id"` - Token string `json:"token"` - PastDays int `json:"past_days"` -} - -type PastEpochsRequest struct { - Token string `json:"token"` - PastEpochs int `json:"past_epochs"` -} - -type PastBatchesRequest struct { - Token string `json:"token"` - PastBatches int `json:"past_batches"` -} - -type DailySubmissions struct { - Day int `json:"day"` - Submissions int64 `json:"submissions"` -} - -func getTotalRewards(slotId int) (int64, error) { - ctx := context.Background() - slotIdStr := strconv.Itoa(slotId) - - totalSlotRewardsKey := redis.TotalSlotRewards(slotIdStr) - totalSlotRewards, err := redis.Get(ctx, totalSlotRewardsKey) - - if err != nil || totalSlotRewards == "" { - slotIdBigInt := big.NewInt(int64(slotId)) - totalSlotRewardsBigInt, err := FetchSlotRewardsPoints(slotIdBigInt) - if err != nil { - return 0, err - } - - totalSlotRewardsInt := totalSlotRewardsBigInt.Int64() - - var day *big.Int - dayStr, _ := redis.Get(context.Background(), pkgs.SequencerDayKey) - if dayStr == "" { - // TODO: Report unhandled error - day, err = FetchDayCounter() - } else { - day, _ = new(big.Int).SetString(dayStr, 10) - } - - currentDayRewardsKey := redis.SlotRewardsForDay(slotIdStr, day.String()) - currentDayRewards, err := redis.Get(ctx, currentDayRewardsKey) - - if err != nil || currentDayRewards == "" { - currentDayRewards = "0" - } - - currentDayRewardsInt, err := strconv.ParseInt(currentDayRewards, 10, 64) - - totalSlotRewardsInt += currentDayRewardsInt - - redis.Set(ctx, totalSlotRewardsKey, totalSlotRewards, 0) - - return totalSlotRewardsInt, nil - } - - parsedRewards, err := strconv.ParseInt(totalSlotRewards, 10, 64) - if err != nil { - return 0, err - } - - return parsedRewards, nil -} - -func FetchSlotRewardsPoints(slotId *big.Int) (*big.Int, error) { - var err error - - slotIdStr := slotId.String() - var points *big.Int - - retryErr := backoff.Retry(func() error { - points, err = prost.Instance.SlotRewardPoints(&bind.CallOpts{}, dataMarketAddress, slotId) - return err - }, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)) - - if retryErr != nil { - log.Errorf("Unable to query SlotRewardPoints for slot %s: %v", slotIdStr, retryErr) - return nil, retryErr - } - - return points, nil -} - -func FetchDayCounter() (*big.Int, error) { - var err error - var dayCounter *big.Int - - retryErr := backoff.Retry(func() error { - dayCounter, err = prost.Instance.DayCounter(&bind.CallOpts{}, dataMarketAddress) - return err - }, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)) - - if retryErr != nil { - return nil, fmt.Errorf("failed to fetch day counter: %v", retryErr) - } - - return dayCounter, nil -} - -func getDailySubmissions(slotId int, day *big.Int) int64 { - if val, err := redis.Get(context.Background(), redis.SlotSubmissionKey(strconv.Itoa(slotId), day.String())); err != nil || val == "" { - subs, err := prost.MustQuery[*big.Int](context.Background(), func(opts *bind.CallOpts) (*big.Int, error) { - subs, err := prost.Instance.SlotSubmissionCount(opts, dataMarketAddress, big.NewInt(int64(slotId)), day) - return subs, err - }) - if err != nil { - log.Errorln("Could not fetch submissions from contract: ", err.Error()) - return 0 - } - return subs.Int64() - } else { - submissions, _ := new(big.Int).SetString(val, 10) - return submissions.Int64() - } -} - -func handleTotalSubmissions(w http.ResponseWriter, r *http.Request) { - var request SubmissionsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - if request.PastDays < 1 { - http.Error(w, "Past days should be at least 1", http.StatusBadRequest) - return - } - - slotID := request.SlotID - if slotID < 1 || slotID > 10000 { - http.Error(w, fmt.Sprintf("Invalid slotId: %d", slotID), http.StatusBadRequest) - return - } - - var day *big.Int - dayStr, _ := redis.Get(context.Background(), pkgs.SequencerDayKey) - if dayStr == "" { - // TODO: Report unhandled error - day, _ = FetchDayCounter() - } else { - day, _ = new(big.Int).SetString(dayStr, 10) - } - - currentDay := new(big.Int).Set(day) - submissionsResponse := make([]DailySubmissions, request.PastDays) - - var wg sync.WaitGroup - ch := make(chan DailySubmissions, request.PastDays) - - for i := 0; i < request.PastDays; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - day := new(big.Int).Sub(currentDay, big.NewInt(int64(i))) - subs := getDailySubmissions(request.SlotID, day) - ch <- DailySubmissions{Day: int(day.Int64()), Submissions: subs} - }(i) - } - - go func() { - wg.Wait() - close(ch) - }() - - for submission := range ch { - submissionsResponse[int(currentDay.Int64())-submission.Day] = submission - } - - response := struct { - Info struct { - Success bool `json:"success"` - Response []DailySubmissions `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Response []DailySubmissions `json:"response"` - }{ - Success: true, - Response: submissionsResponse, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleFinalizedBatchSubmissions(w http.ResponseWriter, r *http.Request) { - var request PastEpochsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastEpochs := request.PastEpochs - if pastEpochs < 0 { - http.Error(w, "Past epochs should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.finalized_batch_submissions.*", pkgs.ProcessTriggerKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - logs = append(logs, logEntry) - } - - if pastEpochs > 0 && len(logs) > pastEpochs { - logs = logs[len(logs)-pastEpochs:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleTriggeredCollectionFlows(w http.ResponseWriter, r *http.Request) { - var request PastEpochsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastEpochs := request.PastEpochs - if pastEpochs < 0 { - http.Error(w, "Past epochs should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.triggered_collection_flow.*", pkgs.ProcessTriggerKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - logs = append(logs, logEntry) - } - - if pastEpochs > 0 && len(logs) > pastEpochs { - logs = logs[len(logs)-pastEpochs:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleBuiltBatches(w http.ResponseWriter, r *http.Request) { - var request PastBatchesRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastBatches := request.PastBatches - if pastBatches < 0 { - http.Error(w, "Past batches should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.build_batch.*", pkgs.ProcessTriggerKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - logs = append(logs, logEntry) - } - - if pastBatches > 0 && len(logs) > pastBatches { - logs = logs[len(logs)-pastBatches:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleCommittedSubmissionBatches(w http.ResponseWriter, r *http.Request) { - var request PastBatchesRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastBatches := request.PastBatches - if pastBatches < 0 { - http.Error(w, "Past batches should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.commit_submission_batch.*", pkgs.ProcessTriggerKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - logs = append(logs, logEntry) - } - - if pastBatches > 0 && len(logs) > pastBatches { - logs = logs[len(logs)-pastBatches:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleBatchResubmissions(w http.ResponseWriter, r *http.Request) { - var request PastBatchesRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastBatches := request.PastBatches - if pastBatches < 0 { - http.Error(w, "Past batches should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.batch_resubmission.*", pkgs.ProcessTriggerKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - logs = append(logs, logEntry) - } - - if pastBatches > 0 && len(logs) > pastBatches { - logs = logs[len(logs)-pastBatches:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleIncludedEpochSubmissionsCount(w http.ResponseWriter, r *http.Request) { - var request PastEpochsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastEpochs := request.PastEpochs - if pastEpochs < 0 { - http.Error(w, "Past epochs should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.build_batch.*", pkgs.ProcessTriggerKey)).Val() - - var totalSubmissions int - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - var logEntry map[string]interface{} - err = json.Unmarshal([]byte(entry), &logEntry) - if err != nil { - continue - } - - if epochIdStr, ok := logEntry["epoch_id"].(string); ok { - epochId, err := strconv.Atoi(epochIdStr) - if err != nil { - continue - } - if pastEpochs == 0 || epochId >= pastEpochs { - if count, ok := logEntry["submissions_count"].(float64); ok { - totalSubmissions += int(count) - } - } - } - } - - response := struct { - Info struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - }{ - Success: true, - TotalSubmissions: totalSubmissions, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleReceivedEpochSubmissionsCount(w http.ResponseWriter, r *http.Request) { - var request PastEpochsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastEpochs := request.PastEpochs - if pastEpochs < 0 { - http.Error(w, "Past epochs should be at least 0", http.StatusBadRequest) - return - } - - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.*", pkgs.EpochSubmissionsCountKey)).Val() - - var totalSubmissions int - - for _, key := range keys { - entry, err := redis.Get(context.Background(), key) - if err != nil { - continue - } - - epochId, err := strconv.Atoi(strings.Split(key, ".")[1]) - if err != nil { - continue - } - if pastEpochs == 0 || epochId >= pastEpochs { - if count, err := strconv.Atoi(entry); err == nil { - totalSubmissions += int(count) - } - } - } - - response := struct { - Info struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - }{ - Success: true, - TotalSubmissions: totalSubmissions, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleReceivedEpochSubmissions(w http.ResponseWriter, r *http.Request) { - var request PastEpochsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - pastEpochs := request.PastEpochs - if pastEpochs < 0 { - http.Error(w, "Past epochs should be at least 0", http.StatusBadRequest) - return - } - - // Fetch keys for epoch submissions - keys := redis.RedisClient.Keys(context.Background(), fmt.Sprintf("%s.*", pkgs.EpochSubmissionsKey)).Val() - - var logs []map[string]interface{} - - for _, key := range keys { - epochId := strings.TrimPrefix(key, fmt.Sprintf("%s.", pkgs.EpochSubmissionsKey)) - epochLog := make(map[string]interface{}) - epochLog["epoch_id"] = epochId - - submissions := redis.RedisClient.HGetAll(context.Background(), key).Val() - submissionsMap := make(map[string]interface{}) - for submissionId, submissionData := range submissions { - var submission interface{} - if err := json.Unmarshal([]byte(submissionData), &submission); err != nil { - continue - } - submissionsMap[submissionId] = submission - } - epochLog["submissions"] = submissionsMap - - logs = append(logs, epochLog) - } - - if pastEpochs > 0 && len(logs) > pastEpochs { - logs = logs[len(logs)-pastEpochs:] - } - - response := struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - }{ - Success: true, - Logs: logs, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -// FetchContractConstants fetches constants from the contract -func FetchContractConstants(ctx context.Context) error { - var err error - - dataMarketAddress = common.HexToAddress(config.SettingsObj.DataMarketAddress) - - snapshotQuotaErr := backoff.Retry(func() error { - dailySnapshotQuota, err = prost.Instance.DailySnapshotQuota(&bind.CallOpts{}, dataMarketAddress) - return err - }, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)) - - if snapshotQuotaErr != nil { - return fmt.Errorf("failed to fetch contract DailySnapshotQuota: %v", snapshotQuotaErr) - } - - rewardsErr := backoff.Retry(func() error { - rewardBasePoints, err = prost.Instance.RewardBasePoints(&bind.CallOpts{}, dataMarketAddress) - return err - }, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)) - - if rewardsErr != nil { - return fmt.Errorf("failed to fetch contract RewardBasePoints: %v", rewardsErr) - } - - return nil -} - -func FetchSlotSubmissionCount(slotID *big.Int, day *big.Int) (*big.Int, error) { - var count *big.Int - var err error - - retryErr := backoff.Retry(func() error { - count, err = prost.Instance.SlotSubmissionCount(&bind.CallOpts{}, dataMarketAddress, slotID, day) - return err - }, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 5)) - - if retryErr != nil { - return nil, fmt.Errorf("failed to fetch submission count: %v", retryErr) - } - - return count, nil -} - -func handleDailyRewards(w http.ResponseWriter, r *http.Request) { - var dailySubmissionCount int64 - - var request DailyRewardsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - slotID := request.SlotID - if slotID < 1 || slotID > 10000 { - http.Error(w, fmt.Sprintf("Invalid slotId: %d", slotID), http.StatusBadRequest) - return - } - - day := int64(request.Day) - - var rewards int - key := redis.SlotSubmissionKey(strconv.Itoa(slotID), strconv.Itoa(request.Day)) - - // Try to get from Redis - dailySubmissionCountFromCache, err := redis.Get(r.Context(), key) - - if err != nil || dailySubmissionCountFromCache == "" { - slotIDBigInt := big.NewInt(int64(slotID)) - count, err := FetchSlotSubmissionCount(slotIDBigInt, big.NewInt(day)) - if err != nil { - http.Error(w, "Failed to fetch submission count: "+err.Error(), http.StatusInternalServerError) - return - } - - dailySubmissionCount = count.Int64() - slotRewardsForDayKey := redis.SlotRewardsForDay(strconv.Itoa(slotID), strconv.Itoa(request.Day)) - redis.Set(r.Context(), slotRewardsForDayKey, strconv.FormatInt(dailySubmissionCount, 10), 0) - } else if dailySubmissionCountFromCache != "" { - dailyCount, err := strconv.ParseInt(dailySubmissionCountFromCache, 10, 64) - if err != nil { - log.Errorf("Failed to parse daily submission count from cache: %v", err) - } - dailySubmissionCount = dailyCount - } - - log.Debugln("DailySnapshotQuota: ", dailySnapshotQuota) - - cmp := big.NewInt(dailySubmissionCount).Cmp(dailySnapshotQuota) - if cmp >= 0 { - rewards = int(new(big.Int).Div(rewardBasePoints, big.NewInt(baseExponent)).Int64()) - } else { - rewards = 0 - } - - response := struct { - Info struct { - Success bool `json:"success"` - Response int `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Response int `json:"response"` - }{ - Success: true, - Response: rewards, - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func handleTotalRewards(w http.ResponseWriter, r *http.Request) { - var request RewardsRequest - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Authenticate token - if request.Token != config.SettingsObj.AuthReadToken { - http.Error(w, "Incorrect Token!", http.StatusUnauthorized) - return - } - - slotID := request.SlotID - if slotID < 1 || slotID > 10000 { - http.Error(w, fmt.Sprintf("Invalid slotId: %d", slotID), http.StatusBadRequest) - return - } - - slotRewardPoints, err := getTotalRewards(slotID) - if err != nil { - http.Error(w, "Failed to fetch slot reward points: "+err.Error(), http.StatusInternalServerError) - return - } - - response := struct { - Info struct { - Success bool `json:"success"` - Response int `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - }{ - Info: struct { - Success bool `json:"success"` - Response int `json:"response"` - }{ - Success: true, - Response: int(slotRewardPoints), - }, - RequestID: r.Context().Value("request_id").(string), - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -func RequestMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestID := uuid.New().String() - ctx := context.WithValue(r.Context(), "request_id", requestID) - r = r.WithContext(ctx) - - log.WithField("request_id", requestID).Infof("Request started for: %s", r.URL.Path) - - w.Header().Set("X-Request-ID", requestID) - - next.ServeHTTP(w, r) - - log.WithField("request_id", requestID).Infof("Request ended") - }) -} - -func StartApiServer() { - err := FetchContractConstants(context.Background()) - if err != nil { - log.Errorf("Failed to fetch contract constants: %v", err) - } - - mux := http.NewServeMux() - mux.HandleFunc("/getTotalRewards", handleTotalRewards) - mux.HandleFunc("/getDailyRewards", handleDailyRewards) - mux.HandleFunc("/totalSubmissions", handleTotalSubmissions) - mux.HandleFunc("/triggeredCollectionFlows", handleTriggeredCollectionFlows) - mux.HandleFunc("/finalizedBatchSubmissions", handleFinalizedBatchSubmissions) - mux.HandleFunc("/builtBatches", handleBuiltBatches) - mux.HandleFunc("/committedSubmissionBatches", handleCommittedSubmissionBatches) - mux.HandleFunc("/batchResubmissions", handleBatchResubmissions) - mux.HandleFunc("/receivedEpochSubmissions", handleReceivedEpochSubmissions) - mux.HandleFunc("/receivedEpochSubmissionsCount", handleReceivedEpochSubmissionsCount) - mux.HandleFunc("/includedEpochSubmissionsCount", handleIncludedEpochSubmissionsCount) - // also add submission body per epoch - handler := RequestMiddleware(mux) - - log.Println("Server is running on port 9988") - log.Fatal(http.ListenAndServe(":9988", handler)) -} diff --git a/pkgs/service/api_test.go b/pkgs/service/api_test.go deleted file mode 100644 index 18721ca..0000000 --- a/pkgs/service/api_test.go +++ /dev/null @@ -1,1383 +0,0 @@ -package service - -import ( - "Listen/config" - "Listen/pkgs" - "Listen/pkgs/prost" - "Listen/pkgs/redis" - "Listen/pkgs/utils" - "bytes" - "context" - "encoding/json" - "github.com/alicebob/miniredis/v2" - redisv8 "github.com/go-redis/redis/v8" - log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "math/big" - "net/http" - "net/http/httptest" - "strconv" - "strings" - "testing" - "time" -) - -var mr *miniredis.Miniredis - -func TestMain(m *testing.M) { - var err error - mr, err = miniredis.Run() - if err != nil { - log.Fatalf("could not start miniredis: %v", err) - } - - // Initialize the config settings - config.SettingsObj = &config.Settings{ - ContractAddress: "0x10c5E2ee14006B3860d4FdF6B173A30553ea6333", - AuthReadToken: "valid-token", - RedisHost: mr.Host(), - RedisPort: mr.Port(), - } - - redis.RedisClient = redis.NewRedisClient() - utils.InitLogger() - - prost.ConfigureClient() - prost.ConfigureContractInstance() - - m.Run() - - mr.Close() -} - -func setupRedisForGetTotalRewardsTest(t *testing.T, slotID int, totalRewards string) { - t.Helper() - - // Create a mini Redis server for testing - mr.FlushDB() - - redis.RedisClient = redisv8.NewClient(&redisv8.Options{ - Addr: mr.Addr(), - }) - utils.InitLogger() - - // Populate Redis with test data - key := redis.TotalSlotRewards(strconv.Itoa(slotID)) - err := redis.Set(context.Background(), key, totalRewards, 0) - if err != nil { - t.Fatalf("Failed to set test data in redis: %v", err) - } -} - -func TestHandleTotalRewards(t *testing.T) { - tests := []struct { - name string - slotID int - totalRewards string - expectedStatus int - expectedReward int - }{ - { - name: "Valid total rewards", - slotID: 1, - totalRewards: "500", - expectedStatus: http.StatusOK, - expectedReward: 500, - }, - { - name: "Empty total rewards", - slotID: 2, - totalRewards: "", - expectedStatus: http.StatusInternalServerError, - expectedReward: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - setupRedisForGetTotalRewardsTest(t, tt.slotID, tt.totalRewards) - - // Set up a valid request body - requestBody := RewardsRequest{ - SlotID: tt.slotID, - Token: config.SettingsObj.AuthReadToken, - } - reqBody, err := json.Marshal(requestBody) - if err != nil { - t.Fatalf("could not marshal request body: %v", err) - } - - // Create a new HTTP request - req, err := http.NewRequest("POST", "/getTotalRewards", bytes.NewBuffer(reqBody)) - if err != nil { - t.Fatalf("could not create HTTP request: %v", err) - } - - // Create a ResponseRecorder to record the response - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleTotalRewards) - - // Wrap the handler with the middleware - testHandler := RequestMiddleware(handler) - - // Call the handler - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - // Check the status code - if status := rr.Code; status != tt.expectedStatus { - t.Errorf("handler returned wrong status code: got %v want %v", status, tt.expectedStatus) - } - - // Parse the response body - var response struct { - Info struct { - Success bool `json:"success"` - Response int `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - if err != nil { - t.Fatalf("could not decode response body: %v", err) - } - - // Validate the response - if tt.expectedStatus == http.StatusOK && !response.Info.Success { - t.Errorf("response success should be true") - } - if response.Info.Response != tt.expectedReward { - t.Errorf("response rewards should match expected value: got %v want %v", response.Info.Response, tt.expectedReward) - } - }) - } -} - -func setupRedisForDailyRewardsTest(t *testing.T, slotID int, day int, dailySubmissionCount string) { - t.Helper() - - // Create a mini Redis server for testing - mr.FlushDB() - - redis.RedisClient = redisv8.NewClient(&redisv8.Options{ - Addr: mr.Addr(), - }) - utils.InitLogger() - - // Mock daily snapshot quota and reward base points - dailySnapshotQuota = big.NewInt(10) - rewardBasePoints = big.NewInt(1000) - - // Populate Redis with test data - key := redis.SlotSubmissionKey(strconv.Itoa(slotID), strconv.Itoa(day)) - err := redis.Set(context.Background(), key, dailySubmissionCount, 0) - if err != nil { - t.Fatalf("Failed to set test data in redis: %v", err) - } -} - -func TestHandleDailyRewards(t *testing.T) { - tests := []struct { - name string - slotID int - day int - dailySubmissionCount string - expectedStatus int - expectedReward int64 - expectedSuccessStatus bool - }{ - { - name: "Valid daily rewards", - slotID: 1, - day: 10, - dailySubmissionCount: "15", - expectedStatus: http.StatusOK, - expectedReward: 1000, - expectedSuccessStatus: true, - }, - { - name: "Empty daily submission count", - slotID: 2, - day: 10, - dailySubmissionCount: "", - expectedStatus: http.StatusInternalServerError, - expectedReward: 0, - expectedSuccessStatus: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - setupRedisForDailyRewardsTest(t, tt.slotID, tt.day, tt.dailySubmissionCount) - - // Set up a valid request body - requestBody := DailyRewardsRequest{ - SlotID: tt.slotID, - Day: tt.day, - Token: config.SettingsObj.AuthReadToken, - } - reqBody, err := json.Marshal(requestBody) - if err != nil { - t.Fatalf("could not marshal request body: %v", err) - } - - // Create a new HTTP request - req, err := http.NewRequest("POST", "/getDailyRewards", bytes.NewBuffer(reqBody)) - if err != nil { - t.Fatalf("could not create HTTP request: %v", err) - } - - // Create a ResponseRecorder to record the response - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleDailyRewards) - - // Wrap the handler with the middleware - testHandler := RequestMiddleware(handler) - - // Call the handler - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - // Check the status code - if status := rr.Code; status != tt.expectedStatus { - t.Errorf("handler returned wrong status code: got %v want %v", status, tt.expectedStatus) - } - - // Parse the response body - var response struct { - Info struct { - Success bool `json:"success"` - Response int `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - if err != nil { - t.Fatalf("could not decode response body: %v", err) - } - - // Validate the response - if response.Info.Success != tt.expectedSuccessStatus { - t.Errorf("response success status should match expected value: got %v want %v", response.Info.Success, tt.expectedSuccessStatus) - } - if int64(response.Info.Response) != tt.expectedReward { - t.Errorf("response rewards should match expected value: got %v want %v", response.Info.Response, tt.expectedReward) - } - }) - } -} - -func TestHandleTotalSubmissions(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.Set(context.Background(), pkgs.SequencerDayKey, "5", 0) - - redis.Set(context.Background(), redis.SlotSubmissionKey("1", "5"), "100", 0) - redis.Set(context.Background(), redis.SlotSubmissionKey("1", "4"), "120", 0) - redis.Set(context.Background(), redis.SlotSubmissionKey("1", "3"), "80", 0) - redis.Set(context.Background(), redis.SlotSubmissionKey("1", "2"), "400", 0) - redis.Set(context.Background(), redis.SlotSubmissionKey("1", "1"), "25", 0) - - tests := []struct { - name string - body string - statusCode int - response []DailySubmissions - }{ - { - name: "Valid token, past days 1", - body: `{"slot_id": 1, "token": "valid-token", "past_days": 1}`, - statusCode: http.StatusOK, - response: []DailySubmissions{ - {Day: 5, Submissions: 100}, - }, - }, - { - name: "Valid token, past days 3", - body: `{"slot_id": 1, "token": "valid-token", "past_days": 3}`, - statusCode: http.StatusOK, - response: []DailySubmissions{ - {Day: 5, Submissions: 100}, - {Day: 4, Submissions: 120}, - {Day: 3, Submissions: 80}, - }, - }, - { - name: "Valid token, total submissions till date", - body: `{"slot_id": 1, "token": "valid-token", "past_days": 5}`, - statusCode: http.StatusOK, - response: []DailySubmissions{ - {Day: 5, Submissions: 100}, - {Day: 4, Submissions: 120}, - {Day: 3, Submissions: 80}, - {Day: 2, Submissions: 400}, - {Day: 1, Submissions: 25}, - }, - }, - { - name: "Valid token, negative past days", - body: `{"slot_id": 1, "token": "valid-token", "past_days": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - { - name: "Invalid token", - body: `{"slot_id": 1, "token": "invalid-token", "past_days": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Invalid slot ID", - body: `{"slot_id": 10001, "token": "valid-token", "past_days": 1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/totalSubmissions", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleTotalSubmissions) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Response []DailySubmissions `json:"response"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - - err := json.Unmarshal([]byte(responseBody), &response) - assert.NoError(t, err) - assert.Equal(t, tt.response, response.Info.Response) - } - }) - } -} - -func TestHandleFinalizedBatchSubmissions(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("finalized_batch_submissions", "1"), - map[string]interface{}{ - "epoch_id": "1", - "finalized_batches_count": 3, - "finalized_batch_ids": []string{"1", "2", "3"}, - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("finalized_batch_submissions", "2"), - map[string]interface{}{ - "epoch_id": "2", - "finalized_batches_count": 1, - "finalized_batch_ids": []string{"4"}, - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("finalized_batch_submissions", "3"), - map[string]interface{}{ - "epoch_id": "3", - "finalized_batches_count": 5, - "finalized_batch_ids": []string{"5", "6", "7", "8", "9"}, - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past epochs 1", - body: `{"token": "valid-token", "past_epochs": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "3", - "finalized_batches_count": 5, - "finalized_batch_ids": []interface{}{"5", "6", "7", "8", "9"}, - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Valid token, past epochs 0", - body: `{"token": "valid-token", "past_batches": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "1", - "finalized_batches_count": 3, - "finalized_batch_ids": []string{"1", "2", "3"}, - "timestamp": float64(123456), - }, - { - "epoch_id": "2", - "finalized_batches_count": 1, - "finalized_batch_ids": []string{"4"}, - "timestamp": float64(12345678), - }, - { - "epoch_id": "3", - "finalized_batches_count": 5, - "finalized_batch_ids": []interface{}{"5", "6", "7", "8", "9"}, - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_epochs": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past batches", - body: `{"token": "valid-token", "past_epochs": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/finalizedBatchSubmissions", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleFinalizedBatchSubmissions) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.Equal(t, expectedResp, actualResp) - } - }) - } -} - -func TestHandleTriggeredCollectionFlows(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("triggered_collection_flow", "1"), - map[string]interface{}{ - "epoch_id": "12", - "start_block": "100", - "current_block": "220", - "header_count": "120", - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("triggered_collection_flow", "2"), - map[string]interface{}{ - "epoch_id": "13", - "start_block": "220", - "current_block": "340", - "header_count": "120", - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("triggered_collection_flow", "3"), - map[string]interface{}{ - "epoch_id": "14", - "start_block": "340", - "current_block": "460", - "header_count": "120", - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past epochs 1", - body: `{"token": "valid-token", "past_epochs": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "14", - "start_block": "340", - "current_block": "460", - "header_count": "120", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Valid token, past epochs 0", - body: `{"token": "valid-token", "past_epochs": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "12", - "start_block": "100", - "current_block": "220", - "header_count": "120", - "timestamp": float64(123456), - }, - { - "epoch_id": "13", - "start_block": "220", - "current_block": "340", - "header_count": "120", - "timestamp": float64(12345678), - }, - { - "epoch_id": "14", - "start_block": "340", - "current_block": "460", - "header_count": "120", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_epochs": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past epochs", - body: `{"token": "valid-token", "past_epochs": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/triggeredCollectionFlows", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleTriggeredCollectionFlows) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.Equal(t, expectedResp, actualResp) - } - }) - } -} - -func TestHandleBuiltBatches(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "1"), - map[string]interface{}{ - "epoch_id": "1", - "batch_id": 1, - "batch_cid": "cid1", - "submissions_count": 3, - "submissions": []interface{}{"sub1", "sub2", "sub3"}, - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "2"), - map[string]interface{}{ - "epoch_id": "2", - "batch_id": 2, - "batch_cid": "cid2", - "submissions_count": 1, - "submissions": []interface{}{"sub4"}, - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "3"), - map[string]interface{}{ - "epoch_id": "3", - "batch_id": 3, - "batch_cid": "cid3", - "submissions_count": 5, - "submissions": []interface{}{"sub5", "sub6", "sub7", "sub8", "sub9"}, - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past batches 1", - body: `{"token": "valid-token", "past_batches": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "3", - "batch_id": float64(3), - "batch_cid": "cid3", - "submissions_count": float64(5), - "submissions": []interface{}{"sub5", "sub6", "sub7", "sub8", "sub9"}, - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Valid token, past batches 0", - body: `{"token": "valid-token", "past_batches": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "1", - "batch_id": float64(1), - "batch_cid": "cid1", - "submissions_count": float64(3), - "submissions": []interface{}{"sub1", "sub2", "sub3"}, - "timestamp": float64(123456), - }, - { - "epoch_id": "2", - "batch_id": float64(2), - "batch_cid": "cid2", - "submissions_count": float64(1), - "submissions": []interface{}{"sub4"}, - "timestamp": float64(12345678), - }, - { - "epoch_id": "3", - "batch_id": float64(3), - "batch_cid": "cid3", - "submissions_count": float64(5), - "submissions": []interface{}{"sub5", "sub6", "sub7", "sub8", "sub9"}, - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_batches": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past batches", - body: `{"token": "valid-token", "past_batches": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/builtBatches", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleBuiltBatches) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.Equal(t, expectedResp, actualResp) - } - }) - } -} - -func TestHandleCommittedSubmissionBatches(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("commit_submission_batch", "1"), - map[string]interface{}{ - "epoch_id": "1", - "batch_id": 1, - "tx_hash": "0x123", - "signer": "0xabc", - "nonce": "1", - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("commit_submission_batch", "2"), - map[string]interface{}{ - "epoch_id": "2", - "batch_id": 2, - "tx_hash": "0x456", - "signer": "0xdef", - "nonce": "2", - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("commit_submission_batch", "3"), - map[string]interface{}{ - "epoch_id": "3", - "batch_id": 3, - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past batches 1", - body: `{"token": "valid-token", "past_batches": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "3", - "batch_id": float64(3), - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Valid token, past batches 0", - body: `{"token": "valid-token", "past_batches": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "1", - "batch_id": float64(1), - "tx_hash": "0x123", - "signer": "0xabc", - "nonce": "1", - "timestamp": float64(123456), - }, - { - "epoch_id": "2", - "batch_id": float64(2), - "tx_hash": "0x456", - "signer": "0xdef", - "nonce": "2", - "timestamp": float64(12345678), - }, - { - "epoch_id": "3", - "batch_id": float64(3), - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_batches": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past batches", - body: `{"token": "valid-token", "past_batches": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/committedSubmissionBatches", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleCommittedSubmissionBatches) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.Equal(t, expectedResp, actualResp) - } - }) - } -} - -func TestHandleBatchResubmissions(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("batch_resubmission", "1"), - map[string]interface{}{ - "epoch_id": "1", - "batch_id": 1, - "tx_hash": "0x123", - "signer": "0xabc", - "nonce": "1", - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("batch_resubmission", "2"), - map[string]interface{}{ - "epoch_id": "2", - "batch_id": 2, - "tx_hash": "0x456", - "signer": "0xdef", - "nonce": "2", - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("batch_resubmission", "3"), - map[string]interface{}{ - "epoch_id": "3", - "batch_id": 3, - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past batches 1", - body: `{"token": "valid-token", "past_batches": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "3", - "batch_id": float64(3), - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Valid token, past batches 0", - body: `{"token": "valid-token", "past_batches": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "1", - "batch_id": float64(1), - "tx_hash": "0x123", - "signer": "0xabc", - "nonce": "1", - "timestamp": float64(123456), - }, - { - "epoch_id": "2", - "batch_id": float64(2), - "tx_hash": "0x456", - "signer": "0xdef", - "nonce": "2", - "timestamp": float64(12345678), - }, - { - "epoch_id": "3", - "batch_id": float64(3), - "tx_hash": "0x789", - "signer": "0xghi", - "nonce": "3", - "timestamp": float64(123456789), - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_batches": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past batches", - body: `{"token": "valid-token", "past_batches": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/batchResubmissions", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleBatchResubmissions) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.Equal(t, expectedResp, actualResp) - } - }) - } -} - -func TestHandleIncludedEpochSubmissionsCount(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "1"), - map[string]interface{}{ - "epoch_id": "1", - "batch_id": 1, - "batch_cid": "cid1", - "submissions_count": 3, - "submissions": []interface{}{"sub1", "sub2", "sub3"}, - "timestamp": float64(123456), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "2"), - map[string]interface{}{ - "epoch_id": "2", - "batch_id": 2, - "batch_cid": "cid2", - "submissions_count": 1, - "submissions": []interface{}{"sub4"}, - "timestamp": float64(12345678), - }, - 0, - ) - - redis.SetProcessLog(context.Background(), - redis.TriggeredProcessLog("build_batch", "3"), - map[string]interface{}{ - "epoch_id": "3", - "batch_id": 3, - "batch_cid": "cid3", - "submissions_count": 5, - "submissions": []interface{}{"sub5", "sub6", "sub7", "sub8", "sub9"}, - "timestamp": float64(123456789), - }, - 0, - ) - - tests := []struct { - name string - body string - statusCode int - response int - }{ - { - name: "Valid token, all epochs", - body: `{"token": "valid-token", "past_epochs": 0}`, - statusCode: http.StatusOK, - response: 9, - }, - { - name: "Valid token, specific epochs", - body: `{"token": "valid-token", "past_epochs": 2}`, - statusCode: http.StatusOK, - response: 6, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_epochs": 1}`, - statusCode: http.StatusUnauthorized, - response: 0, - }, - { - name: "Negative past epochs", - body: `{"token": "valid-token", "past_epochs": -1}`, - statusCode: http.StatusBadRequest, - response: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/includedEpochSubmissions", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleIncludedEpochSubmissionsCount) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - assert.Equal(t, tt.response, response.Info.TotalSubmissions) - } - }) - } -} - -func TestHandleReceivedEpochSubmissionsCount(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - redis.Set(context.Background(), redis.EpochSubmissionsCount(1), "3", time.Hour) - redis.Set(context.Background(), redis.EpochSubmissionsCount(2), "1", time.Hour) - redis.Set(context.Background(), redis.EpochSubmissionsCount(3), "5", time.Hour) - - tests := []struct { - name string - body string - statusCode int - response int - }{ - { - name: "Valid token, all epochs", - body: `{"token": "valid-token", "past_epochs": 0}`, - statusCode: http.StatusOK, - response: 9, - }, - { - name: "Valid token, specific epochs", - body: `{"token": "valid-token", "past_epochs": 2}`, - statusCode: http.StatusOK, - response: 6, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_epochs": 1}`, - statusCode: http.StatusUnauthorized, - response: 0, - }, - { - name: "Negative past epochs", - body: `{"token": "valid-token", "past_epochs": -1}`, - statusCode: http.StatusBadRequest, - response: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/receivedEpochSubmissionsCount", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleReceivedEpochSubmissionsCount) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - TotalSubmissions int `json:"total_submissions"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - assert.Equal(t, tt.response, response.Info.TotalSubmissions) - } - }) - } -} - -func TestHandleReceivedEpochSubmissions(t *testing.T) { - config.SettingsObj.AuthReadToken = "valid-token" - - // Set test data in Redis - redis.RedisClient.HSet(context.Background(), redis.EpochSubmissionsKey(1), "submission1", `{"request": {"slotId": 1, "epochId": 1}}`) - redis.RedisClient.HSet(context.Background(), redis.EpochSubmissionsKey(1), "submission2", `{"request": {"slotId": 1, "epochId": 1}}`) - redis.RedisClient.HSet(context.Background(), redis.EpochSubmissionsKey(2), "submission3", `{"request": {"slotId": 1, "epochId": 2}}`) - redis.RedisClient.HSet(context.Background(), redis.EpochSubmissionsKey(2), "submission4", `{"request": {"slotId": 1, "epochId": 2}}`) - redis.RedisClient.HSet(context.Background(), redis.EpochSubmissionsKey(3), "submission5", `{"request": {"slotId": 1, "epochId": 3}}`) - - tests := []struct { - name string - body string - statusCode int - response []map[string]interface{} - }{ - { - name: "Valid token, past epochs 1", - body: `{"token": "valid-token", "past_epochs": 1}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "3", - "submissions": map[string]interface{}{ - "submission5": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 3, - }, - }, - }, - }, - }, - }, - { - name: "Valid token, past epochs 0", - body: `{"token": "valid-token", "past_epochs": 0}`, - statusCode: http.StatusOK, - response: []map[string]interface{}{ - { - "epoch_id": "1", - "submissions": map[string]interface{}{ - "submission1": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 1, - }, - }, - "submission2": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 1, - }, - }, - }, - }, - { - "epoch_id": "2", - "submissions": map[string]interface{}{ - "submission3": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 2, - }, - }, - "submission4": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 2, - }, - }, - }, - }, - { - "epoch_id": "3", - "submissions": map[string]interface{}{ - "submission5": map[string]interface{}{ - "request": map[string]interface{}{ - "slotId": 1, - "epochId": 3, - }, - }, - }, - }, - }, - }, - { - name: "Invalid token", - body: `{"token": "invalid-token", "past_epochs": 1}`, - statusCode: http.StatusUnauthorized, - response: nil, - }, - { - name: "Negative past epochs", - body: `{"token": "valid-token", "past_epochs": -1}`, - statusCode: http.StatusBadRequest, - response: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest("POST", "/receivedEpochSubmissions", strings.NewReader(tt.body)) - if err != nil { - t.Fatal(err) - } - req.Header.Set("Content-Type", "application/json") - - rr := httptest.NewRecorder() - handler := http.HandlerFunc(handleReceivedEpochSubmissions) - testHandler := RequestMiddleware(handler) - testHandler.ServeHTTP(rr, req) - - responseBody := rr.Body.String() - t.Log("Response Body:", responseBody) - - assert.Equal(t, tt.statusCode, rr.Code) - - if tt.statusCode == http.StatusOK { - var response struct { - Info struct { - Success bool `json:"success"` - Logs []map[string]interface{} `json:"logs"` - } `json:"info"` - RequestID string `json:"request_id"` - } - err = json.NewDecoder(rr.Body).Decode(&response) - assert.NoError(t, err) - actualResp, _ := json.Marshal(tt.response) - expectedResp, _ := json.Marshal(response.Info.Logs) - assert.JSONEq(t, string(expectedResp), string(actualResp)) - } - }) - } -}