diff --git a/chain_capabilities/aptos/actions/view.go b/chain_capabilities/aptos/actions/view.go index 4952823d0..09b9b6d19 100644 --- a/chain_capabilities/aptos/actions/view.go +++ b/chain_capabilities/aptos/actions/view.go @@ -6,6 +6,9 @@ import ( "math" "time" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/smartcontractkit/capabilities/libs/chainconsensus" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" @@ -63,9 +66,11 @@ func (s *Aptos) View( data, err := chainconsensus.ReadType[[]byte](ctx, s.ConsensusHandler, request) if err != nil { + // AptosService returns InvalidArgument when the node rejects the view call itself (e.g. module doesn't exist). + isUserError := status.Code(err) == codes.InvalidArgument monitoring.LogAndEmitError(ctx, s.lggr, s.beholderProcessor, - s.messageBuilder.BuildViewError(telemetryContext, input, "Failed to execute View request", err.Error(), false)) - return nil, capcommon.GetError(err, false) + s.messageBuilder.BuildViewError(telemetryContext, input, "Failed to execute View request", err.Error(), isUserError)) + return nil, capcommon.GetError(err, isUserError) } monitoring.LogAndEmitSuccess(ctx, "Successful View execution", s.lggr, s.beholderProcessor, s.messageBuilder.BuildViewSuccess(telemetryContext, input, uint64(len(data)))) diff --git a/chain_capabilities/aptos/actions/view_test.go b/chain_capabilities/aptos/actions/view_test.go index fbb99417b..0535b7d82 100644 --- a/chain_capabilities/aptos/actions/view_test.go +++ b/chain_capabilities/aptos/actions/view_test.go @@ -8,6 +8,8 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" caperrors "github.com/smartcontractkit/chainlink-common/pkg/capabilities/errors" @@ -200,6 +202,57 @@ func TestView_FailsWhenRequestedLedgerVersionOverflowsChainHeightType(t *testing require.Contains(t, err.Error(), "overflows int64") } +func TestView_ClassifiesAptosServiceErrors(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + serviceErr error + wantOrigin caperrors.Origin + }{ + { + name: "invalid argument is user error", + serviceErr: status.Error(codes.InvalidArgument, "failed to call view function: Linker Error: Module 01::registry doesn't exist"), + wantOrigin: caperrors.OriginUser, + }, + { + name: "unknown grpc code is system error", + serviceErr: status.Error(codes.Unknown, "failed to call view function: 429 Too Many Requests"), + wantOrigin: caperrors.OriginSystem, + }, + { + name: "plain error is system error", + serviceErr: fmt.Errorf("failed to get client"), + wantOrigin: caperrors.OriginSystem, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + svc := typesmocks.NewAptosService(t) + svc.On("View", mock.Anything, mock.AnythingOfType("aptos.ViewRequest")). + Return((*aptostypes.ViewReply)(nil), tt.serviceErr). + Once() + + a := newTestAptos(svc, &ctypes.ChainHeight{Latest: 321, Safe: 321, Finalized: 321}) + + _, err := a.View(context.Background(), capabilities.RequestMetadata{ + WorkflowExecutionID: "weid", + ReferenceID: "step-id", + }, &aptoscap.ViewRequest{ + Payload: &aptoscap.ViewPayload{ + Module: &aptoscap.ModuleID{Address: []byte{1}, Name: "registry"}, + Function: "get_feeds", + }, + }) + require.Error(t, err) + require.Equal(t, tt.wantOrigin, err.Origin()) + }) + } +} + func TestView_FailsOnNilViewReply(t *testing.T) { t.Parallel() diff --git a/chain_capabilities/aptos/go.mod b/chain_capabilities/aptos/go.mod index a533e4090..2e5662c63 100644 --- a/chain_capabilities/aptos/go.mod +++ b/chain_capabilities/aptos/go.mod @@ -14,6 +14,7 @@ require ( github.com/stretchr/testify v1.12.1 go.opentelemetry.io/otel v1.46.0 go.opentelemetry.io/otel/metric v1.46.0 + google.golang.org/grpc v1.83.2 google.golang.org/protobuf v1.36.12 ) @@ -132,6 +133,5 @@ require ( golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect - google.golang.org/grpc v1.83.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )