Skip to content

Commit

Permalink
feat: add timeout (#741)
Browse files Browse the repository at this point in the history
  • Loading branch information
shotasilagadzetaal authored Jan 13, 2025
1 parent 4cedcc5 commit c2c7851
Show file tree
Hide file tree
Showing 14 changed files with 402 additions and 450 deletions.
122 changes: 80 additions & 42 deletions internal/api/handler/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import (
)

const (
maxTimeout = 30
maxTimeoutSecondsDefault = 5
mapExpiryTimeDefault = 24 * time.Hour
maxTimeout = 30
timeoutSecondsDefault = 5
mapExpiryTimeDefault = 24 * time.Hour
)

var (
Expand All @@ -53,11 +53,17 @@ type ArcDefaultHandler struct {
rejectedCallbackURLSubstrings []string
txFinder validator.TxFinderI
mapExpiryTime time.Duration
defaultTimeout time.Duration
mrVerifier validator.MerkleVerifierI
tracingEnabled bool
tracingAttributes []attribute.KeyValue
}

type PostResponse struct {
StatusCode int
response interface{}
}

func WithNow(nowFunc func() time.Time) func(*ArcDefaultHandler) {
return func(p *ArcDefaultHandler) {
p.now = nowFunc
Expand All @@ -70,6 +76,12 @@ func WithCallbackURLRestrictions(rejectedCallbackURLSubstrings []string) func(*A
}
}

func WithServerMaxTimeoutDefault(timeout time.Duration) func(*ArcDefaultHandler) {
return func(s *ArcDefaultHandler) {
s.defaultTimeout = timeout
}
}

func WithCacheExpiryTime(d time.Duration) func(*ArcDefaultHandler) {
return func(p *ArcDefaultHandler) {
p.mapExpiryTime = d
Expand Down Expand Up @@ -109,6 +121,7 @@ func NewDefault(
mrVerifier: mr,
txFinder: cachedFinder,
mapExpiryTime: mapExpiryTimeDefault,
defaultTimeout: timeoutSecondsDefault * time.Second,
}

// apply options
Expand Down Expand Up @@ -180,19 +193,18 @@ func calcFeesFromBSVPerKB(feePerKB float64) (uint64, uint64) {
return satoshis, bytes
}

// POSTTransaction ...
func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTransactionParams) (err error) {
reqCtx := ctx.Request().Context()
func (m ArcDefaultHandler) postTransaction(ctx echo.Context, params api.POSTTransactionParams) PostResponse {
var err error

reqCtx, span := tracing.StartTracing(reqCtx, "POSTTransaction", m.tracingEnabled, m.tracingAttributes...)
reqCtx, span := tracing.StartTracing(ctx.Request().Context(), "POSTTransaction", m.tracingEnabled, m.tracingAttributes...)
defer func() {
tracing.EndTracing(span, err)
}()

transactionOptions, err := getTransactionOptions(params, m.rejectedCallbackURLSubstrings)
if err != nil {
e := api.NewErrorFields(api.ErrStatusBadRequest, err.Error())
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

txHex, err := parseTransactionFromRequest(ctx.Request())
Expand All @@ -202,8 +214,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
attr := e.GetSpanAttributes()
span.SetAttributes(attr...)
}

return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

// Now we check if we have the transaction present in db, if so we skip validation (as we must have already validated it)
Expand All @@ -215,7 +226,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
span.SetAttributes(attr...)
}
// if an error is returned, the processing failed
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

// check if we already have the transaction in db (so no need to validate)
Expand All @@ -224,7 +235,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
// if we have error which is NOT ErrTransactionNotFound, return err
if !errors.Is(err, metamorph.ErrTransactionNotFound) {
e := api.NewErrorFields(api.ErrStatusGeneric, err.Error())
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}
} else {
// if we have found transaction skip the validation
Expand All @@ -240,7 +251,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran

// if LastSubmitted doesn't need to be updated and we already have provided callbacks - skip everything and return current status
if time.Since(tx.LastSubmitted.AsTime()) < m.mapExpiryTime && callbackAlreadyExists {
return ctx.JSON(int(api.StatusOK), &api.TransactionResponse{
return PostResponse{int(api.StatusOK), &api.TransactionResponse{
Status: int(api.StatusOK),
Title: "OK",
BlockHash: &tx.BlockHash,
Expand All @@ -251,7 +262,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
Timestamp: m.now(),
Txid: txIDs[0],
MerklePath: &tx.MerklePath,
})
}}
}
}

Expand All @@ -262,7 +273,7 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
span.SetAttributes(attr...)
}
// if an error is returned, the processing failed
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

if len(fails) > 0 {
Expand All @@ -272,19 +283,34 @@ func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTran
attr := e.GetSpanAttributes()
span.SetAttributes(attr...)
}
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

sizingCtx := context.WithValue(reqCtx, ContextSizings, prepareSizingInfo(txs))
ctx.SetRequest(ctx.Request().WithContext(sizingCtx))

response := successes[0]
res := successes[0]

if span != nil {
span.SetAttributes(attribute.String("status", string(response.TxStatus)))
span.SetAttributes(attribute.String("status", string(res.TxStatus)))
}

return PostResponse{res.Status, res}
}

// POSTTransaction ...
func (m ArcDefaultHandler) POSTTransaction(ctx echo.Context, params api.POSTTransactionParams) (err error) {
timeout := m.defaultTimeout
if params.XMaxTimeout != nil && *params.XMaxTimeout < maxTimeout {
timeout = time.Second * time.Duration(*params.XMaxTimeout)
}

return ctx.JSON(response.Status, response)
timeoutCtx, cancel := context.WithTimeout(ctx.Request().Context(), timeout)
ctx.SetRequest(ctx.Request().WithContext(timeoutCtx))
defer cancel()

postResponse := m.postTransaction(ctx, params)
return ctx.JSON(postResponse.StatusCode, postResponse.response)
}

// GETTransactionStatus ...
Expand Down Expand Up @@ -332,11 +358,9 @@ func (m ArcDefaultHandler) GETTransactionStatus(ctx echo.Context, id string) (er
})
}

