Skip to content

Commit a7b37b6

Browse files
committed
Custom metric for SubmitIntent failures
1 parent e1caa58 commit a7b37b6

File tree

3 files changed

+94
-49
lines changed

3 files changed

+94
-49
lines changed

pkg/code/server/transaction/errors.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"google.golang.org/grpc/codes"
910
"google.golang.org/grpc/status"
1011

1112
commonpb "github.com/code-payments/code-protobuf-api/generated/go/common/v1"
1213
transactionpb "github.com/code-payments/code-protobuf-api/generated/go/transaction/v2"
1314

15+
"github.com/code-payments/code-server/pkg/code/data/intent"
1416
"github.com/code-payments/code-server/pkg/code/transaction"
1517
"github.com/code-payments/code-server/pkg/solana"
1618
"github.com/code-payments/code-server/pkg/solana/cvm"
@@ -182,7 +184,11 @@ func toDeniedErrorDetails(err error) *transactionpb.ErrorDetails {
182184
}
183185
}
184186

185-
func handleSubmitIntentError(streamer transactionpb.Transaction_SubmitIntentServer, err error) error {
187+
func handleSubmitIntentError(ctx context.Context, streamer transactionpb.Transaction_SubmitIntentServer, intentRecord *intent.Record, err error) error {
188+
if !shouldFilterSubmitIntentFailureMetricReport(err) {
189+
recordCriticalSubmitIntentFailure(ctx, intentRecord, err)
190+
}
191+
186192
// gRPC status errors are passed through as is
187193
if _, ok := status.FromError(err); ok {
188194
return err
@@ -244,3 +250,24 @@ func handleSubmitIntentStructuredError(streamer transactionpb.Transaction_Submit
244250
}
245251
return streamer.Send(errResp)
246252
}
253+
254+
func shouldFilterSubmitIntentFailureMetricReport(err error) bool {
255+
if statusErr, ok := status.FromError(err); ok {
256+
switch statusErr.Code() {
257+
case codes.Canceled:
258+
return true
259+
}
260+
}
261+
262+
// todo: something more scalable
263+
for _, filteredSubstr := range []string{
264+
context.Canceled.Error(),
265+
"gift card balance has already been claimed",
266+
} {
267+
if strings.Contains(strings.ToLower(err.Error()), filteredSubstr) {
268+
return true
269+
}
270+
}
271+
272+
return false
273+
}

pkg/code/server/transaction/intent.go

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
6060
req, err := s.boundedSubmitIntentRecv(ctx, streamer)
6161
if err != nil {
6262
log.WithError(err).Info("error receiving request from client")
63-
return handleSubmitIntentError(streamer, err)
63+
return handleSubmitIntentError(ctx, streamer, nil, err)
6464
}
6565

6666
start := time.Now()
@@ -70,17 +70,23 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
7070
return status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions is nil")
7171
}
7272

73+
intentId := base58.Encode(submitActionsReq.Id.Value)
74+
log = log.WithField("intent", intentId)
75+
76+
intentRecord := &intent.Record{
77+
IntentId: intentId,
78+
State: intent.StatePending,
79+
CreatedAt: start,
80+
}
81+
7382
// Do some very basic and general validation that cannot be caught by
7483
// proto validation
7584
for i, protoAction := range submitActionsReq.Actions {
7685
if protoAction.Id != uint32(i) {
77-
return handleSubmitIntentError(streamer, status.Errorf(codes.InvalidArgument, "invalid SubmitIntentRequest.SubmitActions.Actions[%d].Id", i))
86+
return handleSubmitIntentError(ctx, streamer, intentRecord, status.Errorf(codes.InvalidArgument, "invalid SubmitIntentRequest.SubmitActions.Actions[%d].Id", i))
7887
}
7988
}
8089

81-
intentId := base58.Encode(submitActionsReq.Id.Value)
82-
log = log.WithField("intent", intentId)
83-
8490
marshalled, err := proto.Marshal(submitActionsReq)
8591
if err == nil {
8692
log = log.WithField("submit_actions_data_dump", base64.URLEncoding.EncodeToString(marshalled))
@@ -102,22 +108,22 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
102108
log = log.WithField("intent_type", "public_distribution")
103109
intentHandler = NewPublicDistributionIntentHandler(s.conf, s.data, s.antispamGuard, s.amlGuard)
104110
default:
105-
return handleSubmitIntentError(streamer, status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions.Metadata is nil"))
111+
return handleSubmitIntentError(ctx, streamer, intentRecord, status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions.Metadata is nil"))
106112
}
107113

108114
// The public key that is the owner and signed the intent. This may not be
109115
// the user depending upon the context of how the user initiated the intent.
110116
submitActionsOwnerAccount, err := common.NewAccountFromProto(submitActionsReq.Owner)
111117
if err != nil {
112118
log.WithError(err).Warn("invalid submit actions owner account")
113-
return handleSubmitIntentError(streamer, err)
119+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
114120
}
115121
log = log.WithField("submit_actions_owner_account", submitActionsOwnerAccount.PublicKey().ToBase58())
116122

117123
createsNewUserOwner, err := intentHandler.CreatesNewUser(ctx, submitActionsReq.Metadata)
118124
if err != nil {
119125
log.WithError(err).Warn("failure checking if intent creates a new user")
120-
return handleSubmitIntentError(streamer, err)
126+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
121127
}
122128

123129
var initiatorOwnerAccount *common.Account
@@ -142,15 +148,15 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
142148
accountInfoRecord, err := s.data.GetAccountInfoByTokenAddress(ctx, base58.Encode(typed.NoPrivacyWithdraw.Destination.Value))
143149
if err != nil && err != account.ErrAccountInfoNotFound {
144150
log.WithError(err).Warn("failure getting user initiator owner account")
145-
return handleSubmitIntentError(streamer, err)
151+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
146152
} else if err == account.ErrAccountInfoNotFound || accountInfoRecord.AccountType != commonpb.AccountType_PRIMARY {
147-
return handleSubmitIntentError(streamer, NewActionValidationError(submitActionsReq.Actions[0], "destination must be a primary account"))
153+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewActionValidationError(submitActionsReq.Actions[0], "destination must be a primary account"))
148154
}
149155

150156
initiatorOwnerAccount, err = common.NewAccountFromPublicKeyString(accountInfoRecord.OwnerAccount)
151157
if err != nil {
152158
log.WithError(err).Warn("failure getting user initiator owner account")
153-
return handleSubmitIntentError(streamer, err)
159+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
154160
}
155161
default:
156162
return NewActionValidationError(submitActionsReq.Actions[0], "expected a no privacy withdraw action")
@@ -161,75 +167,69 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
161167
}
162168
default:
163169
log.Warnf("unhandled owner account type %s", submitActionsOwnerMetadata.Type)
164-
return handleSubmitIntentError(streamer, errors.New("unhandled owner account type"))
170+
return handleSubmitIntentError(ctx, streamer, intentRecord, errors.New("unhandled owner account type"))
165171
}
166172
} else if err == common.ErrOwnerNotFound {
167173
if !createsNewUserOwner {
168-
return handleSubmitIntentError(streamer, NewIntentDeniedError("unexpected owner account"))
174+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewIntentDeniedError("unexpected owner account"))
169175
}
170176
initiatorOwnerAccount = submitActionsOwnerAccount
171177
} else if err != nil {
172178
log.WithError(err).Warn("failure getting owner account metadata")
173-
return handleSubmitIntentError(streamer, err)
179+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
174180
}
175181

