Skip to content
Closed
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
12 changes: 0 additions & 12 deletions protocol/chainlib/jsonRPC.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,18 +483,6 @@ func (apil *JsonRPCChainListener) Serve(ctx context.Context, cmdFlags common.Con
return addHeadersAndSendString(fiberCtx, reply.GetMetadata(), string(errorResponse))
}

// Check if the error message indicates an unsupported method
if IsUnsupportedMethodErrorMessage(err.Error()) {
// Convert error to JSON string and add headers
errorResponse, _ := json.Marshal(common.JsonRpcMethodNotFoundError)
return addHeadersAndSendString(fiberCtx, reply.GetMetadata(), string(errorResponse))
}

// Check if the error message indicates an unsupported method
if IsUnsupportedMethodErrorMessage(err.Error()) {
return fiberCtx.Status(fiber.StatusBadRequest).JSON(common.JsonRpcMethodNotFoundError)
}

if _, ok := err.(*json.SyntaxError); ok {
// Convert error to JSON string and add headers
errorResponse, _ := json.Marshal(common.JsonRpcParseError)
Expand Down
40 changes: 3 additions & 37 deletions protocol/chainlib/node_error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,44 +115,10 @@ func GetUnsupportedMethodPatterns() map[string][]string {
}

// IsUnsupportedMethodErrorMessage checks if an error message indicates an unsupported method
// This is a convenience function that accepts a string directly
// This is now a wrapper around common.IsUnsupportedMethodMessage for backward compatibility.
// The pattern matching logic has been moved to protocol/common to serve as a single source of truth.
func IsUnsupportedMethodErrorMessage(errorMessage string) bool {
if errorMessage == "" {
return false
}

errorMsg := strings.ToLower(errorMessage)

switch {
// JSON-RPC method not found patterns
case strings.Contains(errorMsg, JSONRPCMethodNotFound),
strings.Contains(errorMsg, JSONRPCMethodNotSupported),
strings.Contains(errorMsg, JSONRPCUnknownMethod),
strings.Contains(errorMsg, JSONRPCMethodDoesNotExist),
strings.Contains(errorMsg, JSONRPCInvalidMethod):
return true

// REST API patterns
case strings.Contains(errorMsg, RESTEndpointNotFound),
strings.Contains(errorMsg, RESTRouteNotFound),
strings.Contains(errorMsg, RESTPathNotFound),
strings.Contains(errorMsg, RESTMethodNotAllowed):
return true

// gRPC patterns
case strings.Contains(errorMsg, GRPCMethodNotImplemented),
strings.Contains(errorMsg, GRPCUnimplemented),
strings.Contains(errorMsg, GRPCNotImplemented),
strings.Contains(errorMsg, GRPCServiceNotFound):
return true

// Check for JSON-RPC error code -32601 (Method not found) in the message
case strings.Contains(errorMsg, JSONRPCErrorCode):
return true

default:
return false
}
return common.IsUnsupportedMethodMessage(errorMessage)
}

// IsUnsupportedMethodError checks if an error indicates an unsupported method
Expand Down
80 changes: 80 additions & 0 deletions protocol/chainlib/node_error_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,83 @@ func BenchmarkIsUnsupportedMethodError(b *testing.B) {
}
}
}

// NEW TEST: Verifies Issue #1 fix - smart contract errors should NOT be classified as unsupported
// This is a critical test for preventing false positives on smart contract reverts
func TestIsUnsupportedMethodErrorMessage_SmartContractErrors(t *testing.T) {
tests := []struct {
name string
message string
expected bool
}{
// CRITICAL: Smart contract errors should NOT match (Issue #1 fix)
{
name: "Smart contract NFT not found",
message: "execution reverted: NFT not found",
expected: false,
},
{
name: "Smart contract User not found",
message: "execution reverted: User not found",
expected: false,
},
{
name: "Smart contract Token not found",
message: "execution reverted: Token not found",
expected: false,
},
{
name: "Smart contract identity not found",
message: "execution reverted: identity not found",
expected: false,
},
{
name: "Smart contract IdentityRegistry specific",
message: "execution reverted: IdentityRegistry: identity not found",
expected: false,
},
{
name: "Smart contract Record not found",
message: "execution reverted: Record not found in database",
expected: false,
},
{
name: "Generic not found without execution reverted",
message: "user not found",
expected: false,
},
{
name: "Item not found",
message: "item not found",
expected: false,
},
// Verify actual unsupported methods still work correctly
{
name: "Actual method not found",
message: "method not found",
expected: true,
},
{
name: "Actual endpoint not found",
message: "endpoint not found",
expected: true,
},
{
name: "Actual route not found",
message: "route not found",
expected: true,
},
{
name: "JSON-RPC method not supported",
message: "method not supported",
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsUnsupportedMethodErrorMessage(tt.message)
require.Equal(t, tt.expected, got, "Message: %s", tt.message)
})
}
}
12 changes: 0 additions & 12 deletions protocol/chainlib/tendermintRPC.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,18 +485,6 @@ func (apil *TendermintRpcChainListener) Serve(ctx context.Context, cmdFlags comm
return addHeadersAndSendString(fiberCtx, reply.GetMetadata(), string(errorResponse))
}

