recordTransactionSingle preserves the existing direct transaction-processing semantics by running the single-transaction flow under the balance lock.
(ctx context.Context, transaction *model.Transaction)
| 781 | // recordTransactionSingle preserves the existing direct transaction-processing semantics by |
| 782 | // running the single-transaction flow under the balance lock. |
| 783 | func (l *LedgerForge) recordTransactionSingle(ctx context.Context, transaction *model.Transaction) (*model.Transaction, error) { |
| 784 | ctx, span := tracer.Start(ctx, "RecordTransaction") |
| 785 | defer span.End() |
| 786 | |
| 787 | startTime := time.Now() |
| 788 | |
| 789 | result, err := l.executeWithLock(ctx, transaction, func(ctx context.Context) (*model.Transaction, error) { |
| 790 | // Execute pre-transaction hooks |
| 791 | if err := l.Hooks.ExecutePreHooks(ctx, transaction.TransactionID, transaction); err != nil { |
| 792 | span.RecordError(err) |
| 793 | return nil, err |
| 794 | } |
| 795 | |
| 796 | // Validate and prepare the transaction, including retrieving source and destination balances |
| 797 | transaction, sourceBalance, destinationBalance, err := l.validateAndPrepareTransaction(ctx, transaction) |
| 798 | if err != nil { |
| 799 | span.RecordError(err) |
| 800 | return nil, err |
| 801 | } |
| 802 | |
| 803 | // Process the balances by applying the transaction |
| 804 | if err := l.processBalances(ctx, transaction, sourceBalance, destinationBalance); err != nil { |
| 805 | span.RecordError(err) |
| 806 | return nil, err |
| 807 | } |
| 808 | |
| 809 | work, skipPersist := l.buildTransactionExecutionWork(ctx, transaction, sourceBalance, destinationBalance) |
| 810 | if skipPersist { |
| 811 | span.AddEvent("Transaction with zero amount discarded, not persisted", trace.WithAttributes(attribute.String("transaction.id", work.transaction.TransactionID))) |
| 812 | return work.transaction, nil |
| 813 | } |
| 814 | |
| 815 | work, err = l.persistSingleTransactionExecutionWork(ctx, work) |
| 816 | if err != nil { |
| 817 | span.RecordError(err) |
| 818 | return nil, err |
| 819 | } |
| 820 | |
| 821 | l.runTransactionPostCommitWork(ctx, span, []*model.Balance{sourceBalance, destinationBalance}, []queuedBatchPostCommitWork{work}) |
| 822 | |
| 823 | span.AddEvent("Transaction processed", trace.WithAttributes(attribute.String("transaction.id", work.transaction.TransactionID))) |
| 824 | logrus.Infof("Transaction %s processed successfully", work.transaction.TransactionID) |
| 825 | return work.transaction, nil |
| 826 | }) |
| 827 | |
| 828 | // Record metrics regardless of success or failure. |
| 829 | duration := time.Since(startTime).Seconds() |
| 830 | status := "error" |
| 831 | currency := transaction.Currency |
| 832 | if result != nil { |
| 833 | status = result.Status |
| 834 | currency = result.Currency |
| 835 | } |
| 836 | metrics.TransactionDuration.Record(ctx, duration, |
| 837 | otelmetric.WithAttributes(attribute.String("status", status)), |
| 838 | ) |
| 839 | if err == nil { |
| 840 | metrics.TransactionTotal.Add(ctx, 1, |
no test coverage detected