Skip to content

Commit

Permalink
Merge pull request #188 from blinklabs-io/feat/refactor-submit
Browse files Browse the repository at this point in the history
feat: refactor submit into top-level package
  • Loading branch information
wolf31o2 authored Feb 24, 2024
2 parents eaa1a16 + a533dc0 commit f622acd
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 70 deletions.
97 changes: 27 additions & 70 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ import (
"time"

ouroboros "github.com/blinklabs-io/gouroboros"
"github.com/blinklabs-io/gouroboros/ledger"
"github.com/blinklabs-io/gouroboros/protocol/localtxsubmission"
"github.com/blinklabs-io/tx-submit-api/internal/config"
"github.com/blinklabs-io/tx-submit-api/internal/logging"

"github.com/fxamacker/cbor/v2"
ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
Expand All @@ -33,6 +29,10 @@ import (
ginSwagger "github.com/swaggo/gin-swagger" // gin-swagger middleware

_ "github.com/blinklabs-io/tx-submit-api/docs" // docs is generated by Swag CLI
"github.com/blinklabs-io/tx-submit-api/internal/config"
"github.com/blinklabs-io/tx-submit-api/internal/logging"
"github.com/blinklabs-io/tx-submit-api/submit"

)

// @title tx-submit-api
Expand All @@ -45,8 +45,8 @@ import (
// @contact.url https://blinklabs.io
// @contact.email [email protected]

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
func Start(cfg *config.Config) error {
// Disable gin debug and color output
gin.SetMode(gin.ReleaseMode)
Expand Down Expand Up @@ -259,58 +259,31 @@ func handleSubmitTx(c *gin.Context) {
if err := c.Request.Body.Close(); err != nil {
logger.Errorf("failed to close request body: %s", err)
}
// Determine transaction type (era)
txType, err := ledger.DetermineTransactionType(txRawBytes)
if err != nil {
logger.Errorf("could not parse transaction to determine type: %s", err)
c.JSON(400, "could not parse transaction to determine type")
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil)
return
}
tx, err := ledger.NewTransactionFromCbor(txType, txRawBytes)
if err != nil {
logger.Errorf("failed to parse transaction CBOR: %s", err)
c.JSON(400, fmt.Sprintf("failed to parse transaction CBOR: %s", err))
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil)
return
}
// Connect to cardano-node and submit TX
// Send TX
errorChan := make(chan error)
oConn, err := ouroboros.NewConnection(
ouroboros.WithNetworkMagic(uint32(cfg.Node.NetworkMagic)),
ouroboros.WithErrorChan(errorChan),
ouroboros.WithNodeToNode(false),
ouroboros.WithLocalTxSubmissionConfig(
localtxsubmission.NewConfig(
localtxsubmission.WithTimeout(
time.Duration(cfg.Node.Timeout)*time.Second,
),
),
),
)
submitConfig := &submit.Config{
ErrorChan: errorChan,
NetworkMagic: cfg.Node.NetworkMagic,
NodeAddress: cfg.Node.Address,
NodePort: cfg.Node.Port,
SocketPath: cfg.Node.SocketPath,
Timeout: cfg.Node.Timeout,
}
txHash, err := submit.SubmitTx(submitConfig, txRawBytes)
if err != nil {
logger.Errorf("failure creating Ouroboros connection: %s", err)
c.JSON(500, "failure communicating with node")
if c.GetHeader("Accept") == "application/cbor" {
txRejectErr := err.(localtxsubmission.TransactionRejectedError)
c.Data(400, "application/cbor", txRejectErr.ReasonCbor)
} else {
if err.Error() != "" {
c.JSON(400, err.Error())
} else {
c.JSON(400, fmt.Sprintf("%s", err))
}
}
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil)
return
}
if cfg.Node.Address != "" && cfg.Node.Port > 0 {
if err := oConn.Dial("tcp", fmt.Sprintf("%s:%d", cfg.Node.Address, cfg.Node.Port)); err != nil {
logger.Errorf("failure connecting to node via TCP: %s", err)
c.JSON(500, "failure communicating with node")
_ = ginmetrics.GetMonitor().
GetMetric("tx_submit_fail_count").
Inc(nil)
return
}
} else {
if err := oConn.Dial("unix", cfg.Node.SocketPath); err != nil {
logger.Errorf("failure connecting to node via UNIX socket: %s", err)
c.JSON(500, "failure communicating with node")
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil)
return
}
}
// Start async error handler
go func() {
err, ok := <-errorChan
Expand All @@ -322,24 +295,8 @@ func handleSubmitTx(c *gin.Context) {
Inc(nil)
}
}()
defer func() {
// Close Ouroboros connection
oConn.Close()
}()
// Submit the transaction
if err := oConn.LocalTxSubmission().Client.SubmitTx(uint16(txType), txRawBytes); err != nil {
if c.GetHeader("Accept") == "application/cbor" {
txRejectErr := err.(localtxsubmission.TransactionRejectedError)
c.Data(400, "application/cbor", txRejectErr.ReasonCbor)
} else {
c.JSON(400, err.Error())
}
// Increment custom metric
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_fail_count").Inc(nil)
return
}
// Return transaction ID
c.JSON(202, tx.Hash())
c.JSON(202, txHash)
// Increment custom metric
_ = ginmetrics.GetMonitor().GetMetric("tx_submit_count").Inc(nil)
}
100 changes: 100 additions & 0 deletions submit/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2023 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 submit

