Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/rpc/core/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (env *Environment) BroadcastTx(ctx context.Context, req *coretypes.RequestB

select {
case <-ctx.Done():
return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Err())
return nil, fmt.Errorf("%w: %w", coretypes.ErrBroadcastConfirmationNotReceived, ctx.Err())
case r := <-resCh:
return &coretypes.ResultBroadcastTx{
Code: r.Code,
Expand Down Expand Up @@ -116,7 +116,7 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.Re

select {
case <-ctx.Done():
return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Err())
return nil, fmt.Errorf("%w: %w", coretypes.ErrBroadcastConfirmationNotReceived, ctx.Err())
case r := <-resCh:
if r.Code != abci.CodeTypeOK {
return &coretypes.ResultBroadcastTxCommit{
Expand Down
4 changes: 3 additions & 1 deletion rpc/coretypes/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ var (
ErrHeightNotAvailable = errors.New("height is not available")
// ErrInvalidRequest is used as a wrapper to cover more specific cases where the user has
// made an invalid request
ErrInvalidRequest = errors.New("invalid request")
ErrInvalidRequest = errors.New("invalid request")
ErrBroadcastConfirmationNotReceived = errors.New("broadcast confirmation not received")
ErrTooManyRequests = errors.New("too_many_requests")
)

// List of blocks
Expand Down
102 changes: 85 additions & 17 deletions rpc/jsonrpc/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/dashpay/tenderdash/rpc/coretypes"
"github.com/dashpay/tenderdash/types"
)

// ErrorCode is the type of JSON-RPC error codes.
Expand All @@ -31,14 +32,28 @@ const (
CodeMethodNotFound ErrorCode = -32601 // The method does not exist or is unavailable
CodeInvalidParams ErrorCode = -32602 // Invalid method parameters
CodeInternalError ErrorCode = -32603 // Internal JSON-RPC error

// Application-specific server error codes (JSON-RPC -32000 to -32099).
CodeTxAlreadyExists ErrorCode = -32000
CodeTxTooLarge ErrorCode = -32001
CodeMempoolIsFull ErrorCode = -32002
CodeTimeout ErrorCode = -32003
CodeTooManyRequests ErrorCode = -32004
CodeBroadcastConfirmationNotReceived ErrorCode = -32005
)

var errorCodeString = map[ErrorCode]string{
CodeParseError: "Parse error",
CodeInvalidRequest: "Invalid request",
CodeMethodNotFound: "Method not found",
CodeInvalidParams: "Invalid params",
CodeInternalError: "Internal error",
CodeParseError: "Parse error",
CodeInvalidRequest: "Invalid request",
CodeMethodNotFound: "Method not found",
CodeInvalidParams: "Invalid params",
CodeInternalError: "Internal error",
CodeTxAlreadyExists: "Tx already exists in cache",
CodeTxTooLarge: "Tx too large",
CodeMempoolIsFull: "Mempool is full",
CodeTimeout: "Timeout",
CodeTooManyRequests: "Too many requests",
CodeBroadcastConfirmationNotReceived: "Broadcast confirmation not received",
}

//----------------------------------------
Expand Down Expand Up @@ -144,23 +159,76 @@ func (req RPCRequest) MakeError(err error) RPCResponse {
if e, ok := err.(*RPCError); ok {
return RPCResponse{id: req.id, Error: e}
}
if errors.Is(err, coretypes.ErrZeroOrNegativeHeight) ||
errors.Is(err, coretypes.ErrZeroOrNegativePerPage) ||
errors.Is(err, coretypes.ErrPageOutOfRange) ||
errors.Is(err, coretypes.ErrInvalidRequest) {
return RPCResponse{id: req.id, Error: &RPCError{
Code: int(CodeInvalidRequest),
Message: CodeInvalidRequest.String(),
Data: err.Error(),
}}
}
code := rpcErrorCodeFor(err)
return RPCResponse{id: req.id, Error: &RPCError{
Code: int(CodeInternalError),
Message: CodeInternalError.String(),
Code: int(code),
Message: code.String(),
Data: err.Error(),
}}
}

func rpcErrorCodeFor(err error) ErrorCode {
switch {
case err == nil:
return CodeInternalError
case errors.Is(err, coretypes.ErrZeroOrNegativeHeight),
errors.Is(err, coretypes.ErrZeroOrNegativePerPage),
errors.Is(err, coretypes.ErrPageOutOfRange),
errors.Is(err, coretypes.ErrInvalidRequest):
return CodeInvalidRequest
case errors.Is(err, types.ErrTxInCache):
return CodeTxAlreadyExists
case isErrTxTooLarge(err):
return CodeTxTooLarge
case isErrMempoolFull(err):
return CodeMempoolIsFull
case errors.Is(err, context.DeadlineExceeded):
return CodeTimeout
case isTooManyRequestsErr(err):
return CodeTooManyRequests
case isBroadcastConfirmationNotReceivedErr(err):
return CodeBroadcastConfirmationNotReceived
default:
return CodeInternalError
}
}

func isErrTxTooLarge(err error) bool {
if err == nil {
return false
}
var txErr types.ErrTxTooLarge
return errors.As(err, &txErr)
}

func isErrMempoolFull(err error) bool {
if err == nil {
return false
}
var fullErr types.ErrMempoolIsFull
return errors.As(err, &fullErr)
}

func isTooManyRequestsErr(err error) bool {
if err == nil {
return false
}
if errors.Is(err, coretypes.ErrTooManyRequests) {
return true
}
return strings.Contains(err.Error(), "too_many_requests")
}

func isBroadcastConfirmationNotReceivedErr(err error) bool {
if err == nil {
return false
}
if errors.Is(err, coretypes.ErrBroadcastConfirmationNotReceived) {
return true
}
return strings.Contains(err.Error(), "broadcast confirmation not received")
}

// SetMethodAndParams updates the method and parameters of req with the given
// values, leaving the ID unchanged.
func (req *RPCRequest) SetMethodAndParams(method string, params interface{}) error {
Expand Down
32 changes: 32 additions & 0 deletions rpc/jsonrpc/types/types_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package types

import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/dashpay/tenderdash/rpc/coretypes"
"github.com/dashpay/tenderdash/types"
)

type SampleResult struct {
Expand Down Expand Up @@ -71,3 +76,30 @@ func TestRPCError(t *testing.T) {
Message: "Badness",
}))
}

func TestRPCRequestMakeErrorMapsCodes(t *testing.T) {
req := RPCRequest{id: json.RawMessage(`"1"`)}
testCases := []struct {
name string
err error
want ErrorCode
}{
{"invalid request", coretypes.ErrInvalidRequest, CodeInvalidRequest},
{"tx already in cache", types.ErrTxInCache, CodeTxAlreadyExists},
{"tx too large", types.ErrTxTooLarge{Max: 1, Actual: 2}, CodeTxTooLarge},
{"mempool full", types.ErrMempoolIsFull{}, CodeMempoolIsFull},
{"deadline exceeded", context.DeadlineExceeded, CodeTimeout},
{"too many requests sentinel", coretypes.ErrTooManyRequests, CodeTooManyRequests},
{"too many requests string", errors.New("too_many_requests: limiter triggered"), CodeTooManyRequests},
{"broadcast confirmation", fmt.Errorf("%w", coretypes.ErrBroadcastConfirmationNotReceived), CodeBroadcastConfirmationNotReceived},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
resp := req.MakeError(tc.err)
require.NotNil(t, resp.Error)
assert.Equal(t, int(tc.want), resp.Error.Code)
})
}
}
Loading