176182
log = log.WithField("initiator_owner_account", initiatorOwnerAccount.PublicKey().ToBase58())
183+
intentRecord.InitiatorOwnerAccount = initiatorOwnerAccount.PublicKey().ToBase58()
177184

178185
// Check that all provided signatures in proto messages are valid
179186
signature := submitActionsReq.Signature
180187
submitActionsReq.Signature = nil
181188
err = s.auth.Authenticate(ctx, submitActionsOwnerAccount, submitActionsReq, signature)
182189
if err != nil {
183-
return handleSubmitIntentError(streamer, err)
190+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
184191
}
185192

186193
for _, action := range submitActionsReq.Actions {
187194
switch typedAction := action.Type.(type) {
188195
case *transactionpb.Action_OpenAccount:
189196
authorityAccount, err := common.NewAccountFromProto(typedAction.OpenAccount.Authority)
190197
if err != nil {
191-
return handleSubmitIntentError(streamer, err)
198+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
192199
}
193200

194201
switch typedAction.OpenAccount.AccountType {
195202
case commonpb.AccountType_REMOTE_SEND_GIFT_CARD:
196203
// Remote gift cards are random accounts not owned by a user account's 12 words
197204
if !bytes.Equal(typedAction.OpenAccount.Owner.Value, typedAction.OpenAccount.Authority.Value) {
198-
return handleSubmitIntentError(streamer, NewActionValidationErrorf(action, "owner must be %s", authorityAccount.PublicKey().ToBase58()))
205+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewActionValidationErrorf(action, "owner must be %s", authorityAccount.PublicKey().ToBase58()))
199206
}
200207
default:
201208
// Everything else is owned by a user account's 12 words
202209
if !bytes.Equal(typedAction.OpenAccount.Owner.Value, initiatorOwnerAccount.PublicKey().ToBytes()) {
203-
return handleSubmitIntentError(streamer, NewActionValidationErrorf(action, "owner must be %s", initiatorOwnerAccount.PublicKey().ToBase58()))
210+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewActionValidationErrorf(action, "owner must be %s", initiatorOwnerAccount.PublicKey().ToBase58()))
204211
}
205212
}
206213