// Check if the error message indicates an unsupported method
if IsUnsupportedMethodErrorMessage(err.Error()) {
// Convert error to JSON string and add headers
errorResponse, _ := json.Marshal(common.JsonRpcMethodNotFoundError)
return addHeadersAndSendString(fiberCtx, reply.GetMetadata(), string(errorResponse))
}

// Check if the error message indicates an unsupported method
if IsUnsupportedMethodErrorMessage(err.Error()) {
return fiberCtx.Status(fiber.StatusBadRequest).JSON(common.JsonRpcMethodNotFoundError)
}

// Get unique GUID response
errMasking := apil.logger.GetUniqueGuidResponseForError(err, msgSeed)

Expand Down
21 changes: 11 additions & 10 deletions protocol/common/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,16 +267,17 @@ type ProviderInfo struct {
}

type RelayResult struct {
Request *pairingtypes.RelayRequest
Reply *pairingtypes.RelayReply
ProviderInfo ProviderInfo
ReplyServer pairingtypes.Relayer_RelaySubscribeClient
Finalized bool
ConflictHandler ConflictHandlerInterface
StatusCode int
Quorum int
ProviderTrailer metadata.MD // the provider trailer attached to the request. used to transfer useful information (which is not signed so shouldn't be trusted completely).
IsNodeError bool
Request *pairingtypes.RelayRequest
Reply *pairingtypes.RelayReply
ProviderInfo ProviderInfo
ReplyServer pairingtypes.Relayer_RelaySubscribeClient
Finalized bool
ConflictHandler ConflictHandlerInterface
StatusCode int
Quorum int
ProviderTrailer metadata.MD // the provider trailer attached to the request. used to transfer useful information (which is not signed so shouldn't be trusted completely).
IsNodeError bool
IsUnsupportedMethod bool // Indicates this node error is an unsupported method
}

