diff --git a/.changelog/1199.feature.md b/.changelog/1199.feature.md new file mode 100644 index 000000000..1c617a872 --- /dev/null +++ b/.changelog/1199.feature.md @@ -0,0 +1 @@ +`/v1/stats/tx_volume` Endpoint returns tx_volume for all layers diff --git a/api/spec/v1.yaml b/api/spec/v1.yaml index b72eca0aa..9c5bf2ba2 100644 --- a/api/spec/v1.yaml +++ b/api/spec/v1.yaml @@ -1650,6 +1650,26 @@ paths: $ref: '#/components/schemas/RoflMarketInstanceList' <<: *common_error_responses + /stats/tx_volume: + get: + summary: | + Returns a timeline of the transaction volume at the chosen granularity + combined for all layers. + parameters: + - *limit + - *offset + - *window_size_seconds + - *window_step_seconds + responses: + '200': + description: | + A JSON object containing a list of TPS values for each interval. + content: + application/json: + schema: + $ref: '#/components/schemas/TxVolumeList' + <<: *common_error_responses + /{layer}/stats/tx_volume: get: summary: | diff --git a/api/v1/strict_server.go b/api/v1/strict_server.go index 1b07b02b4..8382e9954 100644 --- a/api/v1/strict_server.go +++ b/api/v1/strict_server.go @@ -228,13 +228,27 @@ func (srv *StrictServerImpl) GetConsensusProposalsProposalIdVotes(ctx context.Co return apiTypes.GetConsensusProposalsProposalIdVotes200JSONResponse(*votes), nil } +func (srv *StrictServerImpl) GetStatsTxVolume(ctx context.Context, request apiTypes.GetStatsTxVolumeRequestObject) (apiTypes.GetStatsTxVolumeResponseObject, error) { + params := apiTypes.GetLayerStatsTxVolumeParams{ + WindowStepSeconds: request.Params.WindowStepSeconds, + WindowSizeSeconds: request.Params.WindowSizeSeconds, + Limit: request.Params.Limit, + Offset: request.Params.Offset, + } + volumeList, err := srv.dbClient.TxVolumes(ctx, nil, params) + if err != nil { + return nil, err + } + return apiTypes.GetStatsTxVolume200JSONResponse(*volumeList), nil +} + func (srv *StrictServerImpl) GetLayerStatsTxVolume(ctx context.Context, request apiTypes.GetLayerStatsTxVolumeRequestObject) (apiTypes.GetLayerStatsTxVolumeResponseObject, error) { // Additional param validation. if !request.Layer.IsValid() { return nil, &apiTypes.InvalidParamFormatError{ParamName: "layer", Err: fmt.Errorf("not a valid enum value: %s", request.Layer)} } - volumeList, err := srv.dbClient.TxVolumes(ctx, request.Layer, request.Params) + volumeList, err := srv.dbClient.TxVolumes(ctx, &request.Layer, request.Params) if err != nil { return nil, err } diff --git a/storage/client/client.go b/storage/client/client.go index d558315ce..509343848 100644 --- a/storage/client/client.go +++ b/storage/client/client.go @@ -2913,7 +2913,7 @@ func (c *StorageClient) RuntimeRoflmarketInstances(ctx context.Context, runtime } // TxVolumes returns a list of transaction volumes per time window. -func (c *StorageClient) TxVolumes(ctx context.Context, layer apiTypes.Layer, p apiTypes.GetLayerStatsTxVolumeParams) (*TxVolumeList, error) { +func (c *StorageClient) TxVolumes(ctx context.Context, layer *apiTypes.Layer, p apiTypes.GetLayerStatsTxVolumeParams) (*TxVolumeList, error) { var query string switch { @@ -2931,10 +2931,14 @@ func (c *StorageClient) TxVolumes(ctx context.Context, layer apiTypes.Layer, p a return nil, fmt.Errorf("invalid window size parameters: %w", apiCommon.ErrBadRequest) } + var l *common.Layer + if layer != nil { + l = common.Ptr(translateLayer(*layer)) + } rows, err := c.db.Query( ctx, query, - translateLayer(layer), + l, p.Limit, p.Offset, ) diff --git a/storage/client/queries/queries.go b/storage/client/queries/queries.go index 7d56a1cf4..8747337cd 100644 --- a/storage/client/queries/queries.go +++ b/storage/client/queries/queries.go @@ -1194,9 +1194,15 @@ const ( // FineTxVolumes returns the fine-grained query for 5-minute sampled tx volume windows. FineTxVolumes = ` - SELECT window_end, tx_volume - FROM stats.min5_tx_volume - WHERE layer = $1::text + SELECT + window_end, + SUM(tx_volume) AS tx_volume + FROM + stats.min5_tx_volume + WHERE + ($1::text IS NULL OR layer = $1::text) + GROUP BY + window_end ORDER BY window_end DESC LIMIT $2::bigint @@ -1204,9 +1210,15 @@ const ( // FineDailyTxVolumes returns the query for daily tx volume windows. FineDailyTxVolumes = ` - SELECT window_end, tx_volume - FROM stats.daily_tx_volume - WHERE layer = $1::text + SELECT + window_end, + SUM(tx_volume) AS tx_volume + FROM + stats.daily_tx_volume + WHERE + ($1::text IS NULL OR layer = $1::text) + GROUP BY + window_end ORDER BY window_end DESC LIMIT $2::bigint @@ -1214,9 +1226,15 @@ const ( // DailyTxVolumes returns the query for daily sampled daily tx volume windows. DailyTxVolumes = ` - SELECT window_end, tx_volume - FROM stats.daily_tx_volume - WHERE (layer = $1::text AND (window_end AT TIME ZONE 'UTC')::time = '00:00:00') + SELECT + window_end, + SUM(tx_volume) AS tx_volume + FROM + stats.daily_tx_volume + WHERE + ($1::text IS NULL OR layer = $1::text) AND (window_end AT TIME ZONE 'UTC')::time = '00:00:00' + GROUP BY + window_end ORDER BY window_end DESC LIMIT $2::bigint diff --git a/tests/e2e_regression/common_test_cases.sh b/tests/e2e_regression/common_test_cases.sh index 1d91265b4..7382523c5 100644 --- a/tests/e2e_regression/common_test_cases.sh +++ b/tests/e2e_regression/common_test_cases.sh @@ -41,6 +41,7 @@ commonTestCases=( 'accounts_extraneous_key /v1/consensus/accounts?foo=bar' 'recent_blocks /v1/recent_blocks' + 'tx_volume_all_layers /v1/stats/tx_volume' 'blocks /v1/consensus/blocks' 'bad_account /v1/consensus/accounts/oasis1aaaaaaa' diff --git a/tests/e2e_regression/damask/expected/tx_volume_all_layers.body b/tests/e2e_regression/damask/expected/tx_volume_all_layers.body new file mode 100644 index 000000000..792a4bf1e --- /dev/null +++ b/tests/e2e_regression/damask/expected/tx_volume_all_layers.body @@ -0,0 +1,4 @@ +{ + "window_size_seconds": 86400, + "windows": [] +} diff --git a/tests/e2e_regression/damask/expected/tx_volume_all_layers.headers b/tests/e2e_regression/damask/expected/tx_volume_all_layers.headers new file mode 100644 index 000000000..1cce01c2d --- /dev/null +++ b/tests/e2e_regression/damask/expected/tx_volume_all_layers.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Content-Length: UNINTERESTING + diff --git a/tests/e2e_regression/eden/expected/tx_volume_all_layers.body b/tests/e2e_regression/eden/expected/tx_volume_all_layers.body new file mode 100644 index 000000000..792a4bf1e --- /dev/null +++ b/tests/e2e_regression/eden/expected/tx_volume_all_layers.body @@ -0,0 +1,4 @@ +{ + "window_size_seconds": 86400, + "windows": [] +} diff --git a/tests/e2e_regression/eden/expected/tx_volume_all_layers.headers b/tests/e2e_regression/eden/expected/tx_volume_all_layers.headers new file mode 100644 index 000000000..1cce01c2d --- /dev/null +++ b/tests/e2e_regression/eden/expected/tx_volume_all_layers.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Content-Length: UNINTERESTING + diff --git a/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.body b/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.body new file mode 100644 index 000000000..792a4bf1e --- /dev/null +++ b/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.body @@ -0,0 +1,4 @@ +{ + "window_size_seconds": 86400, + "windows": [] +} diff --git a/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.headers b/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.headers new file mode 100644 index 000000000..1cce01c2d --- /dev/null +++ b/tests/e2e_regression/eden_2025/expected/tx_volume_all_layers.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Content-Length: UNINTERESTING + diff --git a/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.body b/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.body new file mode 100644 index 000000000..792a4bf1e --- /dev/null +++ b/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.body @@ -0,0 +1,4 @@ +{ + "window_size_seconds": 86400, + "windows": [] +} diff --git a/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.headers b/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.headers new file mode 100644 index 000000000..1cce01c2d --- /dev/null +++ b/tests/e2e_regression/eden_testnet_2025/expected/tx_volume_all_layers.headers @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json +Vary: Origin +Date: UNINTERESTING +Content-Length: UNINTERESTING +