207214
signature := typedAction.OpenAccount.AuthoritySignature
208215
typedAction.OpenAccount.AuthoritySignature = nil
209216
err = s.auth.Authenticate(ctx, authorityAccount, typedAction.OpenAccount, signature)
210217
if err != nil {
211-
return handleSubmitIntentError(streamer, err)
218+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
212219
}
213220
}
214221
}
215222

216-
intentRecord := &intent.Record{
217-
IntentId: intentId,
218-
InitiatorOwnerAccount: initiatorOwnerAccount.PublicKey().ToBase58(),
219-
State: intent.StatePending,
220-
CreatedAt: time.Now(),
221-
}
222-
223223
existingIntentRecord, err := s.data.GetIntent(ctx, intentId)
224224
if err != intent.ErrIntentNotFound && err != nil {
225225
log.WithError(err).Warn("failure checking for existing intent record")
226-
return handleSubmitIntentError(streamer, err)
226+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
227227
}
228228

229229
// We're operating on a new intent, so validate we don't have an existing DB record
230230
if existingIntentRecord != nil {
231231
log.Warn("client is attempting to resubmit an intent or reuse an intent id")
232-
return handleSubmitIntentError(streamer, NewStaleStateError("intent already exists"))
232+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewStaleStateError("intent already exists"))
233233
}
234234

235235
// Populate metadata into the new DB record
@@ -245,17 +245,17 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
245245
default:
246246
log.WithError(err).Warn("failure populating intent metadata")
247247
}
248-
return handleSubmitIntentError(streamer, err)
248+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
249249
}
250250

251251
// Check whether the intent is a no-op
252252
isNoop, err := intentHandler.IsNoop(ctx, intentRecord, submitActionsReq.Metadata, submitActionsReq.Actions)
253253
if err != nil {
254254
log.WithError(err).Warn("failure checking if intent is a no-op")
255-
return handleSubmitIntentError(streamer, err)
255+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
256256
} else if isNoop {
257257
if err := streamer.Send(okResp); err != nil {
258-
return handleSubmitIntentError(streamer, err)
258+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
259259
}
260260
return nil
261261
}
@@ -268,7 +268,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
268268
globalBalanceLocks, err := intentHandler.GetBalanceLocks(ctx, intentRecord, submitActionsReq.Metadata)
269269
if err != nil {
270270
log.WithError(err).Warn("failure getting accounts with balances to lock")
271-
return handleSubmitIntentError(streamer, err)
271+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
272272
}
273273
localAccountLocks := make([]*sync.Mutex, 0)
274274
locallyLockedAccounts := make(map[string]any)
@@ -293,7 +293,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
293293
default:
294294
log.WithError(err).Warn("failure checking if new intent was allowed")
295295
}
296-
return handleSubmitIntentError(streamer, err)
296+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
297297
}
298298

299299
// Validate the new intent with app-specific logic
@@ -309,7 +309,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
309309
default:
310310
log.WithError(err).Warn("failure checking if new intent was allowed by integration")
311311
}
312-
return handleSubmitIntentError(streamer, err)
312+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
313313
}
314314