import (
"fmt"
"time"

ouroboros "github.com/blinklabs-io/gouroboros"
"github.com/blinklabs-io/gouroboros/ledger"
"github.com/blinklabs-io/gouroboros/protocol/localtxsubmission"
)

type Config struct {
ErrorChan chan error
Network string
NetworkMagic uint32
NodeAddress string
NodePort uint
SocketPath string
Timeout uint
}

func SubmitTx(cfg *Config, txRawBytes []byte) (string, error) {
// Determine transaction type (era)
txType, err := ledger.DetermineTransactionType(txRawBytes)
if err != nil {
return "", fmt.Errorf("could not parse transaction to determine type: %s", err)
}
tx, err := ledger.NewTransactionFromCbor(txType, txRawBytes)
if err != nil {
return "", fmt.Errorf("failed to parse transaction CBOR: %s", err)
}

err = cfg.populateNetworkMagic()
if err != nil {
return "", fmt.Errorf("failed to populate networkMagic: %s", err)
}

// Connect to cardano-node and submit TX using Ouroboros LocalTxSubmission
oConn, err := ouroboros.NewConnection(
ouroboros.WithNetworkMagic(uint32(cfg.NetworkMagic)),
ouroboros.WithErrorChan(cfg.ErrorChan),
ouroboros.WithNodeToNode(false),
ouroboros.WithLocalTxSubmissionConfig(
localtxsubmission.NewConfig(
localtxsubmission.WithTimeout(
time.Duration(cfg.Timeout)*time.Second,
),
),
),
)
if err != nil {
return "", fmt.Errorf("failure creating Ouroboros connection: %s", err)
}
if cfg.NodeAddress != "" && cfg.NodePort > 0 {
if err := oConn.Dial("tcp", fmt.Sprintf("%s:%d", cfg.NodeAddress, cfg.NodePort)); err != nil {
return "", fmt.Errorf("failure connecting to node via TCP: %s", err)
}
} else {
if err := oConn.Dial("unix", cfg.SocketPath); err != nil {
return "", fmt.Errorf("failure connecting to node via UNIX socket: %s", err)
}
}
defer func() {
// Close Ouroboros connection
oConn.Close()
}()
// Submit the transaction
if err := oConn.LocalTxSubmission().Client.SubmitTx(uint16(txType), txRawBytes); err != nil {
return "", fmt.Errorf("%s", err.Error())
}
return tx.Hash(), nil
}

func (c *Config) populateNetworkMagic() error {
if c.NetworkMagic == 0 {
if c.Network != "" {
network := ouroboros.NetworkByName(c.Network)
if network == ouroboros.NetworkInvalid {
return fmt.Errorf("unknown network: %s", c.Network)
}
c.NetworkMagic = uint32(network.NetworkMagic)
return nil
}
}
return nil
}

0 comments on commit f622acd

Please sign in to comment.