diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 87920955a4..6fbcf7f670 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -93,13 +93,16 @@ const ( TxGroupErrorReasonMsigNotWellFormed // TxGroupErrorReasonLogicSigFailed defines logic sig validation errors TxGroupErrorReasonLogicSigFailed + + // TxGroupErrorReasonNumTracked is number of enum values + TxGroupErrorReasonNumValues ) // ErrTxGroupError is an error from txn pre-validation (well form-ness, signature format, etc). // It can be unwrapped into underlying error, as well as has a specific failure reason code. type ErrTxGroupError struct { err error - reason TxGroupErrorReason + Reason TxGroupErrorReason } // Error returns an error message from the underlying error @@ -112,11 +115,6 @@ func (e *ErrTxGroupError) Unwrap() error { return e.err } -// Reason returns a reason code -func (e *ErrTxGroupError) Reason() TxGroupErrorReason { - return e.reason -} - // PrepareGroupContext prepares a verification group parameter object for a given transaction // group. func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) { @@ -152,11 +150,11 @@ func (g *GroupContext) Equal(other *GroupContext) bool { // It is the caller responsibility to call batchVerifier.Verify(). func txnBatchPrep(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *ErrTxGroupError { if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { - return &ErrTxGroupError{err: errRekeyingNotSupported, reason: TxGroupErrorReasonGeneric} + return &ErrTxGroupError{err: errRekeyingNotSupported, Reason: TxGroupErrorReasonGeneric} } if err := s.Txn.WellFormed(groupCtx.specAddrs, groupCtx.consensusParams); err != nil { - return &ErrTxGroupError{err: err, reason: TxGroupErrorReasonNotWellFormed} + return &ErrTxGroupError{err: err, Reason: TxGroupErrorReasonNotWellFormed} } return stxnCoreChecks(s, txnIdx, groupCtx, verifier) @@ -197,7 +195,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.Blo // re-wrap the error, take underlying one and copy the reason code err = &ErrTxGroupError{ err: fmt.Errorf("transaction %+v invalid : %w", stxn, prepErr.err), - reason: prepErr.reason, + Reason: prepErr.Reason, } return nil, err } @@ -208,7 +206,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.Blo } feeNeeded, overflow := basics.OMul(groupCtx.consensusParams.MinTxnFee, minFeeCount) if overflow { - err = &ErrTxGroupError{err: errTxGroupInvalidFee, reason: TxGroupErrorReasonInvalidFee} + err = &ErrTxGroupError{err: errTxGroupInvalidFee, Reason: TxGroupErrorReasonInvalidFee} return nil, err } // feesPaid may have saturated. That's ok. Since we know @@ -219,7 +217,7 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr bookkeeping.Blo err: fmt.Errorf( "txgroup had %d in fees, which is less than the minimum %d * %d", feesPaid, minFeeCount, groupCtx.consensusParams.MinTxnFee), - reason: TxGroupErrorReasonInvalidFee, + Reason: TxGroupErrorReasonInvalidFee, } return nil, err } @@ -253,10 +251,10 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex if s.Txn.Sender == transactions.StateProofSender && s.Txn.Type == protocol.StateProofTx { return nil } - return &ErrTxGroupError{err: errTxnSigHasNoSig, reason: TxGroupErrorReasonHasNoSig} + return &ErrTxGroupError{err: errTxnSigHasNoSig, Reason: TxGroupErrorReasonHasNoSig} } if numSigs > 1 { - return &ErrTxGroupError{err: errTxnSigNotWellFormed, reason: TxGroupErrorReasonSigNotWellFormed} + return &ErrTxGroupError{err: errTxnSigNotWellFormed, Reason: TxGroupErrorReasonSigNotWellFormed} } if hasSig { @@ -265,17 +263,17 @@ func stxnCoreChecks(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex } if hasMsig { if err := crypto.MultisigBatchPrep(s.Txn, crypto.Digest(s.Authorizer()), s.Msig, batchVerifier); err != nil { - return &ErrTxGroupError{err: fmt.Errorf("multisig validation failed: %w", err), reason: TxGroupErrorReasonMsigNotWellFormed} + return &ErrTxGroupError{err: fmt.Errorf("multisig validation failed: %w", err), Reason: TxGroupErrorReasonMsigNotWellFormed} } return nil } if hasLogicSig { if err := logicSigVerify(s, txnIdx, groupCtx); err != nil { - return &ErrTxGroupError{err: err, reason: TxGroupErrorReasonLogicSigFailed} + return &ErrTxGroupError{err: err, Reason: TxGroupErrorReasonLogicSigFailed} } return nil } - return &ErrTxGroupError{err: errUnknownSignature, reason: TxGroupErrorReasonGeneric} + return &ErrTxGroupError{err: errUnknownSignature, Reason: TxGroupErrorReasonGeneric} } // LogicSigSanityCheck checks that the signature is valid and that the program is basically well formed. diff --git a/data/txHandler.go b/data/txHandler.go index f1ae93ec76..485b6d851c 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -198,8 +198,8 @@ func (handler *TxHandler) postProcessReportErrors(err error) { var txGroupErr *verify.ErrTxGroupError if errors.As(err, &txGroupErr) { - txGroupErr = err.(*verify.ErrTxGroupError) - switch txGroupErr.Reason() { + // txGroupErr = err.(*verify.ErrTxGroupError) + switch txGroupErr.Reason { case verify.TxGroupErrorReasonNotWellFormed: transactionMessagesTxnNotWellFormed.Inc(nil) case verify.TxGroupErrorReasonInvalidFee: diff --git a/data/txHandler_test.go b/data/txHandler_test.go index 693b067876..acb898b327 100644 --- a/data/txHandler_test.go +++ b/data/txHandler_test.go @@ -570,9 +570,75 @@ func runHandlerBenchmark(maxGroupSize int, b *testing.B) { wg.Wait() } -func BenchmarkPostProcessError(b *testing.B) { +func TestPostProcessError(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + collect := func() map[string]float64 { + result := map[string]float64{} + transactionMessagesTxnSigVerificationFailed.AddMetric(result) + transactionMessagesAlreadyCommitted.AddMetric(result) + transactionMessagesTxGroupInvalidFee.AddMetric(result) + transactionMessagesTxnNotWellFormed.AddMetric(result) + transactionMessagesTxnSigNotWellFormed.AddMetric(result) + transactionMessagesTxnMsigNotWellFormed.AddMetric(result) + transactionMessagesTxnLogicSig.AddMetric(result) + return result + } var txh TxHandler - err := errors.New("couldn't find latest resources") + errSome := errors.New("some error") + txh.postProcessReportErrors(errSome) + result := collect() + require.Len(t, result, 0) + + counter := 0 + for i := verify.TxGroupErrorReasonGeneric; i <= verify.TxGroupErrorReasonLogicSigFailed; i++ { + errTxGroup := &verify.ErrTxGroupError{Reason: i} + txh.postProcessReportErrors(errTxGroup) + result = collect() + if i == verify.TxGroupErrorReasonSigNotWellFormed { + // TxGroupErrorReasonSigNotWellFormed and TxGroupErrorReasonHasNoSig increment the same metric + counter-- + require.Equal(t, result[metrics.TransactionMessagesTxnSigNotWellFormed.Name], float64(2)) + } + require.Len(t, result, counter) + counter++ + } + + // there are one less metrics than number of tracked values, and one generic non-tracked so -2 + const expected = int(verify.TxGroupErrorReasonNumValues) - 2 + require.Len(t, result, expected) + + errVerify := crypto.ErrBatchVerificationFailed + txh.postProcessReportErrors(errVerify) + result = collect() + require.Len(t, result, expected+1) +} + +func TestPostProcessErrorWithVerify(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + txn := transactions.Transaction{} + stxn := transactions.SignedTxn{Txn: txn} + + // var cache verify.VerifiedTransactionCache + hdr := bookkeeping.BlockHeader{ + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protocol.ConsensusCurrentVersion, + }, + } + _, err := verify.TxnGroup([]transactions.SignedTxn{stxn}, hdr, nil, nil) + var txGroupErr *verify.ErrTxGroupError + require.ErrorAs(t, err, &txGroupErr) + + result := map[string]float64{} + transactionMessagesTxnNotWellFormed.AddMetric(result) + require.Len(t, result, 0) + + var txh TxHandler txh.postProcessReportErrors(err) + transactionMessagesTxnNotWellFormed.AddMetric(result) + require.Len(t, result, 1) }