diff --git a/docs/docs.go b/docs/docs.go index 3423d9b..37b4baa 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -24,6 +24,81 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/localstatequery/current-era": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query Current Era", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQueryCurrentEra" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, + "/localstatequery/system-start": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query System Start", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQuerySystemStart" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, + "/localstatequery/tip": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query Chain Tip", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQueryTip" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, "/localtxmonitor/has_tx/{tx_hash}": { "get": { "consumes": [ @@ -169,6 +244,51 @@ const docTemplate = `{ } } }, + "api.responseLocalStateQueryCurrentEra": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "api.responseLocalStateQuerySystemStart": { + "type": "object", + "properties": { + "day": { + "type": "integer" + }, + "picoseconds": { + "type": "integer" + }, + "year": { + "type": "integer" + } + } + }, + "api.responseLocalStateQueryTip": { + "type": "object", + "properties": { + "block_no": { + "type": "integer" + }, + "epoch_no": { + "type": "integer" + }, + "era": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "slot_no": { + "type": "integer" + } + } + }, "api.responseLocalTxMonitorHasTx": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 5af9c08..4139e30 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -20,6 +20,81 @@ "host": "localhost", "basePath": "/api", "paths": { + "/localstatequery/current-era": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query Current Era", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQueryCurrentEra" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, + "/localstatequery/system-start": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query System Start", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQuerySystemStart" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, + "/localstatequery/tip": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "localstatequery" + ], + "summary": "Query Chain Tip", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.responseLocalStateQueryTip" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/api.responseApiError" + } + } + } + } + }, "/localtxmonitor/has_tx/{tx_hash}": { "get": { "consumes": [ @@ -165,6 +240,51 @@ } } }, + "api.responseLocalStateQueryCurrentEra": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, + "api.responseLocalStateQuerySystemStart": { + "type": "object", + "properties": { + "day": { + "type": "integer" + }, + "picoseconds": { + "type": "integer" + }, + "year": { + "type": "integer" + } + } + }, + "api.responseLocalStateQueryTip": { + "type": "object", + "properties": { + "block_no": { + "type": "integer" + }, + "epoch_no": { + "type": "integer" + }, + "era": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "slot_no": { + "type": "integer" + } + } + }, "api.responseLocalTxMonitorHasTx": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 72f9030..edd9027 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -6,6 +6,35 @@ definitions: example: error message type: string type: object + api.responseLocalStateQueryCurrentEra: + properties: + id: + type: integer + name: + type: string + type: object + api.responseLocalStateQuerySystemStart: + properties: + day: + type: integer + picoseconds: + type: integer + year: + type: integer + type: object + api.responseLocalStateQueryTip: + properties: + block_no: + type: integer + epoch_no: + type: integer + era: + type: string + hash: + type: string + slot_no: + type: integer + type: object api.responseLocalTxMonitorHasTx: properties: has_tx: @@ -44,6 +73,54 @@ info: title: cardano-node-api version: "1.0" paths: + /localstatequery/current-era: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.responseLocalStateQueryCurrentEra' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.responseApiError' + summary: Query Current Era + tags: + - localstatequery + /localstatequery/system-start: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.responseLocalStateQuerySystemStart' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.responseApiError' + summary: Query System Start + tags: + - localstatequery + /localstatequery/tip: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.responseLocalStateQueryTip' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/api.responseApiError' + summary: Query Chain Tip + tags: + - localstatequery /localtxmonitor/has_tx/{tx_hash}: get: consumes: diff --git a/internal/api/api.go b/internal/api/api.go index 7303a87..876125e 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -36,11 +36,10 @@ import ( // @host localhost // @Schemes http // @BasePath /api - // @contact.name Blink Labs // @contact.url https://blinklabs.io // @contact.email support@blinklabs.io - +// // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html func Start(cfg *config.Config) error { @@ -75,6 +74,7 @@ func Start(cfg *config.Config) error { // Configure API routes apiGroup := router.Group("/api") + configureLocalStateQueryRoutes(apiGroup) configureLocalTxMonitorRoutes(apiGroup) configureLocalTxSubmissionRoutes(apiGroup) diff --git a/internal/api/localstatequery.go b/internal/api/localstatequery.go new file mode 100644 index 0000000..e0264c5 --- /dev/null +++ b/internal/api/localstatequery.go @@ -0,0 +1,212 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "encoding/hex" + + "github.com/blinklabs-io/gouroboros/ledger" + "github.com/gin-gonic/gin" + + "github.com/blinklabs-io/cardano-node-api/internal/node" +) + +func configureLocalStateQueryRoutes(apiGroup *gin.RouterGroup) { + group := apiGroup.Group("/localstatequery") + group.GET("/current-era", handleLocalStateQueryCurrentEra) + group.GET("/system-start", handleLocalStateQuerySystemStart) + group.GET("/tip", handleLocalStateQueryTip) +} + +type responseLocalStateQueryCurrentEra struct { + Id uint8 `json:"id"` + Name string `json:"name"` +} + +// handleLocalStateQueryCurrentEra godoc +// +// @Summary Query Current Era +// @Tags localstatequery +// @Produce json +// @Success 200 {object} responseLocalStateQueryCurrentEra +// @Failure 500 {object} responseApiError +// @Router /localstatequery/current-era [get] +func handleLocalStateQueryCurrentEra(c *gin.Context) { + // Connect to node + oConn, err := node.GetConnection() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + // Async error handler + go func() { + err, ok := <-oConn.ErrorChan() + if !ok { + return + } + c.JSON(500, apiError(err.Error())) + }() + defer func() { + // Close Ouroboros connection + oConn.Close() + }() + // Start client + oConn.LocalStateQuery().Client.Start() + + // Get era + eraNum, err := oConn.LocalStateQuery().Client.GetCurrentEra() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + + // Create response + era := ledger.GetEraById(uint8(eraNum)) + resp := responseLocalStateQueryCurrentEra{ + Id: era.Id, + Name: era.Name, + } + c.JSON(200, resp) +} + +type responseLocalStateQuerySystemStart struct { + Year int `json:"year"` + Day int `json:"day"` + Picoseconds uint64 `json:"picoseconds"` +} + +// handleLocalStateQuerySystemStart godoc +// +// @Summary Query System Start +// @Tags localstatequery +// @Produce json +// @Success 200 {object} responseLocalStateQuerySystemStart +// @Failure 500 {object} responseApiError +// @Router /localstatequery/system-start [get] +func handleLocalStateQuerySystemStart(c *gin.Context) { + // Connect to node + oConn, err := node.GetConnection() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + // Async error handler + go func() { + err, ok := <-oConn.ErrorChan() + if !ok { + return + } + c.JSON(500, apiError(err.Error())) + }() + defer func() { + // Close Ouroboros connection + oConn.Close() + }() + // Start client + oConn.LocalStateQuery().Client.Start() + + // Get system start + result, err := oConn.LocalStateQuery().Client.GetSystemStart() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + + // Create response + resp := responseLocalStateQuerySystemStart{ + Year: result.Year, + Day: result.Day, + Picoseconds: result.Picoseconds, + } + c.JSON(200, resp) +} + +type responseLocalStateQueryTip struct { + Era string `json:"era"` + EpochNo int `json:"epoch_no"` + BlockNo int64 `json:"block_no"` + Slot uint64 `json:"slot_no"` + Hash string `json:"hash"` +} + +// handleLocalStateQueryTip godoc +// +// @Summary Query Chain Tip +// @Tags localstatequery +// @Produce json +// @Success 200 {object} responseLocalStateQueryTip +// @Failure 500 {object} responseApiError +// @Router /localstatequery/tip [get] +func handleLocalStateQueryTip(c *gin.Context) { + // Connect to node + oConn, err := node.GetConnection() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + // Async error handler + go func() { + err, ok := <-oConn.ErrorChan() + if !ok { + return + } + c.JSON(500, apiError(err.Error())) + }() + defer func() { + // Close Ouroboros connection + oConn.Close() + }() + // Start client + oConn.LocalStateQuery().Client.Start() + + // Get era + eraNum, err := oConn.LocalStateQuery().Client.GetCurrentEra() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + era := ledger.GetEraById(uint8(eraNum)) + + // Get epochNo + epochNo, err := oConn.LocalStateQuery().Client.GetEpochNo() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + + // Get blockNo + blockNo, err := oConn.LocalStateQuery().Client.GetChainBlockNo() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + + // Get chain point (slot and hash) + point, err := oConn.LocalStateQuery().Client.GetChainPoint() + if err != nil { + c.JSON(500, apiError(err.Error())) + return + } + + // Create response + resp := responseLocalStateQueryTip{ + Era: era.Name, + EpochNo: epochNo, + BlockNo: blockNo, + Slot: point.Slot, + Hash: hex.EncodeToString(point.Hash), + } + c.JSON(200, resp) +}