diff --git a/disperser/dataapi/docs/v2/V2_docs.go b/disperser/dataapi/docs/v2/V2_docs.go index 19f533672e..e6fad337b5 100644 --- a/disperser/dataapi/docs/v2/V2_docs.go +++ b/disperser/dataapi/docs/v2/V2_docs.go @@ -693,8 +693,8 @@ const docTemplateV2 = `{ } ] }, - "reservation_period": { - "description": "ReservationPeriod represents the range of time at which the dispersal is made", + "timestamp": { + "description": "Timestamp represents the nanosecond of the dispersal request creation", "type": "integer" } } @@ -879,10 +879,6 @@ const docTemplateV2 = `{ "items": { "type": "integer" } - }, - "salt": { - "description": "Salt is used to make blob intentionally unique when everything else is the same", - "type": "integer" } } }, @@ -1326,6 +1322,9 @@ const docTemplateV2 = `{ "v2.OperatorStake": { "type": "object", "properties": { + "operator_address": { + "type": "string" + }, "operator_id": { "type": "string" }, diff --git a/disperser/dataapi/docs/v2/V2_swagger.json b/disperser/dataapi/docs/v2/V2_swagger.json index ebea1f42c1..39fbf1a6fe 100644 --- a/disperser/dataapi/docs/v2/V2_swagger.json +++ b/disperser/dataapi/docs/v2/V2_swagger.json @@ -690,8 +690,8 @@ } ] }, - "reservation_period": { - "description": "ReservationPeriod represents the range of time at which the dispersal is made", + "timestamp": { + "description": "Timestamp represents the nanosecond of the dispersal request creation", "type": "integer" } } @@ -1319,6 +1319,9 @@ "v2.OperatorStake": { "type": "object", "properties": { + "operator_address": { + "type": "string" + }, "operator_id": { "type": "string" }, diff --git a/disperser/dataapi/docs/v2/V2_swagger.yaml b/disperser/dataapi/docs/v2/V2_swagger.yaml index 9d3a4d77f7..5ce0aad74c 100644 --- a/disperser/dataapi/docs/v2/V2_swagger.yaml +++ b/disperser/dataapi/docs/v2/V2_swagger.yaml @@ -24,9 +24,9 @@ definitions: - $ref: '#/definitions/big.Int' description: CumulativePayment represents the total amount of payment (in wei) made by the user up to this point - reservation_period: - description: ReservationPeriod represents the range of time at which the dispersal - is made + timestamp: + description: Timestamp represents the nanosecond of the dispersal request + creation type: integer type: object core.Signature: @@ -457,6 +457,8 @@ definitions: type: object v2.OperatorStake: properties: + operator_address: + type: string operator_id: type: string quorum_id: diff --git a/disperser/dataapi/operator_handler.go b/disperser/dataapi/operator_handler.go index 59867ff02f..70fb4d116d 100644 --- a/disperser/dataapi/operator_handler.go +++ b/disperser/dataapi/operator_handler.go @@ -227,8 +227,7 @@ func (oh *OperatorHandler) GetOperatorsStakeAtBlock(ctx context.Context, operato return nil, fmt.Errorf("failed to fetch indexed operator state: %w", err) } - tqs, quorumsStake := operators.GetRankedOperators(state) - oh.metrics.UpdateOperatorsStake(tqs, quorumsStake) + _, quorumsStake := operators.GetRankedOperators(state) stakeRanked := make(map[string][]*OperatorStake) for q, operators := range quorumsStake { diff --git a/disperser/dataapi/server.go b/disperser/dataapi/server.go index ca63b0de90..5eeaa298af 100644 --- a/disperser/dataapi/server.go +++ b/disperser/dataapi/server.go @@ -125,6 +125,7 @@ type ( OperatorStake struct { QuorumId string `json:"quorum_id"` OperatorId string `json:"operator_id"` + OperatorAddress string `json:"operator_address"` StakePercentage float64 `json:"stake_percentage"` Rank int `json:"rank"` } diff --git a/disperser/dataapi/v2/operators.go b/disperser/dataapi/v2/operators.go index cce9b1a147..f43490a6aa 100644 --- a/disperser/dataapi/v2/operators.go +++ b/disperser/dataapi/v2/operators.go @@ -156,6 +156,7 @@ func (s *ServerV2) FetchOperatorSigningInfo(c *gin.Context) { // @Router /operators/stake [get] func (s *ServerV2) FetchOperatorsStake(c *gin.Context) { handlerStart := time.Now() + ctx := c.Request.Context() operatorId := c.DefaultQuery("operator_id", "") s.logger.Info("getting operators stake distribution", "operatorId", operatorId) @@ -166,7 +167,7 @@ func (s *ServerV2) FetchOperatorsStake(c *gin.Context) { errorResponse(c, fmt.Errorf("failed to get current block number: %w", err)) return } - operatorsStakeResponse, err := s.operatorHandler.GetOperatorsStake(c.Request.Context(), operatorId) + operatorsStakeResponse, err := s.operatorHandler.GetOperatorsStakeAtBlock(ctx, operatorId, uint32(currentBlock)) if err != nil { s.metrics.IncrementFailedRequestNum("FetchOperatorsStake") errorResponse(c, fmt.Errorf("failed to get operator stake: %w", err)) @@ -174,6 +175,41 @@ func (s *ServerV2) FetchOperatorsStake(c *gin.Context) { } operatorsStakeResponse.CurrentBlock = uint32(currentBlock) + // Get operators' addresses in batch + operatorsSeen := make(map[string]struct{}, 0) + for _, ops := range operatorsStakeResponse.StakeRankedOperators { + for _, op := range ops { + operatorsSeen[op.OperatorId] = struct{}{} + } + } + operatorIDs := make([]core.OperatorID, 0) + for id := range operatorsSeen { + opId, err := core.OperatorIDFromHex(id) + if err != nil { + s.metrics.IncrementFailedRequestNum("FetchOperatorsStake") + errorResponse(c, fmt.Errorf("malformed operator ID: %w", err)) + return + } + operatorIDs = append(operatorIDs, opId) + } + // Get the address for the operators. + // operatorAddresses[i] is the address for operatorIDs[i]. + operatorAddresses, err := s.chainReader.BatchOperatorIDToAddress(ctx, operatorIDs) + if err != nil { + s.metrics.IncrementFailedRequestNum("FetchOperatorsStake") + errorResponse(c, fmt.Errorf("failed to get operator addresses from IDs: %w", err)) + return + } + idToAddress := make(map[string]string, 0) + for i := range operatorIDs { + idToAddress[operatorIDs[i].Hex()] = operatorAddresses[i].Hex() + } + for _, ops := range operatorsStakeResponse.StakeRankedOperators { + for _, op := range ops { + op.OperatorAddress = idToAddress[op.OperatorId] + } + } + s.metrics.IncrementSuccessfulRequestNum("FetchOperatorsStake") s.metrics.ObserveLatency("FetchOperatorsStake", time.Since(handlerStart)) c.Writer.Header().Set(cacheControlParam, fmt.Sprintf("max-age=%d", maxOperatorsStakeAge)) diff --git a/disperser/dataapi/v2/server_v2.go b/disperser/dataapi/v2/server_v2.go index 6da01af0bd..bd8bff4405 100644 --- a/disperser/dataapi/v2/server_v2.go +++ b/disperser/dataapi/v2/server_v2.go @@ -139,6 +139,7 @@ type ( OperatorStake struct { QuorumId string `json:"quorum_id"` OperatorId string `json:"operator_id"` + OperatorAddress string `json:"operator_address"` StakePercentage float64 `json:"stake_percentage"` Rank int `json:"rank"` } diff --git a/disperser/dataapi/v2/server_v2_test.go b/disperser/dataapi/v2/server_v2_test.go index 16030aeaa2..5e6cbf5f4d 100644 --- a/disperser/dataapi/v2/server_v2_test.go +++ b/disperser/dataapi/v2/server_v2_test.go @@ -1648,26 +1648,57 @@ func TestFetchOperatorsStake(t *testing.T) { mockIndexedChainState.On("GetCurrentBlockNumber").Return(uint(1), nil) + addr0 := gethcommon.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa") + addr1 := gethcommon.HexToAddress("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + mockTx.On("BatchOperatorIDToAddress").Return( + func(ids []core.OperatorID) []gethcommon.Address { + result := make([]gethcommon.Address, len(ids)) + for i, id := range ids { + if id == opId0 { + result[i] = addr0 + } else if id == opId1 { + result[i] = addr1 + } else { + result[i] = gethcommon.Address{} + } + } + return result + }, + nil, + ) + r.GET("/v2/operators/stake", testDataApiServerV2.FetchOperatorsStake) w := executeRequest(t, r, http.MethodGet, "/v2/operators/stake") response := decodeResponseBody[dataapi.OperatorsStakeResponse](t, w) // The quorums and the operators in the quorum are defined in "mockChainState" - // There are 3 quorums (0, 1) and a "total" entry for TotalQuorumStake + // There are 2 quorums (0, 1) require.Equal(t, 2, len(response.StakeRankedOperators)) + checkAddress := func(op *dataapi.OperatorStake) { + if op.OperatorId == opId0.Hex() { + assert.Equal(t, addr0.Hex(), op.OperatorAddress) + } + if op.OperatorId == opId1.Hex() { + assert.Equal(t, addr1.Hex(), op.OperatorAddress) + } + } // Quorum 0 ops, ok := response.StakeRankedOperators["0"] require.True(t, ok) require.Equal(t, 2, len(ops)) assert.Equal(t, opId0.Hex(), ops[0].OperatorId) assert.Equal(t, opId1.Hex(), ops[1].OperatorId) + checkAddress(ops[0]) + checkAddress(ops[1]) // Quorum 1 ops, ok = response.StakeRankedOperators["1"] require.True(t, ok) require.Equal(t, 2, len(ops)) assert.Equal(t, opId1.Hex(), ops[0].OperatorId) assert.Equal(t, opId0.Hex(), ops[1].OperatorId) + checkAddress(ops[0]) + checkAddress(ops[1]) } func TestFetchMetricsSummary(t *testing.T) {