func (rr *RelayResult) GetReplyServer() pairingtypes.Relayer_RelaySubscribeClient {
Expand Down
73 changes: 72 additions & 1 deletion protocol/common/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package common

import sdkerrors "cosmossdk.io/errors"
import (
"strings"

sdkerrors "cosmossdk.io/errors"
)

var (
ContextDeadlineExceededError = sdkerrors.New("ContextDeadlineExceeded Error", 300, "context deadline exceeded")
Expand All @@ -11,3 +15,70 @@ var (
SubscriptionNotFoundError = sdkerrors.New("SubscriptionNotFoundError Error", 901, "subscription not found")
ProviderFinalizationDataAccountabilityError = sdkerrors.New("ProviderFinalizationDataAccountability Error", 3365, "provider returned invalid finalization data, with accountability")
)

// Unsupported method error patterns
// These constants define common error message patterns that indicate an unsupported method/endpoint
// across different API protocols (JSON-RPC, REST, gRPC)
const (
// JSON-RPC error patterns
JSONRPCMethodNotFound = "method not found"
JSONRPCMethodNotSupported = "method not supported"
JSONRPCUnknownMethod = "unknown method"
JSONRPCMethodDoesNotExist = "method does not exist"
JSONRPCInvalidMethod = "invalid method"
JSONRPCErrorCode = "-32601" // JSON-RPC 2.0 method not found error code

// REST API error patterns
RESTEndpointNotFound = "endpoint not found"
RESTRouteNotFound = "route not found"
RESTPathNotFound = "path not found"
RESTMethodNotAllowed = "method not allowed"

// gRPC error patterns
GRPCMethodNotImplemented = "method not implemented"
GRPCUnimplemented = "unimplemented"
GRPCNotImplemented = "not implemented"
GRPCServiceNotFound = "service not found"
)

// IsUnsupportedMethodMessage checks if an error message indicates an unsupported method
// This performs basic string pattern matching on error messages across all supported protocols.
// It is the single source of truth for pattern matching to ensure consistency across the codebase.
//
// For more comprehensive checks including HTTP status codes and gRPC status codes,
// use chainlib.IsUnsupportedMethodError which wraps this function with additional protocol-specific checks.
//
// Returns true if the error message contains any known unsupported method pattern.
func IsUnsupportedMethodMessage(errorMessage string) bool {
if errorMessage == "" {
return false
}

errorMsg := strings.ToLower(errorMessage)

// Check all patterns
patterns := []string{
JSONRPCMethodNotFound,
JSONRPCMethodNotSupported,
JSONRPCUnknownMethod,
JSONRPCMethodDoesNotExist,
JSONRPCInvalidMethod,
RESTEndpointNotFound,
RESTRouteNotFound,
RESTPathNotFound,
RESTMethodNotAllowed,
GRPCMethodNotImplemented,
GRPCUnimplemented,
GRPCNotImplemented,
GRPCServiceNotFound,
JSONRPCErrorCode,
}

for _, pattern := range patterns {
if strings.Contains(errorMsg, pattern) {
return true
}
}

return false
}
117 changes: 117 additions & 0 deletions protocol/common/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package common

import (
"testing"

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

func TestIsUnsupportedMethodMessage_AllPatterns(t *testing.T) {
// Test all 14 patterns are detected
tests := []struct {
name string
message string
want bool
}{
// JSON-RPC patterns (6)
{"method not found", "method not found", true},
{"method not supported", "method not supported", true},
{"unknown method", "unknown method", true},
{"method does not exist", "method does not exist", true},
{"invalid method", "invalid method", true},
{"-32601 code", "error: -32601", true},

// REST patterns (4)
{"endpoint not found", "endpoint not found", true},
{"route not found", "route not found", true},
{"path not found", "path not found", true},
{"method not allowed", "method not allowed", true},

// gRPC patterns (4)
{"method not implemented", "method not implemented", true},
{"unimplemented", "unimplemented", true},
{"not implemented", "not implemented", true},
{"service not found", "service not found", true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsUnsupportedMethodMessage(tt.message)
require.Equal(t, tt.want, got, "Pattern '%s' should be detected", tt.message)
})
}
}

func TestIsUnsupportedMethodMessage_CaseInsensitive(t *testing.T) {
tests := []string{
"METHOD NOT FOUND",
"Method Not Found",
"MeThOd NoT fOuNd",
"UNIMPLEMENTED",
}

for _, msg := range tests {
t.Run(msg, func(t *testing.T) {
require.True(t, IsUnsupportedMethodMessage(msg), "Should be case insensitive")
})
}
}

func TestIsUnsupportedMethodMessage_PartialMatch(t *testing.T) {
// Should match patterns within longer messages
tests := []string{
"Error: method not found in API",
"RPC endpoint not found on server",
"The service is unimplemented",
"JSON-RPC error -32601: method not found",
}

for _, msg := range tests {
t.Run(msg, func(t *testing.T) {
require.True(t, IsUnsupportedMethodMessage(msg), "Should match pattern in longer message")
})
}
}

func TestIsUnsupportedMethodMessage_SmartContractErrors(t *testing.T) {
// CRITICAL: Test Issue #1 fix - smart contract errors should NOT match
tests := []struct {
name string
message string
}{
{"NFT not found", "execution reverted: NFT not found"},
{"User not found", "execution reverted: User not found"},
{"Token not found", "execution reverted: Token not found"},
{"Identity not found", "execution reverted: identity not found"},
{"IdentityRegistry specific", "execution reverted: IdentityRegistry: identity not found"},
{"Record not found", "execution reverted: Record not found in database"},
{"Generic not found", "user not found"}, // Without "execution reverted"
{"Item not found", "item not found"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsUnsupportedMethodMessage(tt.message)
require.False(t, got, "Smart contract error '%s' should NOT be classified as unsupported method", tt.message)
})
}
}

func TestIsUnsupportedMethodMessage_NegativeCases(t *testing.T) {
tests := []string{
"",
"normal error",
"internal server error",
"timeout",
"connection refused",
"execution reverted",
"execution reverted: some other error",
}

for _, msg := range tests {
t.Run(msg, func(t *testing.T) {
require.False(t, IsUnsupportedMethodMessage(msg))
})
}
}

Loading
Loading