// POSTTransactions ...
func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTransactionsParams) (err error) {
reqCtx := ctx.Request().Context()

reqCtx, span := tracing.StartTracing(reqCtx, "POSTTransactions", m.tracingEnabled, m.tracingAttributes...)
func (m ArcDefaultHandler) postTransactions(ctx echo.Context, params api.POSTTransactionsParams) PostResponse {
var err error
reqCtx, span := tracing.StartTracing(ctx.Request().Context(), "POSTTransactions", m.tracingEnabled, m.tracingAttributes...)
defer func() {
tracing.EndTracing(span, err)
}()
Expand All @@ -349,7 +373,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
attr := e.GetSpanAttributes()
span.SetAttributes(attr...)
}
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

txsHex, err := parseTransactionsFromRequest(ctx.Request())
Expand All @@ -359,7 +383,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
attr := e.GetSpanAttributes()
span.SetAttributes(attr...)
}
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

// Now we check if we have the transactions present in db, if so we skip validation (as we must have already validated them)
Expand All @@ -371,7 +395,8 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
span.SetAttributes(attr...)
}
// if an error is returned, the processing failed
return ctx.JSON(e.Status, e)

return PostResponse{e.Status, e}
}

// check if we already have the transactions in db (so no need to validate)
Expand All @@ -381,7 +406,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
// if we have error which is NOT ErrTransactionNotFound, return err
if !errors.Is(err, metamorph.ErrTransactionNotFound) {
e := api.NewErrorFields(api.ErrStatusGeneric, err.Error())
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}
} else if len(txStatuses) == len(txIDs) {
// if we have found all the transactions, skip the validation
Expand Down Expand Up @@ -427,7 +452,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
for _, o := range successes {
responses = append(responses, o)
}
return ctx.JSON(int(api.StatusOK), responses)
return PostResponse{int(api.StatusOK), responses}
}

txs, successes, fails, e := m.processTransactions(reqCtx, txsHex, transactionOptions)
Expand All @@ -436,7 +461,7 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
attr := e.GetSpanAttributes()
span.SetAttributes(attr...)
}
return ctx.JSON(e.Status, e)
return PostResponse{e.Status, e}
}

sizingCtx := context.WithValue(reqCtx, ContextSizings, prepareSizingInfo(txs))
Expand All @@ -453,7 +478,22 @@ func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTra
responses = append(responses, fo)
}

return ctx.JSON(int(api.StatusOK), responses)
return PostResponse{int(api.StatusOK), responses}
}

// POSTTransactions ...
func (m ArcDefaultHandler) POSTTransactions(ctx echo.Context, params api.POSTTransactionsParams) (err error) {
timeout := m.defaultTimeout
if params.XMaxTimeout != nil && *params.XMaxTimeout < maxTimeout {
timeout = time.Second * time.Duration(*params.XMaxTimeout)
}

timeoutCtx, cancel := context.WithTimeout(ctx.Request().Context(), timeout)
ctx.SetRequest(ctx.Request().WithContext(timeoutCtx))
defer cancel()

postResponse := m.postTransactions(ctx, params)
return ctx.JSON(postResponse.StatusCode, postResponse.response)
}

func getTransactionOptions(params api.POSTTransactionParams, rejectedCallbackURLSubstrings []string) (*metamorph.TransactionOptions, error) {
Expand All @@ -475,9 +515,7 @@ func ValidateCallbackURL(callbackURL string, rejectedCallbackURLSubstrings []str
}

func getTransactionsOptions(params api.POSTTransactionsParams, rejectedCallbackURLSubstrings []string) (*metamorph.TransactionOptions, error) {
transactionOptions := &metamorph.TransactionOptions{
MaxTimeout: maxTimeoutSecondsDefault,
}
transactionOptions := &metamorph.TransactionOptions{}
if params.XCallbackUrl != nil {
if err := ValidateCallbackURL(*params.XCallbackUrl, rejectedCallbackURLSubstrings); err != nil {
return nil, err
Expand Down Expand Up @@ -542,14 +580,6 @@ func getTransactionsOptions(params api.POSTTransactionsParams, rejectedCallbackU
transactionOptions.SkipTxValidation = *params.XSkipTxValidation
}

if params.XMaxTimeout != nil {
if *params.XMaxTimeout > maxTimeout {
return nil, ErrMaxTimeoutExceeded
}

transactionOptions.MaxTimeout = *params.XMaxTimeout
}

if params.XFullStatusUpdates != nil {
transactionOptions.FullStatusUpdates = *params.XFullStatusUpdates
}
Expand Down Expand Up @@ -733,6 +763,14 @@ func (m ArcDefaultHandler) submitTransactions(ctx context.Context, txs []*sdkTx.

var submitStatuses []*metamorph.TransactionStatus

// to avoid false negatives first check if ctx is expired
select {
case <-ctx.Done():
_, arcError := m.handleError(ctx, nil, context.DeadlineExceeded)
return nil, arcError
default:
}

if len(txs) == 1 {
tx := txs[0]

Expand Down
Loading

0 comments on commit c2c7851

Please sign in to comment.