315315
type fulfillmentWithSigningMetadata struct {
@@ -353,11 +353,11 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
353353
actionType = action.NoPrivacyWithdraw
354354
actionHandler, err = NewNoPrivacyWithdrawActionHandler(ctx, s.data, intentRecord, typed.NoPrivacyWithdraw)
355355
default:
356-
return handleSubmitIntentError(streamer, status.Errorf(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions.Actions[%d].Type is nil", i))
356+
return handleSubmitIntentError(ctx, streamer, intentRecord, status.Errorf(codes.InvalidArgument, "SubmitIntentRequest.SubmitActions.Actions[%d].Type is nil", i))
357357
}
358358
if err != nil {
359359
log.WithError(err).Warn("failure initializing action handler")
360-
return handleSubmitIntentError(streamer, errors.New("error initializing action handler"))
360+
return handleSubmitIntentError(ctx, streamer, intentRecord, errors.New("error initializing action handler"))
361361
}
362362

363363
actionHandlers = append(actionHandlers, actionHandler)
@@ -376,7 +376,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
376376
err := actionHandler.PopulateMetadata(actionRecord)
377377
if err != nil {
378378
log.WithError(err).Warn("failure populating action metadata")
379-
return handleSubmitIntentError(streamer, err)
379+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
380380
}
381381

382382
actionRecords = append(actionRecords, actionRecord)
@@ -407,13 +407,13 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
407407
)
408408
if err != nil {
409409
log.WithError(err).Warn("failure selecting nonce pool")
410-
return handleSubmitIntentError(streamer, err)
410+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
411411
}
412412

413413
selectedNonce, err = noncePool.GetNonce(ctx)
414414
if err != nil {
415415
log.WithError(err).Warn("failure selecting available nonce")
416-
return handleSubmitIntentError(streamer, err)
416+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
417417
}
418418
defer func() {
419419
// If we never assign the nonce a signature in the action creation flow,
@@ -434,7 +434,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
434434
)
435435
if err != nil {
436436
log.WithError(err).Warn("failure getting fulfillment metadata")
437-
return handleSubmitIntentError(streamer, err)
437+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
438438
}
439439

440440
actionId = protoAction.Id
@@ -526,20 +526,20 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
526526
// Send server parameters, which initiates phase 2 of the RPC for generating
527527
// and receiving signatures.
528528
if err := streamer.Send(serverParametersResp); err != nil {
529-
return handleSubmitIntentError(streamer, err)
529+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
530530
}
531531

532532
req, err = s.boundedSubmitIntentRecv(ctx, streamer)
533533
if err != nil {
534534
log.WithError(err).Info("error receiving request from client")
535-
return handleSubmitIntentError(streamer, err)
535+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
536536
}
537537

538538
tsAfterSignatureSubmission = time.Now()
539539

540540
submitSignaturesReq := req.GetSubmitSignatures()
541541
if submitSignaturesReq == nil {
542-
return handleSubmitIntentError(streamer, status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitSignatures is nil"))
542+
return handleSubmitIntentError(ctx, streamer, intentRecord, status.Error(codes.InvalidArgument, "SubmitIntentRequest.SubmitSignatures is nil"))
543543
}
544544

545545
marshalled, err := proto.Marshal(submitSignaturesReq)
@@ -549,9 +549,9 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
549549

550550
// Validate the number of signatures
551551
if len(submitSignaturesReq.Signatures) < len(unsignedFulfillments) {
552-
return handleSubmitIntentError(streamer, ErrMissingSignature)
552+
return handleSubmitIntentError(ctx, streamer, intentRecord, ErrMissingSignature)
553553
} else if len(submitSignaturesReq.Signatures) > len(unsignedFulfillments) {
554-
return handleSubmitIntentError(streamer, ErrTooManySignatures)
554+
return handleSubmitIntentError(ctx, streamer, intentRecord, ErrTooManySignatures)
555555
}
556556

557557
// Validate the signature value and update the fulfillment
@@ -648,9 +648,9 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
648648
if err != nil {
649649
if strings.Contains(err.Error(), "stale") || strings.Contains(err.Error(), "exist") {
650650
log.WithError(err).Info("race condition detected")
651-
return handleSubmitIntentError(streamer, NewStaleStateErrorf("race detected: %s", err.Error()))
651+
return handleSubmitIntentError(ctx, streamer, intentRecord, NewStaleStateErrorf("race detected: %s", err.Error()))
652652
}
653-
return handleSubmitIntentError(streamer, err)
653+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
654654
}
655655

656656
go func() {
@@ -689,7 +689,7 @@ func (s *transactionServer) SubmitIntent(streamer transactionpb.Transaction_Subm
689689

690690
// RPC is finished. Send success to the client
691691
if err := streamer.Send(okResp); err != nil {
692-
return handleSubmitIntentError(streamer, err)
692+
return handleSubmitIntentError(ctx, streamer, intentRecord, err)
693693
}
694694
return nil
695695
}

0 commit comments

Comments
 (0)