From 6bb89dd46e163b057cb4c7c32cdd8e3a8c418147 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Wed, 8 Jan 2025 20:34:06 +0100 Subject: [PATCH 01/34] feature: new race condition implementation --- .../internal/adapters/http/in/transaction.go | 63 ++++++++++++++----- .../internal/adapters/redis/consumer.redis.go | 49 +++++++++++++++ .../internal/adapters/redis/redis.mock.go | 15 +++++ .../command/create-idempotency-key.go | 2 +- .../command/create-lock-race-condition.go | 49 +++++++++++++++ pkg/utils.go | 12 ++++ 6 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 components/transaction/internal/services/command/create-lock-race-condition.go diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index b200ab1c..f1b225b4 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -62,7 +62,7 @@ func (handler *TransactionHandler) CreateTransactionJSON(p any, c *fiber.Ctx) er parserDSL := input.FromDSl() logger.Infof("Request to create an transaction with details: %#v", parserDSL) - response := handler.createTransaction(c, logger, *parserDSL) + response := handler.createTransaction(c, logger, *parserDSL, false) return response } @@ -130,7 +130,7 @@ func (handler *TransactionHandler) CreateTransactionDSL(c *fiber.Ctx) error { return http.WithError(c, err) } - response := handler.createTransaction(c, logger, parserDSL) + response := handler.createTransaction(c, logger, parserDSL, false) return response } @@ -379,42 +379,71 @@ func (handler *TransactionHandler) GetAllTransactions(c *fiber.Ctx) error { } // createTransaction func that received struct from DSL parsed and create Transaction -func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.Logger, parserDSL goldModel.Transaction) error { +func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.Logger, parserDSL goldModel.Transaction, isLocked bool) error { ctx := c.UserContext() tracer := pkg.NewTracerFromContext(ctx) organizationID := c.Locals("organization_id").(uuid.UUID) ledgerID := c.Locals("ledger_id").(uuid.UUID) - _, spanRedis := tracer.Start(ctx, "handler.create_transaction_idempotency") - - ts, _ := pkg.StructToJSONString(parserDSL) - hash := pkg.HashSHA256(ts) - key, ttl := http.GetIdempotencyKeyAndTTL(c) + _, spanValidateDSL := tracer.Start(ctx, "handler.create_transaction_validate_dsl") - err := handler.Command.CreateOrCheckIdempotencyKey(ctx, organizationID, ledgerID, key, hash, ttl) + validate, err := goldModel.ValidateSendSourceAndDistribute(parserDSL) if err != nil { - mopentelemetry.HandleSpanError(&spanRedis, "Redis idempotency key", err) + mopentelemetry.HandleSpanError(&spanValidateDSL, "Failed to validate send source and distribute", err) - logger.Error("Redis idempotency key:", err.Error()) + logger.Error("Validation failed:", err.Error()) return http.WithError(c, err) } - spanRedis.End() + spanValidateDSL.End() - _, spanValidateDSL := tracer.Start(ctx, "handler.create_transaction_validate_dsl") + _, spanRaceCondition := tracer.Start(ctx, "handler.create_transaction.race_condition") - validate, err := goldModel.ValidateSendSourceAndDistribute(parserDSL) + for _, alias := range validate.Aliases { + if !isLocked { + isLocked, err = handler.Command.CreateLockAccount(ctx, organizationID, ledgerID, alias) + if err != nil { + mopentelemetry.HandleSpanError(&spanRaceCondition, "Race Condition", err) + + logger.Error("Race Condition:", err.Error()) + + return http.WithError(c, err) + } + } + + for !isLocked { + err = handler.createTransaction(c, logger, parserDSL, isLocked) + if err != nil { + mopentelemetry.HandleSpanError(&spanRaceCondition, "Race Condition", err) + + logger.Error("Race Condition:", err.Error()) + + return http.WithError(c, err) + } + } + + } + + spanRaceCondition.End() + + _, spanRedis := tracer.Start(ctx, "handler.create_transaction_idempotency") + + ts, _ := pkg.StructToJSONString(parserDSL) + hash := pkg.HashSHA256(ts) + key, ttl := http.GetIdempotencyKeyAndTTL(c) + + err = handler.Command.CreateOrCheckIdempotencyKey(ctx, organizationID, ledgerID, key, hash, ttl) if err != nil { - mopentelemetry.HandleSpanError(&spanValidateDSL, "Failed to validate send source and distribute", err) + mopentelemetry.HandleSpanError(&spanRedis, "Redis idempotency key", err) - logger.Error("Validation failed:", err.Error()) + logger.Error("Redis idempotency key:", err.Error()) return http.WithError(c, err) } - spanValidateDSL.End() + spanRedis.End() ctxGetAccounts, spanGetAccounts := tracer.Start(ctx, "handler.create_transaction.get_accounts") diff --git a/components/transaction/internal/adapters/redis/consumer.redis.go b/components/transaction/internal/adapters/redis/consumer.redis.go index 22bb5574..29911af5 100644 --- a/components/transaction/internal/adapters/redis/consumer.redis.go +++ b/components/transaction/internal/adapters/redis/consumer.redis.go @@ -14,6 +14,7 @@ import ( //go:generate mockgen --destination=redis.mock.go --package=redis . RedisRepository type RedisRepository interface { Set(ctx context.Context, key, value string, ttl time.Duration) error + SetNX(ctx context.Context, key, value string, ttl time.Duration) (bool, error) Get(ctx context.Context, key string) (string, error) Del(ctx context.Context, key string) error } @@ -61,6 +62,32 @@ func (rr *RedisConsumerRepository) Set(ctx context.Context, key, value string, t return nil } +func (rr *RedisConsumerRepository) SetNX(ctx context.Context, key, value string, ttl time.Duration) (bool, error) { + logger := pkg.NewLoggerFromContext(ctx) + tracer := pkg.NewTracerFromContext(ctx) + + ctx, span := tracer.Start(ctx, "redis.set") + defer span.End() + + rds, err := rr.conn.GetClient(ctx) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get redis", err) + + return false, err + } + + logger.Infof("value of ttl: %v", ttl*time.Second) + + isLocked, err := rds.SetNX(ctx, key, value, ttl*time.Second).Result() + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to set on redis", err) + + return false, err + } + + return isLocked, nil +} + func (rr *RedisConsumerRepository) Get(ctx context.Context, key string) (string, error) { logger := pkg.NewLoggerFromContext(ctx) tracer := pkg.NewTracerFromContext(ctx) @@ -88,5 +115,27 @@ func (rr *RedisConsumerRepository) Get(ctx context.Context, key string) (string, } func (rr *RedisConsumerRepository) Del(ctx context.Context, key string) error { + logger := pkg.NewLoggerFromContext(ctx) + tracer := pkg.NewTracerFromContext(ctx) + + ctx, span := tracer.Start(ctx, "redis.del") + defer span.End() + + rds, err := rr.conn.GetClient(ctx) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to del redis", err) + + return err + } + + val, err := rds.Del(ctx, key).Result() + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to del on redis", err) + + return err + } + + logger.Infof("value : %v", val) + return nil } diff --git a/components/transaction/internal/adapters/redis/redis.mock.go b/components/transaction/internal/adapters/redis/redis.mock.go index 3506c88b..3e610dc1 100644 --- a/components/transaction/internal/adapters/redis/redis.mock.go +++ b/components/transaction/internal/adapters/redis/redis.mock.go @@ -82,3 +82,18 @@ func (mr *MockRedisRepositoryMockRecorder) Set(arg0, arg1, arg2, arg3 any) *gomo mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisRepository)(nil).Set), arg0, arg1, arg2, arg3) } + +// SetNX mocks base method. +func (m *MockRedisRepository) SetNX(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetNX", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SetNX indicates an expected call of SetNX. +func (mr *MockRedisRepositoryMockRecorder) SetNX(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNX", reflect.TypeOf((*MockRedisRepository)(nil).SetNX), arg0, arg1, arg2, arg3) +} diff --git a/components/transaction/internal/services/command/create-idempotency-key.go b/components/transaction/internal/services/command/create-idempotency-key.go index 15f8a393..1a7e0d6b 100644 --- a/components/transaction/internal/services/command/create-idempotency-key.go +++ b/components/transaction/internal/services/command/create-idempotency-key.go @@ -22,7 +22,7 @@ func (uc *UseCase) CreateOrCheckIdempotencyKey(ctx context.Context, organization key = hash } - internalKey := organizationID.String() + ":" + ledgerID.String() + ":" + key + internalKey := pkg.InternalKey(organizationID, ledgerID, key) value, err := uc.RedisRepo.Get(ctx, internalKey) if err != nil { diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go new file mode 100644 index 00000000..5bfe0806 --- /dev/null +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -0,0 +1,49 @@ +package command + +import ( + "context" + "github.com/LerianStudio/midaz/pkg" + "github.com/LerianStudio/midaz/pkg/mopentelemetry" + "github.com/google/uuid" +) + +const TimeSetLock = 30 + +func (uc *UseCase) CreateLockAccount(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) (bool, error) { + logger := pkg.NewLoggerFromContext(context.Background()) + tracer := pkg.NewTracerFromContext(context.Background()) + + ctx, span := tracer.Start(ctx, "query.create_lock_race_condition") + defer span.End() + + internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + + isLocked, err := uc.RedisRepo.SetNX(ctx, internalKey, "processing account...", TimeSetLock) + if err != nil { + logger.Infof("Error to acquire lock: %v", err) + + return false, err + } + + logger.Infof("Account lock on redis: %v", internalKey) + return isLocked, nil +} + +func (uc *UseCase) ReleaseLockAccount(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) { + logger := pkg.NewLoggerFromContext(context.Background()) + tracer := pkg.NewTracerFromContext(context.Background()) + + ctx, span := tracer.Start(ctx, "query.release_lock_account") + defer span.End() + + internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + + logger.Infof("Account release lock on redis: %v", internalKey) + + err := uc.RedisRepo.Del(ctx, internalKey) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) + + logger.Errorf("Failed to release Accounts lock: %v", err) + } +} diff --git a/pkg/utils.go b/pkg/utils.go index 00b342fd..de15311b 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -278,3 +278,15 @@ func Reverse[T any](s []T) []T { return s } + +func InternalKey(organizationID, ledgerID uuid.UUID, key string) string { + internalKey := organizationID.String() + ":" + ledgerID.String() + ":" + key + + return internalKey +} + +func LockInternalKey(organizationID, ledgerID uuid.UUID, key string) string { + lockInternalKey := "lock:" + InternalKey(organizationID, ledgerID, key) + + return lockInternalKey +} From 3248ee7e8fca50dc8882327e94f4c9fcbfd3529e Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 12:07:34 +0100 Subject: [PATCH 02/34] feature: create race condition using gorotine and chanel --- .../internal/adapters/http/in/transaction.go | 65 +++++++---------- .../command/create-lock-race-condition.go | 70 ++++++++++++++----- 2 files changed, 76 insertions(+), 59 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index f1b225b4..8f1d7f09 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -386,6 +386,23 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L organizationID := c.Locals("organization_id").(uuid.UUID) ledgerID := c.Locals("ledger_id").(uuid.UUID) + _, spanIdempotency := tracer.Start(ctx, "handler.create_transaction_idempotency") + + ts, _ := pkg.StructToJSONString(parserDSL) + hash := pkg.HashSHA256(ts) + key, ttl := http.GetIdempotencyKeyAndTTL(c) + + err := handler.Command.CreateOrCheckIdempotencyKey(ctx, organizationID, ledgerID, key, hash, ttl) + if err != nil { + mopentelemetry.HandleSpanError(&spanIdempotency, "Redis idempotency key", err) + + logger.Infof("Redis idempotency key: %v", err.Error()) + + return http.WithError(c, err) + } + + spanIdempotency.End() + _, spanValidateDSL := tracer.Start(ctx, "handler.create_transaction_validate_dsl") validate, err := goldModel.ValidateSendSourceAndDistribute(parserDSL) @@ -401,50 +418,10 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L _, spanRaceCondition := tracer.Start(ctx, "handler.create_transaction.race_condition") - for _, alias := range validate.Aliases { - if !isLocked { - isLocked, err = handler.Command.CreateLockAccount(ctx, organizationID, ledgerID, alias) - if err != nil { - mopentelemetry.HandleSpanError(&spanRaceCondition, "Race Condition", err) - - logger.Error("Race Condition:", err.Error()) - - return http.WithError(c, err) - } - } - - for !isLocked { - err = handler.createTransaction(c, logger, parserDSL, isLocked) - if err != nil { - mopentelemetry.HandleSpanError(&spanRaceCondition, "Race Condition", err) - - logger.Error("Race Condition:", err.Error()) - - return http.WithError(c, err) - } - } - - } + handler.Command.AllKeysUnlocked(ctx, organizationID, ledgerID, validate.Aliases) spanRaceCondition.End() - _, spanRedis := tracer.Start(ctx, "handler.create_transaction_idempotency") - - ts, _ := pkg.StructToJSONString(parserDSL) - hash := pkg.HashSHA256(ts) - key, ttl := http.GetIdempotencyKeyAndTTL(c) - - err = handler.Command.CreateOrCheckIdempotencyKey(ctx, organizationID, ledgerID, key, hash, ttl) - if err != nil { - mopentelemetry.HandleSpanError(&spanRedis, "Redis idempotency key", err) - - logger.Error("Redis idempotency key:", err.Error()) - - return http.WithError(c, err) - } - - spanRedis.End() - ctxGetAccounts, spanGetAccounts := tracer.Start(ctx, "handler.create_transaction.get_accounts") token := http.GetTokenHeader(c) @@ -526,6 +503,12 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanProcessAccounts.End() + _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition") + + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases) + + spanReleaseLock.End() + ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status") //TODO: use event driven and broken and parts diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 5bfe0806..52cf84c1 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -5,45 +5,79 @@ import ( "github.com/LerianStudio/midaz/pkg" "github.com/LerianStudio/midaz/pkg/mopentelemetry" "github.com/google/uuid" + "sync" + "time" ) -const TimeSetLock = 30 +const TimeSetLock = 2 -func (uc *UseCase) CreateLockAccount(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) (bool, error) { +func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) - ctx, span := tracer.Start(ctx, "query.create_lock_race_condition") + ctx, span := tracer.Start(ctx, "redis.all_keys_unlocked") defer span.End() - internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + var wg sync.WaitGroup + resultChan := make(chan bool, len(keys)) - isLocked, err := uc.RedisRepo.SetNX(ctx, internalKey, "processing account...", TimeSetLock) - if err != nil { - logger.Infof("Error to acquire lock: %v", err) + for _, key := range keys { + internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) - return false, err + logger.Infof("Account try to lock on redis: %v", internalKey) + + wg.Add(1) + go uc.checkAndReleaseLock(ctx, &wg, internalKey, resultChan) } - logger.Infof("Account lock on redis: %v", internalKey) - return isLocked, nil + wg.Wait() + close(resultChan) +} + +func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, internalKey string, resultChan chan bool) { + logger := pkg.NewLoggerFromContext(context.Background()) + tracer := pkg.NewTracerFromContext(context.Background()) + + ctx, span := tracer.Start(ctx, "redis.check_and_release_lock") + defer span.End() + + defer wg.Done() + + for { + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, "processing account...", TimeSetLock) + if err != nil { + resultChan <- false + return + } + + logger.Infof("Account locked on redis: %v", internalKey) + + if success { + resultChan <- true + return + } + + time.Sleep(500 * time.Millisecond) + } } -func (uc *UseCase) ReleaseLockAccount(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) { +func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) - ctx, span := tracer.Start(ctx, "query.release_lock_account") + ctx, span := tracer.Start(ctx, "redis.delete_locks") defer span.End() - internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + for _, key := range keys { + internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) - logger.Infof("Account release lock on redis: %v", internalKey) + logger.Infof("Account releasing lock on redis: %v", internalKey) - err := uc.RedisRepo.Del(ctx, internalKey) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) + err := uc.RedisRepo.Del(ctx, internalKey) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) - logger.Errorf("Failed to release Accounts lock: %v", err) + logger.Errorf("Failed to release Accounts lock: %v", err) + } } } From 5a7988e161e0bb64a64149c1871ba2f0c9f2dbd5 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 12:08:07 +0100 Subject: [PATCH 03/34] fix: improve idempotency using setnx; :bug: --- .../services/command/create-idempotency-key.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/components/transaction/internal/services/command/create-idempotency-key.go b/components/transaction/internal/services/command/create-idempotency-key.go index 1a7e0d6b..bc09f625 100644 --- a/components/transaction/internal/services/command/create-idempotency-key.go +++ b/components/transaction/internal/services/command/create-idempotency-key.go @@ -24,17 +24,12 @@ func (uc *UseCase) CreateOrCheckIdempotencyKey(ctx context.Context, organization internalKey := pkg.InternalKey(organizationID, ledgerID, key) - value, err := uc.RedisRepo.Get(ctx, internalKey) + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, ttl) if err != nil { - logger.Error("Error to get idempotency key on redis failed:", err.Error()) + logger.Error("Error to lock idempotency key on redis failed:", err.Error()) } - if value == "" { - err = uc.RedisRepo.Set(ctx, internalKey, hash, ttl) - if err != nil { - logger.Error("Error to set idempotency key on redis failed:", err.Error()) - } - } else { + if !success { err = pkg.ValidateBusinessError(constant.ErrIdempotencyKey, "CreateOrCheckIdempotencyKey", key) mopentelemetry.HandleSpanError(&span, "Failed exists value on redis with this key", err) From b84dc7a5c886496746a382bda6059afb77583c47 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 13:53:53 +0100 Subject: [PATCH 04/34] refactor: change get accounts to command to test :hammer: --- components/ledger/internal/adapters/grpc/in/account.go | 6 +++--- .../services/{query => command}/get-alias-accounts.go | 2 +- .../services/{query => command}/get-alias-accounts_test.go | 5 +++-- .../services/{query => command}/get-ids-accounts.go | 2 +- .../services/{query => command}/get-ids-accounts_test.go | 5 +++-- 5 files changed, 11 insertions(+), 9 deletions(-) rename components/ledger/internal/services/{query => command}/get-alias-accounts.go (98%) rename components/ledger/internal/services/{query => command}/get-alias-accounts_test.go (96%) rename components/ledger/internal/services/{query => command}/get-ids-accounts.go (98%) rename components/ledger/internal/services/{query => command}/get-ids-accounts_test.go (96%) diff --git a/components/ledger/internal/adapters/grpc/in/account.go b/components/ledger/internal/adapters/grpc/in/account.go index 31aa45b5..a2915fa2 100644 --- a/components/ledger/internal/adapters/grpc/in/account.go +++ b/components/ledger/internal/adapters/grpc/in/account.go @@ -60,7 +60,7 @@ func (ap *AccountProto) GetAccountsByIds(ctx context.Context, ids *account.Accou return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), strings.Join(invalidUUIDs, ", ")) } - acc, err := ap.Query.ListAccountsByIDs(ctx, organizationUUID, ledgerUUID, uuids) + acc, err := ap.Command.ListAccountsByIDs(ctx, organizationUUID, ledgerUUID, uuids) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) @@ -99,7 +99,7 @@ func (ap *AccountProto) GetAccountsByAliases(ctx context.Context, aliases *accou return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) } - acc, err := ap.Query.ListAccountsByAlias(ctx, organizationUUID, ledgerUUID, aliases.GetAliases()) + acc, err := ap.Command.ListAccountsByAlias(ctx, organizationUUID, ledgerUUID, aliases.GetAliases()) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by aliases for grpc", err) @@ -177,7 +177,7 @@ func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.Acco organizationID := update.GetOrganizationId() ledgerID := update.GetLedgerId() - acc, err := ap.Query.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) + acc, err := ap.Command.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) diff --git a/components/ledger/internal/services/query/get-alias-accounts.go b/components/ledger/internal/services/command/get-alias-accounts.go similarity index 98% rename from components/ledger/internal/services/query/get-alias-accounts.go rename to components/ledger/internal/services/command/get-alias-accounts.go index 4b5ed64c..ee43040e 100644 --- a/components/ledger/internal/services/query/get-alias-accounts.go +++ b/components/ledger/internal/services/command/get-alias-accounts.go @@ -1,4 +1,4 @@ -package query +package command import ( "context" diff --git a/components/ledger/internal/services/query/get-alias-accounts_test.go b/components/ledger/internal/services/command/get-alias-accounts_test.go similarity index 96% rename from components/ledger/internal/services/query/get-alias-accounts_test.go rename to components/ledger/internal/services/command/get-alias-accounts_test.go index dbe04334..2b86d9f5 100644 --- a/components/ledger/internal/services/query/get-alias-accounts_test.go +++ b/components/ledger/internal/services/command/get-alias-accounts_test.go @@ -1,8 +1,9 @@ -package query +package command import ( "context" "errors" + "github.com/LerianStudio/midaz/components/ledger/internal/services/query" "testing" "github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account" @@ -20,7 +21,7 @@ func TestListAccountsByAlias(t *testing.T) { mockAccountRepo := account.NewMockRepository(ctrl) - uc := &UseCase{ + uc := &query.UseCase{ AccountRepo: mockAccountRepo, } diff --git a/components/ledger/internal/services/query/get-ids-accounts.go b/components/ledger/internal/services/command/get-ids-accounts.go similarity index 98% rename from components/ledger/internal/services/query/get-ids-accounts.go rename to components/ledger/internal/services/command/get-ids-accounts.go index 7074f0de..bcc55fd0 100644 --- a/components/ledger/internal/services/query/get-ids-accounts.go +++ b/components/ledger/internal/services/command/get-ids-accounts.go @@ -1,4 +1,4 @@ -package query +package command import ( "context" diff --git a/components/ledger/internal/services/query/get-ids-accounts_test.go b/components/ledger/internal/services/command/get-ids-accounts_test.go similarity index 96% rename from components/ledger/internal/services/query/get-ids-accounts_test.go rename to components/ledger/internal/services/command/get-ids-accounts_test.go index e690770c..6044d939 100644 --- a/components/ledger/internal/services/query/get-ids-accounts_test.go +++ b/components/ledger/internal/services/command/get-ids-accounts_test.go @@ -1,8 +1,9 @@ -package query +package command import ( "context" "errors" + "github.com/LerianStudio/midaz/components/ledger/internal/services/query" "testing" "github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account" @@ -19,7 +20,7 @@ func TestListAccountsByIDs(t *testing.T) { mockAccountRepo := account.NewMockRepository(ctrl) - uc := &UseCase{ + uc := &query.UseCase{ AccountRepo: mockAccountRepo, } From ddb6a60ca16b3560d6b8c0802e12fb9a246894bf Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 14:03:15 +0100 Subject: [PATCH 05/34] fix: reduce lock time :bug: --- .../internal/services/command/create-lock-race-condition.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 52cf84c1..e324d608 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -9,7 +9,7 @@ import ( "time" ) -const TimeSetLock = 2 +const TimeSetLock = 1 func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string) { logger := pkg.NewLoggerFromContext(context.Background()) @@ -57,7 +57,7 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, return } - time.Sleep(500 * time.Millisecond) + time.Sleep(250 * time.Millisecond) } } From f1ee3ad132573c6715933cbe902001646f43ed85 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 16:35:45 +0100 Subject: [PATCH 06/34] refactor: add update all in one :hammer: --- .../postgres/account/account.postgresql.go | 65 +++++++++++++++++++ .../services/command/update-account-id.go | 26 ++++++++ 2 files changed, 91 insertions(+) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index ebf8bf24..75b18679 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "github.com/LerianStudio/midaz/pkg/mgrpc/account" "reflect" "strconv" "strings" @@ -41,6 +42,7 @@ type Repository interface { ListAccountsByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error) ListAccountsByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, aliases []string) ([]*mmodel.Account, error) UpdateAccountByID(ctx context.Context, organizationID, ledgerID uuid.UUID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error) + UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, acc []*account.Account) error } // AccountPostgreSQLRepository is a Postgresql-specific implementation of the AccountRepository. @@ -939,3 +941,66 @@ func (r *AccountPostgreSQLRepository) UpdateAccountByID(ctx context.Context, org return record.ToEntity(), nil } + +func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error { + tracer := pkg.NewTracerFromContext(ctx) + ctx, span0 := tracer.Start(ctx, "postgres.update_accounts.get_db") + defer span0.End() + + db, err := r.connection.GetDB() + if err != nil { + mopentelemetry.HandleSpanError(&span0, "Failed to get database connection", err) + + return err + } + + ctx, span1 := tracer.Start(ctx, "postgres.update_accounts.begin") + defer span1.End() + + tx, err := db.Begin() + if err != nil { + mopentelemetry.HandleSpanError(&span1, "Failed to init transaction", err) + + return err + } + + for _, acc := range accounts { + var updates []string + var args []any + + updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.Available) + + updates = append(updates, "on_hold_balance = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.OnHold) + + updates = append(updates, "balance_scale = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.Scale) + + updates = append(updates, "updated_at = $"+strconv.Itoa(len(args)+1)) + args = append(args, time.Now(), organizationID, ledgerID, acc.Id) + + query := `UPDATE account SET ` + strings.Join(updates, ", ") + + ` WHERE organization_id = $` + strconv.Itoa(len(args)-2) + + ` AND ledger_id = $` + strconv.Itoa(len(args)-1) + + ` AND id = $` + strconv.Itoa(len(args)) + + ` AND deleted_at IS NULL` + + _, err := tx.ExecContext(ctx, query, args...) + if err != nil { + mopentelemetry.HandleSpanError(&span1, "Failed to update account", err) + + return err + } + } + + if err := tx.Commit().Error; err != nil { + err := pkg.ValidateBusinessError(constant.ErrEntityNotFound, reflect.TypeOf(mmodel.Account{}).Name()) + + mopentelemetry.HandleSpanError(&span1, "Failed to commit accounts", err) + + return err + } + + return nil +} diff --git a/components/ledger/internal/services/command/update-account-id.go b/components/ledger/internal/services/command/update-account-id.go index 51a2c39c..29650a9c 100644 --- a/components/ledger/internal/services/command/update-account-id.go +++ b/components/ledger/internal/services/command/update-account-id.go @@ -3,6 +3,7 @@ package command import ( "context" "errors" + "github.com/LerianStudio/midaz/pkg/mgrpc/account" "reflect" "github.com/LerianStudio/midaz/components/ledger/internal/services" @@ -43,3 +44,28 @@ func (uc *UseCase) UpdateAccountByID(ctx context.Context, organizationID, ledger return accountUpdated, nil } + +func (uc *UseCase) UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error { + logger := pkg.NewLoggerFromContext(ctx) + tracer := pkg.NewTracerFromContext(ctx) + + ctx, span := tracer.Start(ctx, "command.update_accounts") + defer span.End() + + logger.Infof("Trying to update accounts") + + err := uc.AccountRepo.UpdateAccounts(ctx, organizationID, ledgerID, accounts) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to update account on repo by id", err) + + logger.Errorf("Error updating account on repo by id: %v", err) + + if errors.Is(err, services.ErrDatabaseItemNotFound) { + return pkg.ValidateBusinessError(constant.ErrAccountIDNotFound, reflect.TypeOf(mmodel.Account{}).Name()) + } + + return err + } + + return nil +} From 022c3c90f6827149bc8ba4e78b2acb314895bbc8 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 17:38:16 +0100 Subject: [PATCH 07/34] fix: updates to improve race condition :bug: --- .../internal/adapters/grpc/in/account.go | 151 +++++++++++------- .../postgres/account/account.postgresql.go | 19 ++- .../services/command/update-account-id.go | 4 +- .../internal/adapters/http/in/transaction.go | 7 +- .../command/create-lock-race-condition.go | 25 +-- .../services/command/update-transaction.go | 4 +- 6 files changed, 124 insertions(+), 86 deletions(-) diff --git a/components/ledger/internal/adapters/grpc/in/account.go b/components/ledger/internal/adapters/grpc/in/account.go index a2915fa2..656639ce 100644 --- a/components/ledger/internal/adapters/grpc/in/account.go +++ b/components/ledger/internal/adapters/grpc/in/account.go @@ -120,6 +120,83 @@ func (ap *AccountProto) GetAccountsByAliases(ctx context.Context, aliases *accou return &response, nil } +//// UpdateAccounts is a method that update Account balances by a given ids. +//func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) { +// logger := pkg.NewLoggerFromContext(ctx) +// tracer := pkg.NewTracerFromContext(ctx) +// +// ctx, span := tracer.Start(ctx, "handler.UpdateAccounts") +// defer span.End() +// +// accounts := make([]*account.Account, 0) +// +// uuids := make([]uuid.UUID, 0) +// +// for _, getacc := range update.GetAccounts() { +// if pkg.IsNilOrEmpty(&getacc.Id) { +// mopentelemetry.HandleSpanError(&span, "Failed to update Accounts because id is empty", nil) +// +// logger.Errorf("Failed to update Accounts because id is empty") +// +// return nil, pkg.ValidateBusinessError(constant.ErrNoAccountIDsProvided, reflect.TypeOf(mmodel.Account{}).Name()) +// } +// +// balance := mmodel.Balance{ +// Available: &getacc.Balance.Available, +// OnHold: &getacc.Balance.OnHold, +// Scale: &getacc.Balance.Scale, +// } +// +// organizationUUID, err := uuid.Parse(getacc.GetOrganizationId()) +// if err != nil { +// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationUUID) +// } +// +// ledgerUUID, err := uuid.Parse(getacc.GetLedgerId()) +// if err != nil { +// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) +// } +// +// accountUUID, err := uuid.Parse(getacc.GetId()) +// if err != nil { +// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), accountUUID) +// } +// +// _, err = ap.Command.UpdateAccountByID(ctx, organizationUUID, ledgerUUID, accountUUID, &balance) +// if err != nil { +// mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err) +// +// logger.Errorf("Failed to update balance in Account by id for organizationId %s and ledgerId %s in grpc, Error: %s", getacc.OrganizationId, getacc.LedgerId, err.Error()) +// +// return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) +// } +// +// uuids = append(uuids, uuid.MustParse(getacc.Id)) +// } +// +// organizationID := update.GetOrganizationId() +// ledgerID := update.GetLedgerId() +// +// acc, err := ap.Command.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) +// if err != nil { +// mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) +// +// logger.Errorf("Failed to retrieve Accounts by ids for organizationId %s and ledgerId %s in grpc, Error: %s", organizationID, ledgerID, err.Error()) +// +// return nil, pkg.ValidateBusinessError(constant.ErrNoAccountsFound, reflect.TypeOf(mmodel.Account{}).Name()) +// } +// +// for _, ac := range acc { +// accounts = append(accounts, ac.ToProto()) +// } +// +// response := account.AccountsResponse{ +// Accounts: accounts, +// } +// +// return &response, nil +//} + // UpdateAccounts is a method that update Account balances by a given ids. func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) { logger := pkg.NewLoggerFromContext(ctx) @@ -128,71 +205,31 @@ func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.Acco ctx, span := tracer.Start(ctx, "handler.UpdateAccounts") defer span.End() - accounts := make([]*account.Account, 0) - uuids := make([]uuid.UUID, 0) - - for _, getacc := range update.GetAccounts() { - if pkg.IsNilOrEmpty(&getacc.Id) { - mopentelemetry.HandleSpanError(&span, "Failed to update Accounts because id is empty", nil) - - logger.Errorf("Failed to update Accounts because id is empty") - - return nil, pkg.ValidateBusinessError(constant.ErrNoAccountIDsProvided, reflect.TypeOf(mmodel.Account{}).Name()) - } - - balance := mmodel.Balance{ - Available: &getacc.Balance.Available, - OnHold: &getacc.Balance.OnHold, - Scale: &getacc.Balance.Scale, - } - - organizationUUID, err := uuid.Parse(getacc.GetOrganizationId()) - if err != nil { - return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationUUID) - } - - ledgerUUID, err := uuid.Parse(getacc.GetLedgerId()) - if err != nil { - return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) - } - - accountUUID, err := uuid.Parse(getacc.GetId()) - if err != nil { - return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), accountUUID) - } - - _, err = ap.Command.UpdateAccountByID(ctx, organizationUUID, ledgerUUID, accountUUID, &balance) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err) - - logger.Errorf("Failed to update balance in Account by id for organizationId %s and ledgerId %s in grpc, Error: %s", getacc.OrganizationId, getacc.LedgerId, err.Error()) - - return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) - } - - uuids = append(uuids, uuid.MustParse(getacc.Id)) + aliases := make([]string, 0) + for _, getAcc := range update.GetAccounts() { + uuids = append(uuids, uuid.MustParse(getAcc.Id)) + aliases = append(aliases, getAcc.Alias) } - organizationID := update.GetOrganizationId() - ledgerID := update.GetLedgerId() - - acc, err := ap.Command.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) + organizationID, err := uuid.Parse(update.OrganizationId) if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) - - logger.Errorf("Failed to retrieve Accounts by ids for organizationId %s and ledgerId %s in grpc, Error: %s", organizationID, ledgerID, err.Error()) - - return nil, pkg.ValidateBusinessError(constant.ErrNoAccountsFound, reflect.TypeOf(mmodel.Account{}).Name()) + return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationID) } - for _, ac := range acc { - accounts = append(accounts, ac.ToProto()) + ledgerID, err := uuid.Parse(update.LedgerId) + if err != nil { + return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerID) } - response := account.AccountsResponse{ - Accounts: accounts, + err = ap.Command.UpdateAccounts(ctx, organizationID, ledgerID, update.GetAccounts()) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err) + + logger.Errorf("Failed to update balance in Account by id for organizationId %v and ledgerId %v in grpc, Error: %v", organizationID, ledgerID, err.Error()) + + return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) } - return &response, nil + return nil, nil } diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 75b18679..284dfe4e 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -942,24 +942,23 @@ func (r *AccountPostgreSQLRepository) UpdateAccountByID(ctx context.Context, org return record.ToEntity(), nil } +// UpdateAccounts an update all Accounts entity by ID only into Postgresql. func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error { tracer := pkg.NewTracerFromContext(ctx) - ctx, span0 := tracer.Start(ctx, "postgres.update_accounts.get_db") - defer span0.End() + + ctx, span := tracer.Start(ctx, "postgres.update_accounts") + defer span.End() db, err := r.connection.GetDB() if err != nil { - mopentelemetry.HandleSpanError(&span0, "Failed to get database connection", err) + mopentelemetry.HandleSpanError(&span, "Failed to get database connection", err) return err } - ctx, span1 := tracer.Start(ctx, "postgres.update_accounts.begin") - defer span1.End() - tx, err := db.Begin() if err != nil { - mopentelemetry.HandleSpanError(&span1, "Failed to init transaction", err) + mopentelemetry.HandleSpanError(&span, "Failed to init transaction", err) return err } @@ -988,16 +987,16 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi _, err := tx.ExecContext(ctx, query, args...) if err != nil { - mopentelemetry.HandleSpanError(&span1, "Failed to update account", err) + mopentelemetry.HandleSpanError(&span, "Failed to update account", err) return err } } - if err := tx.Commit().Error; err != nil { + if err := tx.Commit(); err != nil { err := pkg.ValidateBusinessError(constant.ErrEntityNotFound, reflect.TypeOf(mmodel.Account{}).Name()) - mopentelemetry.HandleSpanError(&span1, "Failed to commit accounts", err) + mopentelemetry.HandleSpanError(&span, "Failed to commit accounts", err) return err } diff --git a/components/ledger/internal/services/command/update-account-id.go b/components/ledger/internal/services/command/update-account-id.go index 29650a9c..3f5f0b84 100644 --- a/components/ledger/internal/services/command/update-account-id.go +++ b/components/ledger/internal/services/command/update-account-id.go @@ -25,11 +25,11 @@ func (uc *UseCase) UpdateAccountByID(ctx context.Context, organizationID, ledger logger.Infof("Trying to update account by id: %v", id) - account := &mmodel.Account{ + acc := &mmodel.Account{ Balance: *balance, } - accountUpdated, err := uc.AccountRepo.UpdateAccountByID(ctx, organizationID, ledgerID, id, account) + accountUpdated, err := uc.AccountRepo.UpdateAccountByID(ctx, organizationID, ledgerID, id, acc) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to update account on repo by id", err) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 8f1d7f09..53b9f4c7 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -418,7 +418,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L _, spanRaceCondition := tracer.Start(ctx, "handler.create_transaction.race_condition") - handler.Command.AllKeysUnlocked(ctx, organizationID, ledgerID, validate.Aliases) + handler.Command.AllKeysUnlocked(ctx, organizationID, ledgerID, validate.Aliases, hash) spanRaceCondition.End() @@ -505,13 +505,11 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition") - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases) + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) spanReleaseLock.End() ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status") - - //TODO: use event driven and broken and parts _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.APPROVED) if err != nil { mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err) @@ -525,7 +523,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L ctxGetTransaction, spanGetTransaction := tracer.Start(ctx, "handler.create_transaction.get_transaction") - //TODO: use event driven and broken and parts tran, err = handler.Query.GetTransactionByID(ctxGetTransaction, organizationID, ledgerID, tran.IDtoUUID()) if err != nil { mopentelemetry.HandleSpanError(&spanGetTransaction, "Failed to retrieve transaction", err) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index e324d608..7b989449 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -2,16 +2,18 @@ package command import ( "context" + "errors" "github.com/LerianStudio/midaz/pkg" "github.com/LerianStudio/midaz/pkg/mopentelemetry" "github.com/google/uuid" + "github.com/redis/go-redis/v9" "sync" "time" ) const TimeSetLock = 1 -func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string) { +func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -27,14 +29,14 @@ func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID logger.Infof("Account try to lock on redis: %v", internalKey) wg.Add(1) - go uc.checkAndReleaseLock(ctx, &wg, internalKey, resultChan) + go uc.checkAndReleaseLock(ctx, &wg, internalKey, hash, resultChan) } wg.Wait() close(resultChan) } -func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, internalKey string, resultChan chan bool) { +func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, internalKey, hash string, resultChan chan bool) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -44,7 +46,7 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, defer wg.Done() for { - success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, "processing account...", TimeSetLock) + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, TimeSetLock) if err != nil { resultChan <- false return @@ -57,11 +59,11 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, return } - time.Sleep(250 * time.Millisecond) + time.Sleep(200 * time.Millisecond) } } -func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string) { +func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -73,11 +75,14 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui logger.Infof("Account releasing lock on redis: %v", internalKey) - err := uc.RedisRepo.Del(ctx, internalKey) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) + val, err := uc.RedisRepo.Get(ctx, key) + if !errors.Is(err, redis.Nil) && err != nil && val == hash { + err = uc.RedisRepo.Del(ctx, internalKey) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) - logger.Errorf("Failed to release Accounts lock: %v", err) + logger.Errorf("Failed to release Accounts lock: %v", err) + } } } } diff --git a/components/transaction/internal/services/command/update-transaction.go b/components/transaction/internal/services/command/update-transaction.go index dcca08b7..349bccd5 100644 --- a/components/transaction/internal/services/command/update-transaction.go +++ b/components/transaction/internal/services/command/update-transaction.go @@ -80,7 +80,7 @@ func (uc *UseCase) UpdateTransactionStatus(ctx context.Context, organizationID, Status: status, } - transUpdated, err := uc.TransactionRepo.Update(ctx, organizationID, ledgerID, transactionID, trans) + _, err := uc.TransactionRepo.Update(ctx, organizationID, ledgerID, transactionID, trans) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to update status transaction on repo by id", err) @@ -93,5 +93,5 @@ func (uc *UseCase) UpdateTransactionStatus(ctx context.Context, organizationID, return nil, err } - return transUpdated, nil + return nil, nil } From beb49216d8e7c7ddcc76a841c9c454304abd0e62 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Thu, 9 Jan 2025 17:51:24 +0100 Subject: [PATCH 08/34] feat: update time lock :sparkles: --- .../internal/services/command/create-lock-race-condition.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 7b989449..9642aee1 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -11,7 +11,7 @@ import ( "time" ) -const TimeSetLock = 1 +const TimeSetLock = 3 func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) { logger := pkg.NewLoggerFromContext(context.Background()) From 97448dc69d6bcfafe66bbd94d81cff8b4733da3e Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 12:00:09 +0100 Subject: [PATCH 09/34] feature: new updates to avoid race condition --- .../internal/adapters/http/in/transaction.go | 188 +++++++----------- .../command/create-lock-race-condition.go | 46 ++++- .../command/update-accounts-ledger-grpc.go | 65 ++++++ .../query/get-accounts-ledger-grpc.go | 58 ++++++ 4 files changed, 235 insertions(+), 122 deletions(-) create mode 100644 components/transaction/internal/services/command/update-accounts-ledger-grpc.go create mode 100644 components/transaction/internal/services/query/get-accounts-ledger-grpc.go diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 53b9f4c7..7febcff4 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -466,6 +466,39 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanCreateTransaction.End() + ctxProcessAccounts, spanUpdateAccounts := tracer.Start(ctx, "handler.create_transaction.update_accounts") + + err = mopentelemetry.SetSpanAttributesFromStruct(&spanUpdateAccounts, "payload_handler_update_accounts", accounts) + if err != nil { + mopentelemetry.HandleSpanError(&spanUpdateAccounts, "Failed to convert accounts from struct to JSON string", err) + } + + err = handler.Command.UpdateAccounts(ctxProcessAccounts, logger, *validate, token, organizationID, ledgerID, accounts) + if err != nil { + mopentelemetry.HandleSpanError(&spanUpdateAccounts, "Failed to update accounts", err) + + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_race_condition") + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + + ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.update_accounts.update_transaction_status") + _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.DECLINED) + if err != nil { + mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err) + + logger.Errorf("Failed to update Transaction with ID: %s, Error: %s", tran.ID, err.Error()) + + return http.WithError(c, err) + } + + spanUpdateTransactionStatus.End() + + return http.WithError(c, err) + } + + spanUpdateAccounts.End() + e := make(chan error) result := make(chan []*operation.Operation) @@ -487,28 +520,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanCreateOperation.End() - ctxProcessAccounts, spanProcessAccounts := tracer.Start(ctx, "handler.create_transaction.process_accounts") - - err = mopentelemetry.SetSpanAttributesFromStruct(&spanProcessAccounts, "payload_handler_process_accounts", accounts) - if err != nil { - mopentelemetry.HandleSpanError(&spanProcessAccounts, "Failed to convert accounts from struct to JSON string", err) - } - - err = handler.processAccounts(ctxProcessAccounts, logger, *validate, token, organizationID, ledgerID, accounts) - if err != nil { - mopentelemetry.HandleSpanError(&spanProcessAccounts, "Failed to process accounts", err) - - return http.WithError(c, err) - } - - spanProcessAccounts.End() - - _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition") - - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) - - spanReleaseLock.End() - ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status") _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.APPROVED) if err != nil { @@ -540,11 +551,45 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L logger.Infof("Successfully updated Transaction with Organization ID: %s, Ledger ID: %s and ID: %s", organizationID.String(), ledgerID.String(), tran.ID) + _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition") + + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + go handler.logTransaction(ctx, operations, organizationID, ledgerID, tran.IDtoUUID()) return http.Created(c, tran) } +// getAccounts is a function that split aliases and ids, call the properly function and return Accounts +func (handler *TransactionHandler) getAccounts(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) { + span := trace.SpanFromContext(ctx) + defer span.End() + + accounts, err := handler.Query.GetAccountsLedger(ctx, logger, token, organizationID, ledgerID, input) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get accounts", err) + + return nil, err + } + + searchAgain, err := handler.Command.LockBalanceVersion(ctx, organizationID, ledgerID, input, accounts) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err) + + logger.Error("Failed to get account by alias gRPC on Ledger", err.Error()) + + return nil, err + } + + if searchAgain { + return handler.getAccounts(ctx, logger, token, organizationID, ledgerID, input) + } + + return accounts, nil +} + // logTransaction creates a message representing a transaction log and sends to auditing exchange func (handler *TransactionHandler) logTransaction(ctx context.Context, operations []*operation.Operation, organizationID uuid.UUID, ledgerID uuid.UUID, transactionID uuid.UUID) { logger := pkg.NewLoggerFromContext(ctx) @@ -591,105 +636,6 @@ func (handler *TransactionHandler) logTransaction(ctx context.Context, operation } } -// getAccounts is a function that split aliases and ids, call the properly function and return Accounts -func (handler *TransactionHandler) getAccounts(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) { - span := trace.SpanFromContext(ctx) - - var ids []string - - var aliases []string - - for _, item := range input { - if pkg.IsUUID(item) { - ids = append(ids, item) - } else { - aliases = append(aliases, item) - } - } - - var accounts []*account.Account - - if len(ids) > 0 { - gRPCAccounts, err := handler.Query.AccountGRPCRepo.GetAccountsByIds(ctx, token, organizationID, ledgerID, ids) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to get account by ids gRPC on Ledger", err) - - logger.Error("Failed to get account gRPC by ids on Ledger", err.Error()) - - return nil, err - } - - accounts = append(accounts, gRPCAccounts.GetAccounts()...) - } - - if len(aliases) > 0 { - gRPCAccounts, err := handler.Query.AccountGRPCRepo.GetAccountsByAlias(ctx, token, organizationID, ledgerID, aliases) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err) - - logger.Error("Failed to get account by alias gRPC on Ledger", err.Error()) - - return nil, err - } - - accounts = append(accounts, gRPCAccounts.GetAccounts()...) - } - - return accounts, nil -} - -// processAccounts is a function that adjust balance on Accounts -func (handler *TransactionHandler) processAccounts(ctx context.Context, logger mlog.Logger, validate goldModel.Responses, token string, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error { - span := trace.SpanFromContext(ctx) - - e := make(chan error) - result := make(chan []*account.Account) - - var accountsToUpdate []*account.Account - - go goldModel.UpdateAccounts(constant.DEBIT, validate.From, accounts, result, e) - select { - case r := <-result: - accountsToUpdate = append(accountsToUpdate, r...) - case err := <-e: - mopentelemetry.HandleSpanError(&span, "Failed to update debit accounts", err) - - return err - } - - go goldModel.UpdateAccounts(constant.CREDIT, validate.To, accounts, result, e) - select { - case r := <-result: - accountsToUpdate = append(accountsToUpdate, r...) - case err := <-e: - mopentelemetry.HandleSpanError(&span, "Failed to update credit accounts", err) - - return err - } - - err := mopentelemetry.SetSpanAttributesFromStruct(&span, "payload_grpc_update_accounts", accountsToUpdate) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to convert accountsToUpdate from struct to JSON string", err) - - return err - } - - acc, err := handler.Command.AccountGRPCRepo.UpdateAccounts(ctx, token, organizationID, ledgerID, accountsToUpdate) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to update accounts gRPC on Ledger", err) - - logger.Error("Failed to update accounts gRPC on Ledger", err.Error()) - - return err - } - - for _, a := range acc.Accounts { - logger.Infof(a.UpdatedAt) - } - - return nil -} - func isAuditLogEnabled() bool { envValue := strings.ToLower(strings.TrimSpace(os.Getenv("AUDIT_LOG_ENABLED"))) return envValue != "false" diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 9642aee1..0d367aea 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -4,14 +4,17 @@ import ( "context" "errors" "github.com/LerianStudio/midaz/pkg" + "github.com/LerianStudio/midaz/pkg/mgrpc/account" "github.com/LerianStudio/midaz/pkg/mopentelemetry" "github.com/google/uuid" "github.com/redis/go-redis/v9" + "strconv" "sync" "time" ) -const TimeSetLock = 3 +const TimeSetLock = 1 +const TimeSetLockBalance = 60 func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) { logger := pkg.NewLoggerFromContext(context.Background()) @@ -86,3 +89,44 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui } } } + +func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) { + logger := pkg.NewLoggerFromContext(context.Background()) + tracer := pkg.NewTracerFromContext(context.Background()) + + ctx, span := tracer.Start(ctx, "redis.lock_balance_version") + defer span.End() + + accountsMap := make(map[string]*account.Account) + for _, acc := range accounts { + accountsMap[acc.Id] = acc + accountsMap[acc.Alias] = acc + } + + for _, key := range keys { + if acc, exists := accountsMap[key]; exists { + balanceAvailable := strconv.FormatFloat(acc.Balance.Available, 'f', -1, 64) + + internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + ":" + balanceAvailable + + logger.Infof("Account balance version releasing lock on redis: %v", internalKey) + + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, "lock balance version...", TimeSetLockBalance) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to lock Account balance version: ", err) + + logger.Errorf("Failed to lock Account balance version: %v", err) + + return false, err + } + + if !success { + logger.Infof("Lock already exists for key, get Accounts again: %v", internalKey) + + return true, nil + } + } + } + + return false, nil +} diff --git a/components/transaction/internal/services/command/update-accounts-ledger-grpc.go b/components/transaction/internal/services/command/update-accounts-ledger-grpc.go new file mode 100644 index 00000000..e9fae267 --- /dev/null +++ b/components/transaction/internal/services/command/update-accounts-ledger-grpc.go @@ -0,0 +1,65 @@ +package command + +import ( + "context" + + "github.com/LerianStudio/midaz/pkg/constant" + goldModel "github.com/LerianStudio/midaz/pkg/gold/transaction/model" + "github.com/LerianStudio/midaz/pkg/mgrpc/account" + "github.com/LerianStudio/midaz/pkg/mlog" + "github.com/LerianStudio/midaz/pkg/mopentelemetry" + "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" +) + +// UpdateAccounts methods that is responsible to update accounts on ledger by gRpc. +func (uc *UseCase) UpdateAccounts(ctx context.Context, logger mlog.Logger, validate goldModel.Responses, token string, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error { + span := trace.SpanFromContext(ctx) + + e := make(chan error) + result := make(chan []*account.Account) + + var accountsToUpdate []*account.Account + + go goldModel.UpdateAccounts(constant.DEBIT, validate.From, accounts, result, e) + select { + case r := <-result: + accountsToUpdate = append(accountsToUpdate, r...) + case err := <-e: + mopentelemetry.HandleSpanError(&span, "Failed to update debit accounts", err) + + return err + } + + go goldModel.UpdateAccounts(constant.CREDIT, validate.To, accounts, result, e) + select { + case r := <-result: + accountsToUpdate = append(accountsToUpdate, r...) + case err := <-e: + mopentelemetry.HandleSpanError(&span, "Failed to update credit accounts", err) + + return err + } + + err := mopentelemetry.SetSpanAttributesFromStruct(&span, "payload_grpc_update_accounts", accountsToUpdate) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to convert accountsToUpdate from struct to JSON string", err) + + return err + } + + acc, err := uc.AccountGRPCRepo.UpdateAccounts(ctx, token, organizationID, ledgerID, accountsToUpdate) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to update accounts gRPC on Ledger", err) + + logger.Error("Failed to update accounts gRPC on Ledger", err.Error()) + + return err + } + + for _, a := range acc.Accounts { + logger.Infof(a.UpdatedAt) + } + + return nil +} diff --git a/components/transaction/internal/services/query/get-accounts-ledger-grpc.go b/components/transaction/internal/services/query/get-accounts-ledger-grpc.go new file mode 100644 index 00000000..766de9f9 --- /dev/null +++ b/components/transaction/internal/services/query/get-accounts-ledger-grpc.go @@ -0,0 +1,58 @@ +package query + +import ( + "context" + "github.com/LerianStudio/midaz/pkg" + "github.com/LerianStudio/midaz/pkg/mgrpc/account" + "github.com/LerianStudio/midaz/pkg/mlog" + "github.com/LerianStudio/midaz/pkg/mopentelemetry" + "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" +) + +// GetAccountsLedger methods responsible to get accounts on ledger by gRpc. +func (uc *UseCase) GetAccountsLedger(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) { + span := trace.SpanFromContext(ctx) + + var ids []string + + var aliases []string + + for _, item := range input { + if pkg.IsUUID(item) { + ids = append(ids, item) + } else { + aliases = append(aliases, item) + } + } + + var accounts []*account.Account + + if len(ids) > 0 { + gRPCAccounts, err := uc.AccountGRPCRepo.GetAccountsByIds(ctx, token, organizationID, ledgerID, ids) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get account by ids gRPC on Ledger", err) + + logger.Error("Failed to get account gRPC by ids on Ledger", err.Error()) + + return nil, err + } + + accounts = append(accounts, gRPCAccounts.GetAccounts()...) + } + + if len(aliases) > 0 { + gRPCAccounts, err := uc.AccountGRPCRepo.GetAccountsByAlias(ctx, token, organizationID, ledgerID, aliases) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err) + + logger.Error("Failed to get account by alias gRPC on Ledger", err.Error()) + + return nil, err + } + + accounts = append(accounts, gRPCAccounts.GetAccounts()...) + } + + return accounts, nil +} From 1ddf09f939da41d4ebabfd339b00d7caf9dc29f6 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 13:12:58 +0100 Subject: [PATCH 10/34] fix: adjust to remove lock of get accounts :bug: --- .../transaction/internal/adapters/http/in/transaction.go | 3 +++ .../services/command/create-lock-race-condition.go | 8 +++----- pkg/constant/account.go | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 7febcff4..59c8361f 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -439,6 +439,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L err = goldModel.ValidateAccounts(*validate, accounts) if err != nil { + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) mopentelemetry.HandleSpanError(&spanValidateAccounts, "Failed to validate accounts", err) return http.WithError(c, err) @@ -513,6 +514,8 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L case err := <-e: mopentelemetry.HandleSpanError(&spanCreateOperation, "Failed to create operations", err) + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) + logger.Error("Failed to create operations: ", err.Error()) return http.WithError(c, err) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 0d367aea..017d210d 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -4,6 +4,7 @@ import ( "context" "errors" "github.com/LerianStudio/midaz/pkg" + "github.com/LerianStudio/midaz/pkg/constant" "github.com/LerianStudio/midaz/pkg/mgrpc/account" "github.com/LerianStudio/midaz/pkg/mopentelemetry" "github.com/google/uuid" @@ -13,9 +14,6 @@ import ( "time" ) -const TimeSetLock = 1 -const TimeSetLockBalance = 60 - func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -49,7 +47,7 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, defer wg.Done() for { - success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, TimeSetLock) + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, constant.TimeSetLock) if err != nil { resultChan <- false return @@ -111,7 +109,7 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge logger.Infof("Account balance version releasing lock on redis: %v", internalKey) - success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, "lock balance version...", TimeSetLockBalance) + success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, constant.ValueBalanceLock, constant.TimeSetLockBalance) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to lock Account balance version: ", err) diff --git a/pkg/constant/account.go b/pkg/constant/account.go index 24585c4e..77d75aa5 100644 --- a/pkg/constant/account.go +++ b/pkg/constant/account.go @@ -3,4 +3,7 @@ package constant const ( DefaultExternalAccountAliasPrefix = "@external/" ExternalAccountType = "external" + TimeSetLock = 1 + TimeSetLockBalance = 60 + ValueBalanceLock = "lock balance version..." ) From 3970a04dd1ac5157081815726766387434ad0b66 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 13:13:30 +0100 Subject: [PATCH 11/34] fix: change place :bug: --- .../transaction/internal/adapters/http/in/transaction.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 59c8361f..60c73ef6 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -439,8 +439,9 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L err = goldModel.ValidateAccounts(*validate, accounts) if err != nil { - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) mopentelemetry.HandleSpanError(&spanValidateAccounts, "Failed to validate accounts", err) + + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) return http.WithError(c, err) } From 26af4697aff95095a98681af98a3c0658a60c75b Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 13:45:57 +0100 Subject: [PATCH 12/34] fix: unlock specify by get accounts :bug: --- .../internal/adapters/http/in/transaction.go | 6 +-- .../command/create-lock-race-condition.go | 38 ++++++++++++++++++- pkg/utils.go | 6 +++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 60c73ef6..92ee5022 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -440,8 +440,8 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L err = goldModel.ValidateAccounts(*validate, accounts) if err != nil { mopentelemetry.HandleSpanError(&spanValidateAccounts, "Failed to validate accounts", err) - - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) + + handler.Command.DeleteLocksBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts) return http.WithError(c, err) } @@ -515,7 +515,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L case err := <-e: mopentelemetry.HandleSpanError(&spanCreateOperation, "Failed to create operations", err) - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, constant.ValueBalanceLock) + handler.Command.DeleteLocksBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts) logger.Error("Failed to create operations: ", err.Error()) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 017d210d..15386085 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -88,6 +88,41 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui } } +func (uc *UseCase) DeleteLocksBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) { + logger := pkg.NewLoggerFromContext(context.Background()) + tracer := pkg.NewTracerFromContext(context.Background()) + + ctx, span := tracer.Start(ctx, "redis.delete_locks") + defer span.End() + + accountsMap := make(map[string]*account.Account) + for _, acc := range accounts { + accountsMap[acc.Id] = acc + accountsMap[acc.Alias] = acc + } + + for _, key := range keys { + if acc, exists := accountsMap[key]; exists { + balanceAvailable := strconv.FormatFloat(acc.Balance.Available, 'f', -1, 64) + balanceScale := strconv.FormatFloat(acc.Balance.Scale, 'f', -1, 64) + + internalKey := pkg.LockBalanceInternalKey(organizationID, ledgerID, key, balanceAvailable, balanceScale) + + logger.Infof("Account balance version releasing lock on redis: %v", internalKey) + + _, err := uc.RedisRepo.Get(ctx, key) + if !errors.Is(err, redis.Nil) && err != nil { + err = uc.RedisRepo.Del(ctx, internalKey) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) + + logger.Errorf("Failed to release Accounts lock: %v", err) + } + } + } + } +} + func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -104,8 +139,9 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge for _, key := range keys { if acc, exists := accountsMap[key]; exists { balanceAvailable := strconv.FormatFloat(acc.Balance.Available, 'f', -1, 64) + balanceScale := strconv.FormatFloat(acc.Balance.Scale, 'f', -1, 64) - internalKey := pkg.LockInternalKey(organizationID, ledgerID, key) + ":" + balanceAvailable + internalKey := pkg.LockBalanceInternalKey(organizationID, ledgerID, key, balanceAvailable, balanceScale) logger.Infof("Account balance version releasing lock on redis: %v", internalKey) diff --git a/pkg/utils.go b/pkg/utils.go index de15311b..84e1baf2 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -290,3 +290,9 @@ func LockInternalKey(organizationID, ledgerID uuid.UUID, key string) string { return lockInternalKey } + +func LockBalanceInternalKey(organizationID, ledgerID uuid.UUID, key, balanceAvailable, balanceScale string) string { + lockBalanceInternalKey := LockInternalKey(organizationID, ledgerID, key) + ":" + balanceAvailable + ":" + balanceScale + + return lockBalanceInternalKey +} From 3ff4f8df98aaceedb15cbfcacc5615ea1544012b Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 14:26:45 +0100 Subject: [PATCH 13/34] refactor: remove delete lock balance because is unnecessary; :hammer: --- .../internal/adapters/http/in/transaction.go | 32 +++++++---------- .../command/create-lock-race-condition.go | 35 ------------------- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 92ee5022..13fcaa12 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -426,7 +426,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L token := http.GetTokenHeader(c) - accounts, err := handler.getAccounts(ctxGetAccounts, logger, token, organizationID, ledgerID, validate.Aliases) + accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, organizationID, ledgerID, validate) if err != nil { mopentelemetry.HandleSpanError(&spanGetAccounts, "Failed to get accounts", err) @@ -435,19 +435,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanGetAccounts.End() - _, spanValidateAccounts := tracer.Start(ctx, "handler.create_transaction.validate_accounts") - - err = goldModel.ValidateAccounts(*validate, accounts) - if err != nil { - mopentelemetry.HandleSpanError(&spanValidateAccounts, "Failed to validate accounts", err) - - handler.Command.DeleteLocksBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts) - - return http.WithError(c, err) - } - - spanValidateAccounts.End() - ctxCreateTransaction, spanCreateTransaction := tracer.Start(ctx, "handler.create_transaction.create_transaction") err = mopentelemetry.SetSpanAttributesFromStruct(&spanCreateTransaction, "payload_command_create_transaction", parserDSL) @@ -515,8 +502,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L case err := <-e: mopentelemetry.HandleSpanError(&spanCreateOperation, "Failed to create operations", err) - handler.Command.DeleteLocksBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts) - logger.Error("Failed to create operations: ", err.Error()) return http.WithError(c, err) @@ -567,18 +552,25 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L } // getAccounts is a function that split aliases and ids, call the properly function and return Accounts -func (handler *TransactionHandler) getAccounts(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) { +func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses) ([]*account.Account, error) { span := trace.SpanFromContext(ctx) defer span.End() - accounts, err := handler.Query.GetAccountsLedger(ctx, logger, token, organizationID, ledgerID, input) + accounts, err := handler.Query.GetAccountsLedger(ctx, logger, token, organizationID, ledgerID, validate.Aliases) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to get accounts", err) return nil, err } - searchAgain, err := handler.Command.LockBalanceVersion(ctx, organizationID, ledgerID, input, accounts) + err = goldModel.ValidateAccounts(*validate, accounts) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to validate accounts", err) + + return nil, err + } + + searchAgain, err := handler.Command.LockBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err) @@ -588,7 +580,7 @@ func (handler *TransactionHandler) getAccounts(ctx context.Context, logger mlog. } if searchAgain { - return handler.getAccounts(ctx, logger, token, organizationID, ledgerID, input) + return handler.getAccountsAndValidate(ctx, logger, token, organizationID, ledgerID, validate) } return accounts, nil diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 15386085..3a570505 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -88,41 +88,6 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui } } -func (uc *UseCase) DeleteLocksBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) { - logger := pkg.NewLoggerFromContext(context.Background()) - tracer := pkg.NewTracerFromContext(context.Background()) - - ctx, span := tracer.Start(ctx, "redis.delete_locks") - defer span.End() - - accountsMap := make(map[string]*account.Account) - for _, acc := range accounts { - accountsMap[acc.Id] = acc - accountsMap[acc.Alias] = acc - } - - for _, key := range keys { - if acc, exists := accountsMap[key]; exists { - balanceAvailable := strconv.FormatFloat(acc.Balance.Available, 'f', -1, 64) - balanceScale := strconv.FormatFloat(acc.Balance.Scale, 'f', -1, 64) - - internalKey := pkg.LockBalanceInternalKey(organizationID, ledgerID, key, balanceAvailable, balanceScale) - - logger.Infof("Account balance version releasing lock on redis: %v", internalKey) - - _, err := uc.RedisRepo.Get(ctx, key) - if !errors.Is(err, redis.Nil) && err != nil { - err = uc.RedisRepo.Del(ctx, internalKey) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err) - - logger.Errorf("Failed to release Accounts lock: %v", err) - } - } - } - } -} - func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) From ac48054b9df26fb20af19b21da38c9d9da885de4 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 15:53:09 +0100 Subject: [PATCH 14/34] refactor: change string to constant :hammer: --- pkg/gold/transaction/model/validations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index ef118273..7a62be8d 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -78,7 +78,7 @@ func ValidateFromToOperation(ft FromTo, validate Responses, acc *a.Account) (Amo if ft.IsFrom { ba := OperateAmounts(validate.From[ft.Account], acc.Balance, constant.DEBIT) - if ba.Available < 0 && acc.Type != "external" { + if ba.Available < 0 && acc.Type != constant.ExternalAccountType { return amount, balanceAfter, pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateFromToOperation", acc.Alias) } From 3e4c7c0601a2c525a917dec8882b1e499ba6db35 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 19:09:28 +0100 Subject: [PATCH 15/34] refactor: to use count and not waiting ttl :hammer: --- .../internal/adapters/http/in/transaction.go | 71 ++++++++++++------- .../internal/adapters/redis/consumer.redis.go | 21 +++++- .../internal/adapters/redis/redis.mock.go | 14 ++++ .../command/create-lock-race-condition.go | 36 +++++++++- pkg/constant/account.go | 1 - pkg/constant/errors.go | 1 + pkg/errors.go | 6 ++ 7 files changed, 119 insertions(+), 31 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 13fcaa12..a165745a 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -426,10 +426,15 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L token := http.GetTokenHeader(c) - accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, organizationID, ledgerID, validate) + accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, organizationID, ledgerID, validate, parserDSL) if err != nil { mopentelemetry.HandleSpanError(&spanGetAccounts, "Failed to get accounts", err) + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition") + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + return http.WithError(c, err) } @@ -450,11 +455,42 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L logger.Error("Failed to create transaction", err.Error()) + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition") + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + return http.WithError(c, err) } spanCreateTransaction.End() + e := make(chan error) + result := make(chan []*operation.Operation) + + var operations []*operation.Operation + + ctxCreateOperation, spanCreateOperation := tracer.Start(ctx, "handler.create_transaction.create_operation") + + go handler.Command.CreateOperation(ctxCreateOperation, accounts, tran.ID, &parserDSL, *validate, result, e) + select { + case ops := <-result: + operations = append(operations, ops...) + case err := <-e: + mopentelemetry.HandleSpanError(&spanCreateOperation, "Failed to create operations", err) + + logger.Error("Failed to create operations: ", err.Error()) + + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition") + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + + return http.WithError(c, err) + } + + spanCreateOperation.End() + ctxProcessAccounts, spanUpdateAccounts := tracer.Start(ctx, "handler.create_transaction.update_accounts") err = mopentelemetry.SetSpanAttributesFromStruct(&spanUpdateAccounts, "payload_handler_update_accounts", accounts) @@ -466,7 +502,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L if err != nil { mopentelemetry.HandleSpanError(&spanUpdateAccounts, "Failed to update accounts", err) - _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_race_condition") + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition") handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) spanReleaseLock.End() @@ -488,27 +524,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanUpdateAccounts.End() - e := make(chan error) - result := make(chan []*operation.Operation) - - var operations []*operation.Operation - - ctxCreateOperation, spanCreateOperation := tracer.Start(ctx, "handler.create_transaction.create_operation") - - go handler.Command.CreateOperation(ctxCreateOperation, accounts, tran.ID, &parserDSL, *validate, result, e) - select { - case ops := <-result: - operations = append(operations, ops...) - case err := <-e: - mopentelemetry.HandleSpanError(&spanCreateOperation, "Failed to create operations", err) - - logger.Error("Failed to create operations: ", err.Error()) - - return http.WithError(c, err) - } - - spanCreateOperation.End() - ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status") _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.APPROVED) if err != nil { @@ -516,6 +531,11 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L logger.Errorf("Failed to update Transaction with ID: %s, Error: %s", tran.ID, err.Error()) + _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition") + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + + spanReleaseLock.End() + return http.WithError(c, err) } @@ -541,7 +561,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L logger.Infof("Successfully updated Transaction with Organization ID: %s, Ledger ID: %s and ID: %s", organizationID.String(), ledgerID.String(), tran.ID) _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition") - handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) spanReleaseLock.End() @@ -552,7 +571,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L } // getAccounts is a function that split aliases and ids, call the properly function and return Accounts -func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses) ([]*account.Account, error) { +func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, transaction goldModel.Transaction) ([]*account.Account, error) { span := trace.SpanFromContext(ctx) defer span.End() @@ -580,7 +599,7 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l } if searchAgain { - return handler.getAccountsAndValidate(ctx, logger, token, organizationID, ledgerID, validate) + return handler.getAccountsAndValidate(ctx, logger, token, organizationID, ledgerID, validate, transaction) } return accounts, nil diff --git a/components/transaction/internal/adapters/redis/consumer.redis.go b/components/transaction/internal/adapters/redis/consumer.redis.go index 29911af5..d6036951 100644 --- a/components/transaction/internal/adapters/redis/consumer.redis.go +++ b/components/transaction/internal/adapters/redis/consumer.redis.go @@ -17,6 +17,7 @@ type RedisRepository interface { SetNX(ctx context.Context, key, value string, ttl time.Duration) (bool, error) Get(ctx context.Context, key string) (string, error) Del(ctx context.Context, key string) error + Incr(ctx context.Context, key string) int64 } // RedisConsumerRepository is a Redis implementation of the Redis consumer. @@ -66,7 +67,7 @@ func (rr *RedisConsumerRepository) SetNX(ctx context.Context, key, value string, logger := pkg.NewLoggerFromContext(ctx) tracer := pkg.NewTracerFromContext(ctx) - ctx, span := tracer.Start(ctx, "redis.set") + ctx, span := tracer.Start(ctx, "redis.set_nx") defer span.End() rds, err := rr.conn.GetClient(ctx) @@ -80,7 +81,7 @@ func (rr *RedisConsumerRepository) SetNX(ctx context.Context, key, value string, isLocked, err := rds.SetNX(ctx, key, value, ttl*time.Second).Result() if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to set on redis", err) + mopentelemetry.HandleSpanError(&span, "Failed to set nx on redis", err) return false, err } @@ -139,3 +140,19 @@ func (rr *RedisConsumerRepository) Del(ctx context.Context, key string) error { return nil } + +func (rr *RedisConsumerRepository) Incr(ctx context.Context, key string) int64 { + tracer := pkg.NewTracerFromContext(ctx) + + ctx, span := tracer.Start(ctx, "redis.incr") + defer span.End() + + rds, err := rr.conn.GetClient(ctx) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get redis", err) + + return 0 + } + + return rds.Incr(ctx, key).Val() +} diff --git a/components/transaction/internal/adapters/redis/redis.mock.go b/components/transaction/internal/adapters/redis/redis.mock.go index 3e610dc1..b3ea9588 100644 --- a/components/transaction/internal/adapters/redis/redis.mock.go +++ b/components/transaction/internal/adapters/redis/redis.mock.go @@ -69,6 +69,20 @@ func (mr *MockRedisRepositoryMockRecorder) Get(arg0, arg1 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisRepository)(nil).Get), arg0, arg1) } +// Incr mocks base method. +func (m *MockRedisRepository) Incr(arg0 context.Context, arg1 string) int64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Incr", arg0, arg1) + ret0, _ := ret[0].(int64) + return ret0 +} + +// Incr indicates an expected call of Incr. +func (mr *MockRedisRepositoryMockRecorder) Incr(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisRepository)(nil).Incr), arg0, arg1) +} + // Set mocks base method. func (m *MockRedisRepository) Set(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) error { m.ctrl.T.Helper() diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 3a570505..2e27ec59 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -88,6 +88,22 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui } } +func (uc *UseCase) getCounter(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) (int, error) { + val, err := uc.RedisRepo.Get(ctx, key) + if err != nil { + if !errors.Is(err, redis.Nil) && err != nil { + return 1, nil + } + return 1, err + } + integer, err := strconv.Atoi(val) + if err != nil { + return 1, err + } + + return integer, nil +} + func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -110,7 +126,7 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge logger.Infof("Account balance version releasing lock on redis: %v", internalKey) - success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, constant.ValueBalanceLock, constant.TimeSetLockBalance) + isSuccess, err := uc.RedisRepo.SetNX(ctx, internalKey, "0", constant.TimeSetLockBalance) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to lock Account balance version: ", err) @@ -119,7 +135,23 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge return false, err } - if !success { + total := uc.RedisRepo.Incr(ctx, internalKey) + if total > 3 { + err = uc.RedisRepo.Del(ctx, internalKey) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to release Account balance version lock", err) + + logger.Errorf("Failed to release Account balance version lock: %v", err) + } + + logger.Infof("Account balance version releasing lock on redis: %v", internalKey) + + return false, pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, "LockBalanceVersion") + } + + if !isSuccess { + time.Sleep(200 * time.Millisecond) + logger.Infof("Lock already exists for key, get Accounts again: %v", internalKey) return true, nil diff --git a/pkg/constant/account.go b/pkg/constant/account.go index 77d75aa5..6de2c2cf 100644 --- a/pkg/constant/account.go +++ b/pkg/constant/account.go @@ -5,5 +5,4 @@ const ( ExternalAccountType = "external" TimeSetLock = 1 TimeSetLockBalance = 60 - ValueBalanceLock = "lock balance version..." ) diff --git a/pkg/constant/errors.go b/pkg/constant/errors.go index 8dc2d27a..cc1fc086 100644 --- a/pkg/constant/errors.go +++ b/pkg/constant/errors.go @@ -92,4 +92,5 @@ var ( ErrInvalidQueryParameter = errors.New("0082") ErrInvalidDateRange = errors.New("0083") ErrIdempotencyKey = errors.New("0084") + ErrLockVersionAccountBalance = errors.New("0085") ) diff --git a/pkg/errors.go b/pkg/errors.go index dc418467..f954865b 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -783,6 +783,12 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { Title: "Duplicate Idempotency Key", Message: fmt.Sprintf("The idempotency key %v is already in use. Please provide a unique key and try again.", args), }, + constant.ErrLockVersionAccountBalance: ValidationError{ + EntityType: entityType, + Code: constant.ErrLockVersionAccountBalance.Error(), + Title: "Race conditioning detected", + Message: "A race condition was detected while processing your request. Please try again", + }, } if mappedError, found := errorMap[err]; found { From 6ff156d268d5647f19c9bcae394a5e788fddac4b Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 19:59:44 +0100 Subject: [PATCH 16/34] feat: add logger :sparkles: --- .../internal/services/command/create-lock-race-condition.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 2e27ec59..fbed4640 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -136,6 +136,8 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge } total := uc.RedisRepo.Incr(ctx, internalKey) + logger.Infof("%v attempt(s) to get Account balance", internalKey) + if total > 3 { err = uc.RedisRepo.Del(ctx, internalKey) if err != nil { From 5a8259fc7116628fb00a232ef6e582cd5c4b3810 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 20:00:25 +0100 Subject: [PATCH 17/34] add: validation to balance before second lock --- .../internal/adapters/http/in/transaction.go | 2 +- pkg/gold/transaction/model/validations.go | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index a165745a..b2edc770 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -582,7 +582,7 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l return nil, err } - err = goldModel.ValidateAccounts(*validate, accounts) + err = goldModel.ValidateAccounts(transaction, *validate, accounts) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to validate accounts", err) diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index 7a62be8d..88600a6a 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -11,7 +11,7 @@ import ( ) // ValidateAccounts function with some validates in accounts and DSL operations -func ValidateAccounts(validate Responses, accounts []*a.Account) error { +func ValidateAccounts(transaction Transaction, validate Responses, accounts []*a.Account) error { if len(accounts) != (len(validate.From) + len(validate.To)) { return pkg.ValidateBusinessError(constant.ErrAccountIneligibility, "ValidateAccounts") } @@ -24,6 +24,27 @@ func ValidateAccounts(validate Responses, accounts []*a.Account) error { if err := validateToAccounts(acc, validate.To, validate.Asset); err != nil { return err } + + if err := validateBalance(transaction, validate.From, acc); err != nil { + return err + } + + } + + return nil +} + +func validateBalance(dsl Transaction, from map[string]Amount, acc *a.Account) error { + for key := range from { + for _, f := range dsl.Send.Source.From { + if acc.Id == key || acc.Alias == key { + ba := OperateAmounts(from[f.Account], acc.Balance, constant.DEBIT) + + if ba.Available < 0 && acc.Type != constant.ExternalAccountType { + return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateFromToOperation", acc.Alias) + } + } + } } return nil From 0c62a314e4ad86918b6955ba3792ce2017102c8e Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Fri, 10 Jan 2025 20:15:09 +0100 Subject: [PATCH 18/34] fix: add unlock :bug: --- .../transaction/internal/adapters/http/in/transaction.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index b2edc770..872f80c2 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -426,7 +426,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L token := http.GetTokenHeader(c) - accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, organizationID, ledgerID, validate, parserDSL) + accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, hash, organizationID, ledgerID, validate, parserDSL) if err != nil { mopentelemetry.HandleSpanError(&spanGetAccounts, "Failed to get accounts", err) @@ -571,7 +571,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L } // getAccounts is a function that split aliases and ids, call the properly function and return Accounts -func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, transaction goldModel.Transaction) ([]*account.Account, error) { +func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token, hash string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, transaction goldModel.Transaction) ([]*account.Account, error) { span := trace.SpanFromContext(ctx) defer span.End() @@ -586,6 +586,8 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to validate accounts", err) + handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash) + return nil, err } @@ -599,7 +601,7 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l } if searchAgain { - return handler.getAccountsAndValidate(ctx, logger, token, organizationID, ledgerID, validate, transaction) + return handler.getAccountsAndValidate(ctx, logger, token, hash, organizationID, ledgerID, validate, transaction) } return accounts, nil From 83f01302838cb2438dbd6d0a5316ffe27bd4d174 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 11:12:39 +0100 Subject: [PATCH 19/34] refactor: change some names to correct reference :hammer: --- pkg/gold/transaction/model/validations.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index 88600a6a..76d4e957 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -41,7 +41,7 @@ func validateBalance(dsl Transaction, from map[string]Amount, acc *a.Account) er ba := OperateAmounts(from[f.Account], acc.Balance, constant.DEBIT) if ba.Available < 0 && acc.Type != constant.ExternalAccountType { - return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateFromToOperation", acc.Alias) + return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateBalance", acc.Alias) } } } @@ -54,15 +54,15 @@ func validateFromAccounts(acc *a.Account, from map[string]Amount, asset string) for key := range from { if acc.Id == key || acc.Alias == key { if acc.AssetCode != asset { - return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "ValidateAccounts") + return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "validateFromAccounts") } if !acc.AllowSending { - return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "ValidateAccounts") + return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "validateFromAccounts") } if acc.Balance.Available <= 0 && acc.Type != constant.ExternalAccountType { - return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateAccounts", acc.Alias) + return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateFromAccounts", acc.Alias) } } } @@ -74,15 +74,15 @@ func validateToAccounts(acc *a.Account, to map[string]Amount, asset string) erro for key := range to { if acc.Id == key || acc.Alias == key { if acc.AssetCode != asset { - return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "ValidateAccounts") + return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "validateToAccounts") } if !acc.AllowReceiving { - return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "ValidateAccounts") + return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "validateToAccounts") } if acc.Balance.Available > 0 && acc.Type == constant.ExternalAccountType { - return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateAccounts", acc.Alias) + return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateToAccounts", acc.Alias) } } } From 429215c43932501ea5cd9962b8bd087f1d9b7b65 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 11:13:49 +0100 Subject: [PATCH 20/34] refactor: create constants; delete non used method; change magic numbers; :hammer: --- .../command/create-lock-race-condition.go | 20 ++----------------- pkg/constant/account.go | 4 +++- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index fbed4640..e9bcceb9 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -88,22 +88,6 @@ func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uui } } -func (uc *UseCase) getCounter(ctx context.Context, organizationID, ledgerID uuid.UUID, key string) (int, error) { - val, err := uc.RedisRepo.Get(ctx, key) - if err != nil { - if !errors.Is(err, redis.Nil) && err != nil { - return 1, nil - } - return 1, err - } - integer, err := strconv.Atoi(val) - if err != nil { - return 1, err - } - - return integer, nil -} - func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) { logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) @@ -138,7 +122,7 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge total := uc.RedisRepo.Incr(ctx, internalKey) logger.Infof("%v attempt(s) to get Account balance", internalKey) - if total > 3 { + if total > constant.RedisTimesRetry { err = uc.RedisRepo.Del(ctx, internalKey) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to release Account balance version lock", err) @@ -152,7 +136,7 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge } if !isSuccess { - time.Sleep(200 * time.Millisecond) + time.Sleep(constant.LockRetry * time.Millisecond) logger.Infof("Lock already exists for key, get Accounts again: %v", internalKey) diff --git a/pkg/constant/account.go b/pkg/constant/account.go index 6de2c2cf..413681d3 100644 --- a/pkg/constant/account.go +++ b/pkg/constant/account.go @@ -4,5 +4,7 @@ const ( DefaultExternalAccountAliasPrefix = "@external/" ExternalAccountType = "external" TimeSetLock = 1 - TimeSetLockBalance = 60 + TimeSetLockBalance = 10 + LockRetry = 200 + RedisTimesRetry = 3 ) From 3f37adeb3592531aa64612915066998defa00c06 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 13:00:36 +0100 Subject: [PATCH 21/34] feature: add optimistic lock on database using version to control race condition; --- components/ledger/Makefile | 2 +- .../adapters/postgres/account/account.go | 3 + .../postgres/account/account.postgresql.go | 36 ++++- .../000005_create_account_table.up.sql | 1 + .../command/create-lock-race-condition.go | 5 +- pkg/gold/transaction/model/validations.go | 1 + pkg/mgrpc/account/account.pb.go | 142 ++++++++++-------- pkg/mgrpc/account/account.proto | 9 +- pkg/mgrpc/account/account_grpc.pb.go | 48 ++++-- pkg/mmodel/account.go | 2 + pkg/utils.go | 6 +- 11 files changed, 159 insertions(+), 96 deletions(-) diff --git a/components/ledger/Makefile b/components/ledger/Makefile index 757e7169..faebdd87 100644 --- a/components/ledger/Makefile +++ b/components/ledger/Makefile @@ -95,7 +95,7 @@ ps: # App Commands .PHONY: grpc-ledger-gen grpc-ledger-gen: - @protoc --proto_path=../../common/mgrpc --go-grpc_out=../../common/mgrpc --go_out=../../common/mgrpc ../../common/mgrpc/account/account.proto + @protoc --proto_path=../../pkg/mgrpc --go-grpc_out=../../pkg/mgrpc --go_out=../../pkg/mgrpc ../../pkg/mgrpc/account/account.proto .PHONY: run run: diff --git a/components/ledger/internal/adapters/postgres/account/account.go b/components/ledger/internal/adapters/postgres/account/account.go index 0928104b..a23920a5 100644 --- a/components/ledger/internal/adapters/postgres/account/account.go +++ b/components/ledger/internal/adapters/postgres/account/account.go @@ -28,6 +28,7 @@ type AccountPostgreSQLModel struct { AllowReceiving bool Alias *string Type string + Version int64 CreatedAt time.Time UpdatedAt time.Time DeletedAt sql.NullTime @@ -63,6 +64,7 @@ func (t *AccountPostgreSQLModel) ToEntity() *mmodel.Account { AllowReceiving: &t.AllowReceiving, Alias: t.Alias, Type: t.Type, + Version: t.Version, CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, DeletedAt: nil, @@ -94,6 +96,7 @@ func (t *AccountPostgreSQLModel) FromEntity(account *mmodel.Account) { StatusDescription: account.Status.Description, Alias: account.Alias, Type: account.Type, + Version: account.Version, CreatedAt: account.CreatedAt, UpdatedAt: account.UpdatedAt, } diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 284dfe4e..e3dc9287 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -94,7 +94,7 @@ func (r *AccountPostgreSQLRepository) Create(ctx context.Context, acc *mmodel.Ac result, err := db.ExecContext(ctx, `INSERT INTO account VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22 ) RETURNING *`, record.ID, @@ -115,6 +115,7 @@ func (r *AccountPostgreSQLRepository) Create(ctx context.Context, acc *mmodel.Ac record.AllowReceiving, record.Alias, record.Type, + record.Version, record.CreatedAt, record.UpdatedAt, record.DeletedAt, @@ -767,6 +768,7 @@ func (r *AccountPostgreSQLRepository) ListAccountsByIDs(ctx context.Context, org &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -837,6 +839,7 @@ func (r *AccountPostgreSQLRepository) ListAccountsByAlias(ctx context.Context, o &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -976,21 +979,42 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi updates = append(updates, "balance_scale = $"+strconv.Itoa(len(args)+1)) args = append(args, acc.Balance.Scale) + updates = append(updates, "version = $"+strconv.Itoa(len(args)+1)) + version := acc.Version + 1 + args = append(args, version) + updates = append(updates, "updated_at = $"+strconv.Itoa(len(args)+1)) - args = append(args, time.Now(), organizationID, ledgerID, acc.Id) + args = append(args, time.Now(), organizationID, ledgerID, acc.Id, acc.Version) query := `UPDATE account SET ` + strings.Join(updates, ", ") + - ` WHERE organization_id = $` + strconv.Itoa(len(args)-2) + - ` AND ledger_id = $` + strconv.Itoa(len(args)-1) + - ` AND id = $` + strconv.Itoa(len(args)) + + ` WHERE organization_id = $` + strconv.Itoa(len(args)-3) + + ` AND ledger_id = $` + strconv.Itoa(len(args)-2) + + ` AND id = $` + strconv.Itoa(len(args)-1) + + ` AND version = $` + strconv.Itoa(len(args)) + ` AND deleted_at IS NULL` - _, err := tx.ExecContext(ctx, query, args...) + result, err := tx.ExecContext(ctx, query, args...) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to update account", err) return err } + + rowsAffected, err := result.RowsAffected() + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to get rows affected", err) + + return err + } + + if rowsAffected == 0 { + err = pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, reflect.TypeOf(mmodel.Account{}).Name()) + + mopentelemetry.HandleSpanError(&span, "Failed to update account", err) + + return err + } + } if err := tx.Commit(); err != nil { diff --git a/components/ledger/migrations/000005_create_account_table.up.sql b/components/ledger/migrations/000005_create_account_table.up.sql index 672c4eb1..4ab65f56 100644 --- a/components/ledger/migrations/000005_create_account_table.up.sql +++ b/components/ledger/migrations/000005_create_account_table.up.sql @@ -18,6 +18,7 @@ CREATE TABLE IF NOT EXISTS account allow_receiving BOOLEAN NOT NULL, alias TEXT NULL, type TEXT NOT NULL, + version NUMERIC DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE, deleted_at TIMESTAMP WITH TIME ZONE, diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index e9bcceb9..6a3c2a55 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -103,10 +103,7 @@ func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledge for _, key := range keys { if acc, exists := accountsMap[key]; exists { - balanceAvailable := strconv.FormatFloat(acc.Balance.Available, 'f', -1, 64) - balanceScale := strconv.FormatFloat(acc.Balance.Scale, 'f', -1, 64) - - internalKey := pkg.LockBalanceInternalKey(organizationID, ledgerID, key, balanceAvailable, balanceScale) + internalKey := pkg.LockVersionInternalKey(organizationID, ledgerID, key, strconv.FormatInt(acc.Version, 10)) logger.Infof("Account balance version releasing lock on redis: %v", internalKey) diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index 76d4e957..85b14c3f 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -158,6 +158,7 @@ func UpdateAccounts(operation string, fromTo map[string]Amount, accounts []*a.Ac AllowSending: acc.AllowSending, AllowReceiving: acc.AllowReceiving, Type: acc.Type, + Version: acc.Version, CreatedAt: acc.CreatedAt, UpdatedAt: acc.UpdatedAt, } diff --git a/pkg/mgrpc/account/account.pb.go b/pkg/mgrpc/account/account.pb.go index 70a29d1e..6d1f5000 100644 --- a/pkg/mgrpc/account/account.pb.go +++ b/pkg/mgrpc/account/account.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0-devel -// protoc v3.14.0 +// protoc-gen-go v1.34.2 +// protoc v5.27.0 // source: account/account.proto package account @@ -205,10 +205,11 @@ type Account struct { AllowReceiving bool `protobuf:"varint,13,opt,name=allow_receiving,json=allowReceiving,proto3" json:"allow_receiving,omitempty"` Alias string `protobuf:"bytes,14,opt,name=alias,proto3" json:"alias,omitempty"` Type string `protobuf:"bytes,15,opt,name=type,proto3" json:"type,omitempty"` - CreatedAt string `protobuf:"bytes,16,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt string `protobuf:"bytes,17,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - DeletedAt string `protobuf:"bytes,18,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` - Metadata *Metadata `protobuf:"bytes,19,opt,name=metadata,proto3" json:"metadata,omitempty"` + Version int64 `protobuf:"varint,16,opt,name=version,proto3" json:"version,omitempty"` + CreatedAt string `protobuf:"bytes,17,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,18,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + DeletedAt string `protobuf:"bytes,19,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` + Metadata *Metadata `protobuf:"bytes,20,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Account) Reset() { @@ -348,6 +349,13 @@ func (x *Account) GetType() string { return "" } +func (x *Account) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + func (x *Account) GetCreatedAt() string { if x != nil { return x.CreatedAt @@ -634,7 +642,7 @@ var file_account_account_proto_rawDesc = []byte{ 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0xf6, 0x04, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, + 0x6f, 0x6e, 0x22, 0x90, 0x05, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63, @@ -665,57 +673,59 @@ var file_account_account_proto_rawDesc = []byte{ 0x69, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, - 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, - 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x11, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x40, 0x0a, 0x10, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x01, - 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, - 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, - 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x64, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, - 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x6f, 0x0a, 0x0d, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x27, 0x0a, 0x0f, - 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x32, 0xea, 0x01, 0x0a, - 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x44, 0x0a, - 0x10, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x49, 0x64, - 0x73, 0x12, 0x13, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x42, 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c, - 0x69, 0x61, 0x73, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x47, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x40, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, + 0x64, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44, 0x12, 0x27, 0x0a, + 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, + 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x6f, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x32, 0xea, 0x01, 0x0a, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x44, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x49, 0x64, 0x73, 0x12, 0x13, 0x2e, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44, + 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, + 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x1a, 0x19, 0x2e, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x61, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -731,7 +741,7 @@ func file_account_account_proto_rawDescGZIP() []byte { } var file_account_account_proto_msgTypes = make([]protoimpl.MessageInfo, 9) -var file_account_account_proto_goTypes = []interface{}{ +var file_account_account_proto_goTypes = []any{ (*Balance)(nil), // 0: account.Balance (*Metadata)(nil), // 1: account.Metadata (*Status)(nil), // 2: account.Status @@ -768,7 +778,7 @@ func file_account_account_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_account_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Balance); i { case 0: return &v.state @@ -780,7 +790,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Metadata); i { case 0: return &v.state @@ -792,7 +802,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*Status); i { case 0: return &v.state @@ -804,7 +814,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[3].Exporter = func(v any, i int) any { switch v := v.(*Account); i { case 0: return &v.state @@ -816,7 +826,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[4].Exporter = func(v any, i int) any { switch v := v.(*AccountsResponse); i { case 0: return &v.state @@ -828,7 +838,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[5].Exporter = func(v any, i int) any { switch v := v.(*AccountsRequest); i { case 0: return &v.state @@ -840,7 +850,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[6].Exporter = func(v any, i int) any { switch v := v.(*AccountsID); i { case 0: return &v.state @@ -852,7 +862,7 @@ func file_account_account_proto_init() { return nil } } - file_account_account_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + file_account_account_proto_msgTypes[7].Exporter = func(v any, i int) any { switch v := v.(*AccountsAlias); i { case 0: return &v.state diff --git a/pkg/mgrpc/account/account.proto b/pkg/mgrpc/account/account.proto index 47ca487b..f3a3cca5 100644 --- a/pkg/mgrpc/account/account.proto +++ b/pkg/mgrpc/account/account.proto @@ -34,10 +34,11 @@ message Account { bool allow_receiving = 13; string alias = 14; string type = 15; - string created_at = 16; - string updated_at = 17; - string deleted_at = 18; - Metadata metadata = 19; + int64 version = 16; + string created_at = 17; + string updated_at = 18; + string deleted_at = 19; + Metadata metadata = 20; } message AccountsResponse { diff --git a/pkg/mgrpc/account/account_grpc.pb.go b/pkg/mgrpc/account/account_grpc.pb.go index bbee49d0..7d24d0a2 100644 --- a/pkg/mgrpc/account/account_grpc.pb.go +++ b/pkg/mgrpc/account/account_grpc.pb.go @@ -1,4 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.27.0 +// source: account/account.proto package account @@ -11,8 +15,14 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AccountProto_GetAccountsByIds_FullMethodName = "/account.AccountProto/GetAccountsByIds" + AccountProto_GetAccountsByAliases_FullMethodName = "/account.AccountProto/GetAccountsByAliases" + AccountProto_UpdateAccounts_FullMethodName = "/account.AccountProto/UpdateAccounts" +) // AccountProtoClient is the client API for AccountProto service. // @@ -32,8 +42,9 @@ func NewAccountProtoClient(cc grpc.ClientConnInterface) AccountProtoClient { } func (c *accountProtoClient) GetAccountsByIds(ctx context.Context, in *AccountsID, opts ...grpc.CallOption) (*AccountsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AccountsResponse) - err := c.cc.Invoke(ctx, "/account.AccountProto/GetAccountsByIds", in, out, opts...) + err := c.cc.Invoke(ctx, AccountProto_GetAccountsByIds_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -41,8 +52,9 @@ func (c *accountProtoClient) GetAccountsByIds(ctx context.Context, in *AccountsI } func (c *accountProtoClient) GetAccountsByAliases(ctx context.Context, in *AccountsAlias, opts ...grpc.CallOption) (*AccountsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AccountsResponse) - err := c.cc.Invoke(ctx, "/account.AccountProto/GetAccountsByAliases", in, out, opts...) + err := c.cc.Invoke(ctx, AccountProto_GetAccountsByAliases_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -50,8 +62,9 @@ func (c *accountProtoClient) GetAccountsByAliases(ctx context.Context, in *Accou } func (c *accountProtoClient) UpdateAccounts(ctx context.Context, in *AccountsRequest, opts ...grpc.CallOption) (*AccountsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AccountsResponse) - err := c.cc.Invoke(ctx, "/account.AccountProto/UpdateAccounts", in, out, opts...) + err := c.cc.Invoke(ctx, AccountProto_UpdateAccounts_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -60,7 +73,7 @@ func (c *accountProtoClient) UpdateAccounts(ctx context.Context, in *AccountsReq // AccountProtoServer is the server API for AccountProto service. // All implementations must embed UnimplementedAccountProtoServer -// for forward compatibility +// for forward compatibility. type AccountProtoServer interface { GetAccountsByIds(context.Context, *AccountsID) (*AccountsResponse, error) GetAccountsByAliases(context.Context, *AccountsAlias) (*AccountsResponse, error) @@ -68,9 +81,12 @@ type AccountProtoServer interface { mustEmbedUnimplementedAccountProtoServer() } -// UnimplementedAccountProtoServer must be embedded to have forward compatible implementations. -type UnimplementedAccountProtoServer struct { -} +// UnimplementedAccountProtoServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAccountProtoServer struct{} func (UnimplementedAccountProtoServer) GetAccountsByIds(context.Context, *AccountsID) (*AccountsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAccountsByIds not implemented") @@ -82,6 +98,7 @@ func (UnimplementedAccountProtoServer) UpdateAccounts(context.Context, *Accounts return nil, status.Errorf(codes.Unimplemented, "method UpdateAccounts not implemented") } func (UnimplementedAccountProtoServer) mustEmbedUnimplementedAccountProtoServer() {} +func (UnimplementedAccountProtoServer) testEmbeddedByValue() {} // UnsafeAccountProtoServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to AccountProtoServer will @@ -91,6 +108,13 @@ type UnsafeAccountProtoServer interface { } func RegisterAccountProtoServer(s grpc.ServiceRegistrar, srv AccountProtoServer) { + // If the following call pancis, it indicates UnimplementedAccountProtoServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&AccountProto_ServiceDesc, srv) } @@ -104,7 +128,7 @@ func _AccountProto_GetAccountsByIds_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/account.AccountProto/GetAccountsByIds", + FullMethod: AccountProto_GetAccountsByIds_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AccountProtoServer).GetAccountsByIds(ctx, req.(*AccountsID)) @@ -122,7 +146,7 @@ func _AccountProto_GetAccountsByAliases_Handler(srv interface{}, ctx context.Con } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/account.AccountProto/GetAccountsByAliases", + FullMethod: AccountProto_GetAccountsByAliases_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AccountProtoServer).GetAccountsByAliases(ctx, req.(*AccountsAlias)) @@ -140,7 +164,7 @@ func _AccountProto_UpdateAccounts_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/account.AccountProto/UpdateAccounts", + FullMethod: AccountProto_UpdateAccounts_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(AccountProtoServer).UpdateAccounts(ctx, req.(*AccountsRequest)) diff --git a/pkg/mmodel/account.go b/pkg/mmodel/account.go index 127a42bd..d45214c4 100644 --- a/pkg/mmodel/account.go +++ b/pkg/mmodel/account.go @@ -59,6 +59,7 @@ type Account struct { AllowReceiving *bool `json:"allowReceiving" example:"true"` Alias *string `json:"alias" example:"@person1"` Type string `json:"type" example:"creditCard"` + Version int64 `json:"-"` CreatedAt time.Time `json:"createdAt" example:"2021-01-01T00:00:00Z"` UpdatedAt time.Time `json:"updatedAt" example:"2021-01-01T00:00:00Z"` DeletedAt *time.Time `json:"deletedAt" example:"2021-01-01T00:00:00Z"` @@ -117,6 +118,7 @@ func (e *Account) ToProto() *proto.Account { AllowSending: *e.AllowSending, AllowReceiving: *e.AllowReceiving, Type: e.Type, + Version: e.Version, } if e.ParentAccountID != nil { diff --git a/pkg/utils.go b/pkg/utils.go index 84e1baf2..195f14bc 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -291,8 +291,8 @@ func LockInternalKey(organizationID, ledgerID uuid.UUID, key string) string { return lockInternalKey } -func LockBalanceInternalKey(organizationID, ledgerID uuid.UUID, key, balanceAvailable, balanceScale string) string { - lockBalanceInternalKey := LockInternalKey(organizationID, ledgerID, key) + ":" + balanceAvailable + ":" + balanceScale +func LockVersionInternalKey(organizationID, ledgerID uuid.UUID, key, version string) string { + lockVersionInternalKey := LockInternalKey(organizationID, ledgerID, key) + ":" + version - return lockBalanceInternalKey + return lockVersionInternalKey } From a04a8eebeda62ff6f1812606c778aa5bcbf15041 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 13:05:43 +0100 Subject: [PATCH 22/34] fix: merge with develop :bug: --- pkg/constant/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/constant/errors.go b/pkg/constant/errors.go index 3ed91db3..73a06898 100644 --- a/pkg/constant/errors.go +++ b/pkg/constant/errors.go @@ -93,5 +93,5 @@ var ( ErrInvalidDateRange = errors.New("0083") ErrIdempotencyKey = errors.New("0084") ErrAccountAliasNotFound = errors.New("0085") - ErrLockVersionAccountBalance = errors.New("0085") + ErrLockVersionAccountBalance = errors.New("0086") ) From 6c54b034f152a58077a8f74b1b1fe956a0124dbd Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 16:07:29 +0100 Subject: [PATCH 23/34] refacotr: back get accounts to query cqrs; --- .../internal/adapters/grpc/in/account.go | 158 +++++++++--------- .../adapters/postgres/account/account.mock.go | 124 ++++++++------ .../{command => query}/get-alias-accounts.go | 2 +- .../get-alias-accounts_test.go | 7 +- .../{command => query}/get-ids-accounts.go | 2 +- .../get-ids-accounts_test.go | 5 +- .../internal/adapters/http/in/transaction.go | 8 +- 7 files changed, 159 insertions(+), 147 deletions(-) rename components/ledger/internal/services/{command => query}/get-alias-accounts.go (98%) rename components/ledger/internal/services/{command => query}/get-alias-accounts_test.go (96%) rename components/ledger/internal/services/{command => query}/get-ids-accounts.go (98%) rename components/ledger/internal/services/{command => query}/get-ids-accounts_test.go (96%) diff --git a/components/ledger/internal/adapters/grpc/in/account.go b/components/ledger/internal/adapters/grpc/in/account.go index 656639ce..9215a163 100644 --- a/components/ledger/internal/adapters/grpc/in/account.go +++ b/components/ledger/internal/adapters/grpc/in/account.go @@ -60,7 +60,7 @@ func (ap *AccountProto) GetAccountsByIds(ctx context.Context, ids *account.Accou return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), strings.Join(invalidUUIDs, ", ")) } - acc, err := ap.Command.ListAccountsByIDs(ctx, organizationUUID, ledgerUUID, uuids) + acc, err := ap.Query.ListAccountsByIDs(ctx, organizationUUID, ledgerUUID, uuids) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) @@ -99,7 +99,7 @@ func (ap *AccountProto) GetAccountsByAliases(ctx context.Context, aliases *accou return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) } - acc, err := ap.Command.ListAccountsByAlias(ctx, organizationUUID, ledgerUUID, aliases.GetAliases()) + acc, err := ap.Query.ListAccountsByAlias(ctx, organizationUUID, ledgerUUID, aliases.GetAliases()) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by aliases for grpc", err) @@ -120,82 +120,82 @@ func (ap *AccountProto) GetAccountsByAliases(ctx context.Context, aliases *accou return &response, nil } -//// UpdateAccounts is a method that update Account balances by a given ids. -//func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) { -// logger := pkg.NewLoggerFromContext(ctx) -// tracer := pkg.NewTracerFromContext(ctx) -// -// ctx, span := tracer.Start(ctx, "handler.UpdateAccounts") -// defer span.End() -// -// accounts := make([]*account.Account, 0) -// -// uuids := make([]uuid.UUID, 0) -// -// for _, getacc := range update.GetAccounts() { -// if pkg.IsNilOrEmpty(&getacc.Id) { -// mopentelemetry.HandleSpanError(&span, "Failed to update Accounts because id is empty", nil) -// -// logger.Errorf("Failed to update Accounts because id is empty") -// -// return nil, pkg.ValidateBusinessError(constant.ErrNoAccountIDsProvided, reflect.TypeOf(mmodel.Account{}).Name()) -// } -// -// balance := mmodel.Balance{ -// Available: &getacc.Balance.Available, -// OnHold: &getacc.Balance.OnHold, -// Scale: &getacc.Balance.Scale, -// } -// -// organizationUUID, err := uuid.Parse(getacc.GetOrganizationId()) -// if err != nil { -// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationUUID) -// } -// -// ledgerUUID, err := uuid.Parse(getacc.GetLedgerId()) -// if err != nil { -// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) -// } -// -// accountUUID, err := uuid.Parse(getacc.GetId()) -// if err != nil { -// return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), accountUUID) -// } -// -// _, err = ap.Command.UpdateAccountByID(ctx, organizationUUID, ledgerUUID, accountUUID, &balance) -// if err != nil { -// mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err) -// -// logger.Errorf("Failed to update balance in Account by id for organizationId %s and ledgerId %s in grpc, Error: %s", getacc.OrganizationId, getacc.LedgerId, err.Error()) -// -// return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) -// } -// -// uuids = append(uuids, uuid.MustParse(getacc.Id)) -// } -// -// organizationID := update.GetOrganizationId() -// ledgerID := update.GetLedgerId() -// -// acc, err := ap.Command.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) -// if err != nil { -// mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) -// -// logger.Errorf("Failed to retrieve Accounts by ids for organizationId %s and ledgerId %s in grpc, Error: %s", organizationID, ledgerID, err.Error()) -// -// return nil, pkg.ValidateBusinessError(constant.ErrNoAccountsFound, reflect.TypeOf(mmodel.Account{}).Name()) -// } -// -// for _, ac := range acc { -// accounts = append(accounts, ac.ToProto()) -// } -// -// response := account.AccountsResponse{ -// Accounts: accounts, -// } -// -// return &response, nil -//} +// UpdateAccountsByIDS is a method that update Account balances by a given ids. +func (ap *AccountProto) UpdateAccountsByIDS(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) { + logger := pkg.NewLoggerFromContext(ctx) + tracer := pkg.NewTracerFromContext(ctx) + + ctx, span := tracer.Start(ctx, "handler.UpdateAccounts") + defer span.End() + + accounts := make([]*account.Account, 0) + + uuids := make([]uuid.UUID, 0) + + for _, getacc := range update.GetAccounts() { + if pkg.IsNilOrEmpty(&getacc.Id) { + mopentelemetry.HandleSpanError(&span, "Failed to update Accounts because id is empty", nil) + + logger.Errorf("Failed to update Accounts because id is empty") + + return nil, pkg.ValidateBusinessError(constant.ErrNoAccountIDsProvided, reflect.TypeOf(mmodel.Account{}).Name()) + } + + balance := mmodel.Balance{ + Available: &getacc.Balance.Available, + OnHold: &getacc.Balance.OnHold, + Scale: &getacc.Balance.Scale, + } + + organizationUUID, err := uuid.Parse(getacc.GetOrganizationId()) + if err != nil { + return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationUUID) + } + + ledgerUUID, err := uuid.Parse(getacc.GetLedgerId()) + if err != nil { + return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerUUID) + } + + accountUUID, err := uuid.Parse(getacc.GetId()) + if err != nil { + return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), accountUUID) + } + + _, err = ap.Command.UpdateAccountByID(ctx, organizationUUID, ledgerUUID, accountUUID, &balance) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err) + + logger.Errorf("Failed to update balance in Account by id for organizationId %s and ledgerId %s in grpc, Error: %s", getacc.OrganizationId, getacc.LedgerId, err.Error()) + + return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) + } + + uuids = append(uuids, uuid.MustParse(getacc.Id)) + } + + organizationID := update.GetOrganizationId() + ledgerID := update.GetLedgerId() + + acc, err := ap.Query.ListAccountsByIDs(ctx, uuid.MustParse(organizationID), uuid.MustParse(ledgerID), uuids) + if err != nil { + mopentelemetry.HandleSpanError(&span, "Failed to retrieve Accounts by ids for grpc", err) + + logger.Errorf("Failed to retrieve Accounts by ids for organizationId %s and ledgerId %s in grpc, Error: %s", organizationID, ledgerID, err.Error()) + + return nil, pkg.ValidateBusinessError(constant.ErrNoAccountsFound, reflect.TypeOf(mmodel.Account{}).Name()) + } + + for _, ac := range acc { + accounts = append(accounts, ac.ToProto()) + } + + response := account.AccountsResponse{ + Accounts: accounts, + } + + return &response, nil +} // UpdateAccounts is a method that update Account balances by a given ids. func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) { @@ -228,7 +228,7 @@ func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.Acco logger.Errorf("Failed to update balance in Account by id for organizationId %v and ledgerId %v in grpc, Error: %v", organizationID, ledgerID, err.Error()) - return nil, pkg.ValidateBusinessError(constant.ErrBalanceUpdateFailed, reflect.TypeOf(mmodel.Account{}).Name()) + return nil, err } return nil, nil diff --git a/components/ledger/internal/adapters/postgres/account/account.mock.go b/components/ledger/internal/adapters/postgres/account/account.mock.go index 743eb60f..b3a8c3e4 100644 --- a/components/ledger/internal/adapters/postgres/account/account.mock.go +++ b/components/ledger/internal/adapters/postgres/account/account.mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: /home/max/Workspace/midaz/components/ledger/internal/adapters/postgres/account/account.postgresql.go +// Source: github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account (interfaces: Repository) // // Generated by this command: // -// mockgen -source=/home/max/Workspace/midaz/components/ledger/internal/adapters/postgres/account/account.postgresql.go -destination=/home/max/Workspace/midaz/components/ledger/internal/adapters/postgres/account/account.postgresql_mock.go -package account +// mockgen --destination=account.mock.go --package=account . Repository // // Package account is a generated GoMock package. @@ -13,6 +13,7 @@ import ( context "context" reflect "reflect" + account "github.com/LerianStudio/midaz/pkg/mgrpc/account" mmodel "github.com/LerianStudio/midaz/pkg/mmodel" http "github.com/LerianStudio/midaz/pkg/net/http" uuid "github.com/google/uuid" @@ -23,7 +24,6 @@ import ( type MockRepository struct { ctrl *gomock.Controller recorder *MockRepositoryMockRecorder - isgomock struct{} } // MockRepositoryMockRecorder is the mock recorder for MockRepository. @@ -44,195 +44,209 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { } // Create mocks base method. -func (m *MockRepository) Create(ctx context.Context, acc *mmodel.Account) (*mmodel.Account, error) { +func (m *MockRepository) Create(arg0 context.Context, arg1 *mmodel.Account) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Create", ctx, acc) + ret := m.ctrl.Call(m, "Create", arg0, arg1) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // Create indicates an expected call of Create. -func (mr *MockRepositoryMockRecorder) Create(ctx, acc any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Create(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), ctx, acc) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), arg0, arg1) } // Delete mocks base method. -func (m *MockRepository) Delete(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) error { +func (m *MockRepository) Delete(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", ctx, organizationID, ledgerID, portfolioID, id) + ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(error) return ret0 } // Delete indicates an expected call of Delete. -func (mr *MockRepositoryMockRecorder) Delete(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Delete(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), ctx, organizationID, ledgerID, portfolioID, id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), arg0, arg1, arg2, arg3, arg4) } // Find mocks base method. -func (m *MockRepository) Find(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error) { +func (m *MockRepository) Find(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Find", ctx, organizationID, ledgerID, portfolioID, id) + ret := m.ctrl.Call(m, "Find", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // Find indicates an expected call of Find. -func (mr *MockRepositoryMockRecorder) Find(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Find(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), ctx, organizationID, ledgerID, portfolioID, id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), arg0, arg1, arg2, arg3, arg4) } // FindAlias mocks base method. -func (m *MockRepository) FindAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, alias string) (*mmodel.Account, error) { +func (m *MockRepository) FindAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 string) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindAlias", ctx, organizationID, ledgerID, portfolioID, alias) + ret := m.ctrl.Call(m, "FindAlias", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // FindAlias indicates an expected call of FindAlias. -func (mr *MockRepositoryMockRecorder) FindAlias(ctx, organizationID, ledgerID, portfolioID, alias any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) FindAlias(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAlias", reflect.TypeOf((*MockRepository)(nil).FindAlias), ctx, organizationID, ledgerID, portfolioID, alias) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAlias", reflect.TypeOf((*MockRepository)(nil).FindAlias), arg0, arg1, arg2, arg3, arg4) } // FindAll mocks base method. -func (m *MockRepository) FindAll(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, filter http.Pagination) ([]*mmodel.Account, error) { +func (m *MockRepository) FindAll(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 http.Pagination) ([]*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindAll", ctx, organizationID, ledgerID, portfolioID, filter) + ret := m.ctrl.Call(m, "FindAll", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].([]*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // FindAll indicates an expected call of FindAll. -func (mr *MockRepositoryMockRecorder) FindAll(ctx, organizationID, ledgerID, portfolioID, filter any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) FindAll(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), ctx, organizationID, ledgerID, portfolioID, filter) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), arg0, arg1, arg2, arg3, arg4) } // FindByAlias mocks base method. -func (m *MockRepository) FindByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, alias string) (bool, error) { +func (m *MockRepository) FindByAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 string) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByAlias", ctx, organizationID, ledgerID, alias) + ret := m.ctrl.Call(m, "FindByAlias", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } // FindByAlias indicates an expected call of FindByAlias. -func (mr *MockRepositoryMockRecorder) FindByAlias(ctx, organizationID, ledgerID, alias any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) FindByAlias(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByAlias", reflect.TypeOf((*MockRepository)(nil).FindByAlias), ctx, organizationID, ledgerID, alias) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByAlias", reflect.TypeOf((*MockRepository)(nil).FindByAlias), arg0, arg1, arg2, arg3) } // FindWithDeleted mocks base method. -func (m *MockRepository) FindWithDeleted(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error) { +func (m *MockRepository) FindWithDeleted(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindWithDeleted", ctx, organizationID, ledgerID, portfolioID, id) + ret := m.ctrl.Call(m, "FindWithDeleted", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // FindWithDeleted indicates an expected call of FindWithDeleted. -func (mr *MockRepositoryMockRecorder) FindWithDeleted(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) FindWithDeleted(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindWithDeleted", reflect.TypeOf((*MockRepository)(nil).FindWithDeleted), ctx, organizationID, ledgerID, portfolioID, id) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindWithDeleted", reflect.TypeOf((*MockRepository)(nil).FindWithDeleted), arg0, arg1, arg2, arg3, arg4) } // ListAccountsByAlias mocks base method. -func (m *MockRepository) ListAccountsByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, aliases []string) ([]*mmodel.Account, error) { +func (m *MockRepository) ListAccountsByAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []string) ([]*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAccountsByAlias", ctx, organizationID, ledgerID, aliases) + ret := m.ctrl.Call(m, "ListAccountsByAlias", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // ListAccountsByAlias indicates an expected call of ListAccountsByAlias. -func (mr *MockRepositoryMockRecorder) ListAccountsByAlias(ctx, organizationID, ledgerID, aliases any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) ListAccountsByAlias(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByAlias", reflect.TypeOf((*MockRepository)(nil).ListAccountsByAlias), ctx, organizationID, ledgerID, aliases) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByAlias", reflect.TypeOf((*MockRepository)(nil).ListAccountsByAlias), arg0, arg1, arg2, arg3) } // ListAccountsByIDs mocks base method. -func (m *MockRepository) ListAccountsByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error) { +func (m *MockRepository) ListAccountsByIDs(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []uuid.UUID) ([]*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAccountsByIDs", ctx, organizationID, ledgerID, ids) + ret := m.ctrl.Call(m, "ListAccountsByIDs", arg0, arg1, arg2, arg3) ret0, _ := ret[0].([]*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // ListAccountsByIDs indicates an expected call of ListAccountsByIDs. -func (mr *MockRepositoryMockRecorder) ListAccountsByIDs(ctx, organizationID, ledgerID, ids any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) ListAccountsByIDs(arg0, arg1, arg2, arg3 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByIDs", reflect.TypeOf((*MockRepository)(nil).ListAccountsByIDs), ctx, organizationID, ledgerID, ids) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByIDs", reflect.TypeOf((*MockRepository)(nil).ListAccountsByIDs), arg0, arg1, arg2, arg3) } // ListByAlias mocks base method. -func (m *MockRepository) ListByAlias(ctx context.Context, organizationID, ledgerID, portfolioID uuid.UUID, alias []string) ([]*mmodel.Account, error) { +func (m *MockRepository) ListByAlias(arg0 context.Context, arg1, arg2, arg3 uuid.UUID, arg4 []string) ([]*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListByAlias", ctx, organizationID, ledgerID, portfolioID, alias) + ret := m.ctrl.Call(m, "ListByAlias", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].([]*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // ListByAlias indicates an expected call of ListByAlias. -func (mr *MockRepositoryMockRecorder) ListByAlias(ctx, organizationID, ledgerID, portfolioID, alias any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) ListByAlias(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByAlias", reflect.TypeOf((*MockRepository)(nil).ListByAlias), ctx, organizationID, ledgerID, portfolioID, alias) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByAlias", reflect.TypeOf((*MockRepository)(nil).ListByAlias), arg0, arg1, arg2, arg3, arg4) } // ListByIDs mocks base method. -func (m *MockRepository) ListByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error) { +func (m *MockRepository) ListByIDs(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 []uuid.UUID) ([]*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListByIDs", ctx, organizationID, ledgerID, portfolioID, ids) + ret := m.ctrl.Call(m, "ListByIDs", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].([]*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // ListByIDs indicates an expected call of ListByIDs. -func (mr *MockRepositoryMockRecorder) ListByIDs(ctx, organizationID, ledgerID, portfolioID, ids any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) ListByIDs(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), ctx, organizationID, ledgerID, portfolioID, ids) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), arg0, arg1, arg2, arg3, arg4) } // Update mocks base method. -func (m *MockRepository) Update(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error) { +func (m *MockRepository) Update(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID, arg5 *mmodel.Account) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", ctx, organizationID, ledgerID, portfolioID, id, acc) + ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // Update indicates an expected call of Update. -func (mr *MockRepositoryMockRecorder) Update(ctx, organizationID, ledgerID, portfolioID, id, acc any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) Update(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), ctx, organizationID, ledgerID, portfolioID, id, acc) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), arg0, arg1, arg2, arg3, arg4, arg5) } // UpdateAccountByID mocks base method. -func (m *MockRepository) UpdateAccountByID(ctx context.Context, organizationID, ledgerID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error) { +func (m *MockRepository) UpdateAccountByID(arg0 context.Context, arg1, arg2, arg3 uuid.UUID, arg4 *mmodel.Account) (*mmodel.Account, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateAccountByID", ctx, organizationID, ledgerID, id, acc) + ret := m.ctrl.Call(m, "UpdateAccountByID", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*mmodel.Account) ret1, _ := ret[1].(error) return ret0, ret1 } // UpdateAccountByID indicates an expected call of UpdateAccountByID. -func (mr *MockRepositoryMockRecorder) UpdateAccountByID(ctx, organizationID, ledgerID, id, acc any) *gomock.Call { +func (mr *MockRepositoryMockRecorder) UpdateAccountByID(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountByID", reflect.TypeOf((*MockRepository)(nil).UpdateAccountByID), ctx, organizationID, ledgerID, id, acc) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountByID", reflect.TypeOf((*MockRepository)(nil).UpdateAccountByID), arg0, arg1, arg2, arg3, arg4) +} + +// UpdateAccounts mocks base method. +func (m *MockRepository) UpdateAccounts(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []*account.Account) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateAccounts", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateAccounts indicates an expected call of UpdateAccounts. +func (mr *MockRepositoryMockRecorder) UpdateAccounts(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccounts", reflect.TypeOf((*MockRepository)(nil).UpdateAccounts), arg0, arg1, arg2, arg3) } diff --git a/components/ledger/internal/services/command/get-alias-accounts.go b/components/ledger/internal/services/query/get-alias-accounts.go similarity index 98% rename from components/ledger/internal/services/command/get-alias-accounts.go rename to components/ledger/internal/services/query/get-alias-accounts.go index ee43040e..4b5ed64c 100644 --- a/components/ledger/internal/services/command/get-alias-accounts.go +++ b/components/ledger/internal/services/query/get-alias-accounts.go @@ -1,4 +1,4 @@ -package command +package query import ( "context" diff --git a/components/ledger/internal/services/command/get-alias-accounts_test.go b/components/ledger/internal/services/query/get-alias-accounts_test.go similarity index 96% rename from components/ledger/internal/services/command/get-alias-accounts_test.go rename to components/ledger/internal/services/query/get-alias-accounts_test.go index 2b86d9f5..5440797b 100644 --- a/components/ledger/internal/services/command/get-alias-accounts_test.go +++ b/components/ledger/internal/services/query/get-alias-accounts_test.go @@ -1,15 +1,14 @@ -package command +package query import ( "context" "errors" - "github.com/LerianStudio/midaz/components/ledger/internal/services/query" + "github.com/LerianStudio/midaz/pkg/mpointers" "testing" "github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account" "github.com/LerianStudio/midaz/components/ledger/internal/services" "github.com/LerianStudio/midaz/pkg/mmodel" - "github.com/LerianStudio/midaz/pkg/mpointers" "github.com/google/uuid" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -21,7 +20,7 @@ func TestListAccountsByAlias(t *testing.T) { mockAccountRepo := account.NewMockRepository(ctrl) - uc := &query.UseCase{ + uc := &UseCase{ AccountRepo: mockAccountRepo, } diff --git a/components/ledger/internal/services/command/get-ids-accounts.go b/components/ledger/internal/services/query/get-ids-accounts.go similarity index 98% rename from components/ledger/internal/services/command/get-ids-accounts.go rename to components/ledger/internal/services/query/get-ids-accounts.go index bcc55fd0..7074f0de 100644 --- a/components/ledger/internal/services/command/get-ids-accounts.go +++ b/components/ledger/internal/services/query/get-ids-accounts.go @@ -1,4 +1,4 @@ -package command +package query import ( "context" diff --git a/components/ledger/internal/services/command/get-ids-accounts_test.go b/components/ledger/internal/services/query/get-ids-accounts_test.go similarity index 96% rename from components/ledger/internal/services/command/get-ids-accounts_test.go rename to components/ledger/internal/services/query/get-ids-accounts_test.go index 6044d939..e690770c 100644 --- a/components/ledger/internal/services/command/get-ids-accounts_test.go +++ b/components/ledger/internal/services/query/get-ids-accounts_test.go @@ -1,9 +1,8 @@ -package command +package query import ( "context" "errors" - "github.com/LerianStudio/midaz/components/ledger/internal/services/query" "testing" "github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account" @@ -20,7 +19,7 @@ func TestListAccountsByIDs(t *testing.T) { mockAccountRepo := account.NewMockRepository(ctrl) - uc := &query.UseCase{ + uc := &UseCase{ AccountRepo: mockAccountRepo, } diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index 872f80c2..e8b80115 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -446,7 +446,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L if err != nil { mopentelemetry.HandleSpanError(&spanCreateTransaction, "Failed to convert parserDSL from struct to JSON string", err) - return err + return http.WithError(c, err) } tran, err := handler.Command.CreateTransaction(ctxCreateTransaction, organizationID, ledgerID, &parserDSL) @@ -508,13 +508,13 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L spanReleaseLock.End() ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.update_accounts.update_transaction_status") - _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.DECLINED) - if err != nil { + _, er := handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.DECLINED) + if er != nil { mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err) logger.Errorf("Failed to update Transaction with ID: %s, Error: %s", tran.ID, err.Error()) - return http.WithError(c, err) + return http.WithError(c, er) } spanUpdateTransactionStatus.End() From 60da1cf7dce25469c87bf5786aa6b603fbe79638 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 18:32:37 +0100 Subject: [PATCH 24/34] feat: adjust time :sparkles: --- pkg/constant/account.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/constant/account.go b/pkg/constant/account.go index 413681d3..81ea9144 100644 --- a/pkg/constant/account.go +++ b/pkg/constant/account.go @@ -4,7 +4,7 @@ const ( DefaultExternalAccountAliasPrefix = "@external/" ExternalAccountType = "external" TimeSetLock = 1 - TimeSetLockBalance = 10 - LockRetry = 200 + TimeSetLockBalance = 5 + LockRetry = 100 RedisTimesRetry = 3 ) From ff3a8ad792b1b592fb3faa93bcd86dc4f45a1572 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 18:49:41 +0100 Subject: [PATCH 25/34] fix: make lint :bug: --- .../postgres/account/account.postgresql.go | 140 +++++++++--------- .../internal/adapters/http/in/transaction.go | 14 +- .../command/create-idempotency-key.go | 2 +- .../command/create-lock-race-condition.go | 4 +- pkg/gold/transaction/model/validations.go | 1 - .../transaction/model/validations_test.go | 4 +- 6 files changed, 85 insertions(+), 80 deletions(-) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 26b4b886..1910de40 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -271,7 +271,7 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID, query += " ORDER BY created_at DESC" - account := &AccountPostgreSQLModel{} + acc := &AccountPostgreSQLModel{} ctx, spanQuery := tracer.Start(ctx, "postgres.find.query") @@ -280,27 +280,27 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID, spanQuery.End() if err := row.Scan( - &account.ID, - &account.Name, - &account.ParentAccountID, - &account.EntityID, - &account.AssetCode, - &account.OrganizationID, - &account.LedgerID, - &account.PortfolioID, - &account.ProductID, - &account.AvailableBalance, - &account.OnHoldBalance, - &account.BalanceScale, - &account.Status, - &account.StatusDescription, - &account.AllowSending, - &account.AllowReceiving, - &account.Alias, - &account.Type, - &account.CreatedAt, - &account.UpdatedAt, - &account.DeletedAt, + &acc.ID, + &acc.Name, + &acc.ParentAccountID, + &acc.EntityID, + &acc.AssetCode, + &acc.OrganizationID, + &acc.LedgerID, + &acc.PortfolioID, + &acc.ProductID, + &acc.AvailableBalance, + &acc.OnHoldBalance, + &acc.BalanceScale, + &acc.Status, + &acc.StatusDescription, + &acc.AllowSending, + &acc.AllowReceiving, + &acc.Alias, + &acc.Type, + &acc.CreatedAt, + &acc.UpdatedAt, + &acc.DeletedAt, ); err != nil { mopentelemetry.HandleSpanError(&span, "Failed to scan row", err) @@ -311,7 +311,7 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID, return nil, err } - return account.ToEntity(), nil + return acc.ToEntity(), nil } // FindWithDeleted retrieves an Account entity from the database using the provided ID (including soft-deleted ones). @@ -339,7 +339,7 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ query += " ORDER BY created_at DESC" - account := &AccountPostgreSQLModel{} + acc := &AccountPostgreSQLModel{} ctx, spanQuery := tracer.Start(ctx, "postgres.find_with_deleted.query") @@ -348,27 +348,27 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ spanQuery.End() if err := row.Scan( - &account.ID, - &account.Name, - &account.ParentAccountID, - &account.EntityID, - &account.AssetCode, - &account.OrganizationID, - &account.LedgerID, - &account.PortfolioID, - &account.ProductID, - &account.AvailableBalance, - &account.OnHoldBalance, - &account.BalanceScale, - &account.Status, - &account.StatusDescription, - &account.AllowSending, - &account.AllowReceiving, - &account.Alias, - &account.Type, - &account.CreatedAt, - &account.UpdatedAt, - &account.DeletedAt, + &acc.ID, + &acc.Name, + &acc.ParentAccountID, + &acc.EntityID, + &acc.AssetCode, + &acc.OrganizationID, + &acc.LedgerID, + &acc.PortfolioID, + &acc.ProductID, + &acc.AvailableBalance, + &acc.OnHoldBalance, + &acc.BalanceScale, + &acc.Status, + &acc.StatusDescription, + &acc.AllowSending, + &acc.AllowReceiving, + &acc.Alias, + &acc.Type, + &acc.CreatedAt, + &acc.UpdatedAt, + &acc.DeletedAt, ); err != nil { mopentelemetry.HandleSpanError(&span, "Failed to scan row", err) @@ -379,7 +379,7 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ return nil, err } - return account.ToEntity(), nil + return acc.ToEntity(), nil } // FindAlias retrieves an Account entity from the database using the provided Alias (including soft-deleted ones). @@ -407,7 +407,7 @@ func (r *AccountPostgreSQLRepository) FindAlias(ctx context.Context, organizatio query += " ORDER BY created_at DESC" - account := &AccountPostgreSQLModel{} + acc := &AccountPostgreSQLModel{} ctx, spanQuery := tracer.Start(ctx, "postgres.find_with_deleted.query") @@ -416,27 +416,27 @@ func (r *AccountPostgreSQLRepository) FindAlias(ctx context.Context, organizatio spanQuery.End() if err := row.Scan( - &account.ID, - &account.Name, - &account.ParentAccountID, - &account.EntityID, - &account.AssetCode, - &account.OrganizationID, - &account.LedgerID, - &account.PortfolioID, - &account.ProductID, - &account.AvailableBalance, - &account.OnHoldBalance, - &account.BalanceScale, - &account.Status, - &account.StatusDescription, - &account.AllowSending, - &account.AllowReceiving, - &account.Alias, - &account.Type, - &account.CreatedAt, - &account.UpdatedAt, - &account.DeletedAt, + &acc.ID, + &acc.Name, + &acc.ParentAccountID, + &acc.EntityID, + &acc.AssetCode, + &acc.OrganizationID, + &acc.LedgerID, + &acc.PortfolioID, + &acc.ProductID, + &acc.AvailableBalance, + &acc.OnHoldBalance, + &acc.BalanceScale, + &acc.Status, + &acc.StatusDescription, + &acc.AllowSending, + &acc.AllowReceiving, + &acc.Alias, + &acc.Type, + &acc.CreatedAt, + &acc.UpdatedAt, + &acc.DeletedAt, ); err != nil { mopentelemetry.HandleSpanError(&span, "Failed to scan row", err) @@ -447,7 +447,7 @@ func (r *AccountPostgreSQLRepository) FindAlias(ctx context.Context, organizatio return nil, err } - return account.ToEntity(), nil + return acc.ToEntity(), nil } // FindByAlias find account from the database using Organization and Ledger id and Alias. Returns true and ErrAliasUnavailability error if the alias is already taken. @@ -1033,6 +1033,7 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi for _, acc := range accounts { var updates []string + var args []any updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1)) @@ -1079,7 +1080,6 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi return err } - } if err := tx.Commit(); err != nil { diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index e8b80115..2bdc2d9e 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -62,7 +62,7 @@ func (handler *TransactionHandler) CreateTransactionJSON(p any, c *fiber.Ctx) er parserDSL := input.FromDSl() logger.Infof("Request to create an transaction with details: %#v", parserDSL) - response := handler.createTransaction(c, logger, *parserDSL, false) + response := handler.createTransaction(c, logger, *parserDSL) return response } @@ -130,7 +130,7 @@ func (handler *TransactionHandler) CreateTransactionDSL(c *fiber.Ctx) error { return http.WithError(c, err) } - response := handler.createTransaction(c, logger, parserDSL, false) + response := handler.createTransaction(c, logger, parserDSL) return response } @@ -379,7 +379,7 @@ func (handler *TransactionHandler) GetAllTransactions(c *fiber.Ctx) error { } // createTransaction func that received struct from DSL parsed and create Transaction -func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.Logger, parserDSL goldModel.Transaction, isLocked bool) error { +func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.Logger, parserDSL goldModel.Transaction) error { ctx := c.UserContext() tracer := pkg.NewTracerFromContext(ctx) @@ -509,6 +509,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.update_accounts.update_transaction_status") _, er := handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.DECLINED) + if er != nil { mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err) @@ -526,6 +527,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status") _, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.APPROVED) + if err != nil { mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err) @@ -571,7 +573,7 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L } // getAccounts is a function that split aliases and ids, call the properly function and return Accounts -func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token, hash string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, transaction goldModel.Transaction) ([]*account.Account, error) { +func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token, hash string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, body goldModel.Transaction) ([]*account.Account, error) { span := trace.SpanFromContext(ctx) defer span.End() @@ -582,7 +584,7 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l return nil, err } - err = goldModel.ValidateAccounts(transaction, *validate, accounts) + err = goldModel.ValidateAccounts(body, *validate, accounts) if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to validate accounts", err) @@ -601,7 +603,7 @@ func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, l } if searchAgain { - return handler.getAccountsAndValidate(ctx, logger, token, hash, organizationID, ledgerID, validate, transaction) + return handler.getAccountsAndValidate(ctx, logger, token, hash, organizationID, ledgerID, validate, body) } return accounts, nil diff --git a/components/transaction/internal/services/command/create-idempotency-key.go b/components/transaction/internal/services/command/create-idempotency-key.go index bc09f625..5b994c14 100644 --- a/components/transaction/internal/services/command/create-idempotency-key.go +++ b/components/transaction/internal/services/command/create-idempotency-key.go @@ -13,7 +13,7 @@ func (uc *UseCase) CreateOrCheckIdempotencyKey(ctx context.Context, organization logger := pkg.NewLoggerFromContext(ctx) tracer := pkg.NewTracerFromContext(ctx) - ctx, span := tracer.Start(ctx, "command.create-idempotency-key") + _, span := tracer.Start(ctx, "command.create-idempotency-key") defer span.End() logger.Infof("Trying to create or check idempotency key in redis") diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 6a3c2a55..1d40689a 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -22,6 +22,7 @@ func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID defer span.End() var wg sync.WaitGroup + resultChan := make(chan bool, len(keys)) for _, key := range keys { @@ -30,6 +31,7 @@ func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID logger.Infof("Account try to lock on redis: %v", internalKey) wg.Add(1) + go uc.checkAndReleaseLock(ctx, &wg, internalKey, hash, resultChan) } @@ -41,7 +43,7 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, logger := pkg.NewLoggerFromContext(context.Background()) tracer := pkg.NewTracerFromContext(context.Background()) - ctx, span := tracer.Start(ctx, "redis.check_and_release_lock") + _, span := tracer.Start(ctx, "redis.check_and_release_lock") defer span.End() defer wg.Done() diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index 85b14c3f..be98aa8c 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -28,7 +28,6 @@ func ValidateAccounts(transaction Transaction, validate Responses, accounts []*a if err := validateBalance(transaction, validate.From, acc); err != nil { return err } - } return nil diff --git a/pkg/gold/transaction/model/validations_test.go b/pkg/gold/transaction/model/validations_test.go index 2095ebad..5bc9c69d 100644 --- a/pkg/gold/transaction/model/validations_test.go +++ b/pkg/gold/transaction/model/validations_test.go @@ -14,6 +14,7 @@ import ( func TestValidateAccounts(t *testing.T) { tests := []struct { name string + tran Transaction validate Responses accounts []*a.Account expectedError error @@ -65,7 +66,7 @@ func TestValidateAccounts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := ValidateAccounts(tt.validate, tt.accounts) + err := ValidateAccounts(tt.tran, tt.validate, tt.accounts) if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) { t.Fatalf("Expected error: %v, got: %v", tt.expectedError, err) @@ -82,6 +83,7 @@ func TestValidateFromToOperation(t *testing.T) { tests := []struct { name string ft FromTo + tran Transaction validate Responses acc *a.Account expectedError error From 40a294ee420d0c275b358dde10b2d1765433ed2f Mon Sep 17 00:00:00 2001 From: lerian-studio Date: Mon, 13 Jan 2025 17:49:41 +0000 Subject: [PATCH 26/34] chore(release): update version in .env.example to v1.45.0 --- chocolatey/tools/chocolateyinstall.ps1 | 2 +- components/audit/.env.example | 2 +- components/ledger/.env.example | 2 +- components/mdz/.env.example | 2 +- components/transaction/.env.example | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chocolatey/tools/chocolateyinstall.ps1 b/chocolatey/tools/chocolateyinstall.ps1 index b817fc46..d4875150 100644 --- a/chocolatey/tools/chocolateyinstall.ps1 +++ b/chocolatey/tools/chocolateyinstall.ps1 @@ -1,4 +1,4 @@ -$version = 'v1.44.1' +$version = 'v1.45.0' $ErrorActionPreference = 'Stop'; diff --git a/components/audit/.env.example b/components/audit/.env.example index 59735e70..c74246e8 100644 --- a/components/audit/.env.example +++ b/components/audit/.env.example @@ -2,7 +2,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 SERVER_PORT=3005 SERVER_ADDRESS=:${SERVER_PORT} diff --git a/components/ledger/.env.example b/components/ledger/.env.example index 48bb6090..bb0b58c2 100644 --- a/components/ledger/.env.example +++ b/components/ledger/.env.example @@ -4,7 +4,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 SERVER_PORT=3000 SERVER_ADDRESS=:${SERVER_PORT} diff --git a/components/mdz/.env.example b/components/mdz/.env.example index cfc5d36e..d88cea36 100644 --- a/components/mdz/.env.example +++ b/components/mdz/.env.example @@ -2,4 +2,4 @@ CLIENT_ID=9670e0ca55a29a466d31 CLIENT_SECRET=dd03f916cacf4a98c6a413d9c38ba102dce436a9 URL_API_AUTH=http://127.0.0.1:8080 URL_API_LEDGER=http://127.0.0.1:3000 -VERSION=v1.44.1 +VERSION=v1.45.0 diff --git a/components/transaction/.env.example b/components/transaction/.env.example index 2025762d..2640ac7e 100644 --- a/components/transaction/.env.example +++ b/components/transaction/.env.example @@ -4,7 +4,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 APP_CONTEXT=/transaction/v1 SERVER_PORT=3002 SERVER_ADDRESS=:${SERVER_PORT} From a1e82dace5122444d15300350f64cab4ffa59224 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 18:55:05 +0100 Subject: [PATCH 27/34] Merge branch 'feature/MIDAZ-420-b' of ssh://github.com/LerianStudio/midaz into feature/MIDAZ-420-b --- chocolatey/tools/chocolateyinstall.ps1 | 2 +- components/audit/.env.example | 2 +- components/ledger/.env.example | 2 +- components/mdz/.env.example | 2 +- components/transaction/.env.example | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chocolatey/tools/chocolateyinstall.ps1 b/chocolatey/tools/chocolateyinstall.ps1 index b817fc46..d4875150 100644 --- a/chocolatey/tools/chocolateyinstall.ps1 +++ b/chocolatey/tools/chocolateyinstall.ps1 @@ -1,4 +1,4 @@ -$version = 'v1.44.1' +$version = 'v1.45.0' $ErrorActionPreference = 'Stop'; diff --git a/components/audit/.env.example b/components/audit/.env.example index 59735e70..c74246e8 100644 --- a/components/audit/.env.example +++ b/components/audit/.env.example @@ -2,7 +2,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 SERVER_PORT=3005 SERVER_ADDRESS=:${SERVER_PORT} diff --git a/components/ledger/.env.example b/components/ledger/.env.example index 48bb6090..bb0b58c2 100644 --- a/components/ledger/.env.example +++ b/components/ledger/.env.example @@ -4,7 +4,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 SERVER_PORT=3000 SERVER_ADDRESS=:${SERVER_PORT} diff --git a/components/mdz/.env.example b/components/mdz/.env.example index cfc5d36e..d88cea36 100644 --- a/components/mdz/.env.example +++ b/components/mdz/.env.example @@ -2,4 +2,4 @@ CLIENT_ID=9670e0ca55a29a466d31 CLIENT_SECRET=dd03f916cacf4a98c6a413d9c38ba102dce436a9 URL_API_AUTH=http://127.0.0.1:8080 URL_API_LEDGER=http://127.0.0.1:3000 -VERSION=v1.44.1 +VERSION=v1.45.0 diff --git a/components/transaction/.env.example b/components/transaction/.env.example index 2025762d..2640ac7e 100644 --- a/components/transaction/.env.example +++ b/components/transaction/.env.example @@ -4,7 +4,7 @@ # ENV_NAME=production # APP -VERSION=v1.44.1 +VERSION=v1.45.0 APP_CONTEXT=/transaction/v1 SERVER_PORT=3002 SERVER_ADDRESS=:${SERVER_PORT} From da168e176c58d2d093f336640cfab808226c7fc1 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 18:55:17 +0100 Subject: [PATCH 28/34] Update account.go --- components/ledger/internal/adapters/grpc/in/account.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/components/ledger/internal/adapters/grpc/in/account.go b/components/ledger/internal/adapters/grpc/in/account.go index 9215a163..90502791 100644 --- a/components/ledger/internal/adapters/grpc/in/account.go +++ b/components/ledger/internal/adapters/grpc/in/account.go @@ -204,14 +204,7 @@ func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.Acco ctx, span := tracer.Start(ctx, "handler.UpdateAccounts") defer span.End() - - uuids := make([]uuid.UUID, 0) - aliases := make([]string, 0) - for _, getAcc := range update.GetAccounts() { - uuids = append(uuids, uuid.MustParse(getAcc.Id)) - aliases = append(aliases, getAcc.Alias) - } - + organizationID, err := uuid.Parse(update.OrganizationId) if err != nil { return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationID) From 0cdabd13e58d91d6f86170c70baf4d602690bd16 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 19:17:13 +0100 Subject: [PATCH 29/34] fix: add defer rollback :bug: --- .../postgres/account/account.postgresql.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 1910de40..26c24e1a 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "github.com/LerianStudio/midaz/pkg/mgrpc/account" + "github.com/bxcodec/dbresolver/v2" "reflect" "strconv" "strings" @@ -1031,6 +1032,13 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi return err } + defer func(tx dbresolver.Tx) { + rollbackErr := tx.Rollback() + if rollbackErr != nil { + mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + } + }(tx) + for _, acc := range accounts { var updates []string @@ -1074,7 +1082,7 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi } if rowsAffected == 0 { - err = pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, reflect.TypeOf(mmodel.Account{}).Name()) + err := pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, reflect.TypeOf(mmodel.Account{}).Name()) mopentelemetry.HandleSpanError(&span, "Failed to update account", err) @@ -1082,12 +1090,12 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi } } - if err := tx.Commit(); err != nil { + if commitErr := tx.Commit(); commitErr != nil { err := pkg.ValidateBusinessError(constant.ErrEntityNotFound, reflect.TypeOf(mmodel.Account{}).Name()) mopentelemetry.HandleSpanError(&span, "Failed to commit accounts", err) - return err + return commitErr } return nil From 66d74168b2da61b5fc74662ff7150403fb624b36 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Mon, 13 Jan 2025 20:27:47 +0100 Subject: [PATCH 30/34] fix: add rollback in case of error to unlock database; :bug: --- .../postgres/account/account.postgresql.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 26c24e1a..42b04e7b 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -1071,6 +1071,11 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to update account", err) + rollbackErr := tx.Rollback() + if rollbackErr != nil { + mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + } + return err } @@ -1078,6 +1083,11 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi if err != nil { mopentelemetry.HandleSpanError(&span, "Failed to get rows affected", err) + rollbackErr := tx.Rollback() + if rollbackErr != nil { + mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + } + return err } @@ -1086,6 +1096,11 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi mopentelemetry.HandleSpanError(&span, "Failed to update account", err) + rollbackErr := tx.Rollback() + if rollbackErr != nil { + mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + } + return err } } From f776fcc678cbbb652dfb01ff6ab7890b6ac85777 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Tue, 14 Jan 2025 10:57:21 +0100 Subject: [PATCH 31/34] fix: update go mod dependabot :bug: --- go.mod | 8 ++++---- go.sum | 18 ++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 150f9848..70ca8b54 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/go-playground/locales v0.14.1 github.com/go-playground/universal-translator v0.18.1 github.com/gofiber/fiber/v2 v2.52.6 - github.com/google/trillian v1.7.0 + github.com/google/trillian v1.7.1 github.com/icrowley/fake v0.0.0-20240710202011-f797eb4a99c0 github.com/jackc/pgx/v5 v5.7.2 github.com/jarcoal/httpmock v1.3.1 @@ -29,7 +29,7 @@ require ( github.com/swaggo/fiber-swagger v1.3.0 github.com/swaggo/swag v1.16.4 github.com/transparency-dev/merkle v0.0.2 - go.mongodb.org/mongo-driver v1.17.1 + go.mongodb.org/mongo-driver v1.17.2 go.opentelemetry.io/contrib/bridges/otelzap v0.8.0 go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.9.0 @@ -43,7 +43,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.33.0 go.opentelemetry.io/otel/trace v1.33.0 go.uber.org/mock v0.5.0 - google.golang.org/grpc v1.69.2 + google.golang.org/grpc v1.69.4 google.golang.org/protobuf v1.36.2 gotest.tools v2.2.0+incompatible ) @@ -132,7 +132,7 @@ require ( github.com/google/uuid v1.6.0 github.com/klauspost/compress v1.17.11 // indirect github.com/lestrrat-go/jwx v1.2.30 - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/go.sum b/go.sum index 1c34b968..4d339505 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/trillian v1.7.0 h1:Oib7mKRvZ0Z3GjvNcn2C4clRmFouEOkBcbzw7q8JlFI= -github.com/google/trillian v1.7.0/go.mod h1:JMp1zzzHe7j2m9m8P/eTWOaoon3R/SwgqUnFMhm4vfw= +github.com/google/trillian v1.7.1 h1:+zX8jLM3524bAMPS+VxaDIDgsMv3/ty6DuLWerHXcek= +github.com/google/trillian v1.7.1/go.mod h1:E1UMAHqpZCA8AQdrKdWmHmtUfSeiD0sDWD1cv00Xa+c= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -192,9 +192,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -301,8 +300,8 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zU github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= -go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= +go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/bridges/otelzap v0.8.0 h1:4jqXEd0FGULFBy1bF1ledBePc0Ssu8YVddTgr8BXDTc= @@ -385,7 +384,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= @@ -415,8 +413,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 25fdb706ef65ec550172bb7f6d47652eb8f944f5 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Tue, 14 Jan 2025 13:15:04 +0100 Subject: [PATCH 32/34] feat: add go routines to update; some postgres configs :sparkles: --- .../postgres/account/account.postgresql.go | 94 +++++++++---------- pkg/mpostgres/postgres.go | 10 ++ 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 42b04e7b..540d7c6b 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -5,10 +5,10 @@ import ( "database/sql" "errors" "github.com/LerianStudio/midaz/pkg/mgrpc/account" - "github.com/bxcodec/dbresolver/v2" "reflect" "strconv" "strings" + "sync" "time" "github.com/LerianStudio/midaz/pkg/mpointers" @@ -1032,75 +1032,65 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi return err } - defer func(tx dbresolver.Tx) { - rollbackErr := tx.Rollback() - if rollbackErr != nil { - mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) - } - }(tx) + var wg sync.WaitGroup + errChan := make(chan error, len(accounts)) for _, acc := range accounts { - var updates []string - - var args []any + wg.Add(1) - updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1)) - args = append(args, acc.Balance.Available) + go func(acc *account.Account) { + defer wg.Done() + var updates []string + var args []interface{} - updates = append(updates, "on_hold_balance = $"+strconv.Itoa(len(args)+1)) - args = append(args, acc.Balance.OnHold) + updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.Available) - updates = append(updates, "balance_scale = $"+strconv.Itoa(len(args)+1)) - args = append(args, acc.Balance.Scale) + updates = append(updates, "on_hold_balance = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.OnHold) - updates = append(updates, "version = $"+strconv.Itoa(len(args)+1)) - version := acc.Version + 1 - args = append(args, version) + updates = append(updates, "balance_scale = $"+strconv.Itoa(len(args)+1)) + args = append(args, acc.Balance.Scale) - updates = append(updates, "updated_at = $"+strconv.Itoa(len(args)+1)) - args = append(args, time.Now(), organizationID, ledgerID, acc.Id, acc.Version) + updates = append(updates, "version = $"+strconv.Itoa(len(args)+1)) + version := acc.Version + 1 + args = append(args, version) - query := `UPDATE account SET ` + strings.Join(updates, ", ") + - ` WHERE organization_id = $` + strconv.Itoa(len(args)-3) + - ` AND ledger_id = $` + strconv.Itoa(len(args)-2) + - ` AND id = $` + strconv.Itoa(len(args)-1) + - ` AND version = $` + strconv.Itoa(len(args)) + - ` AND deleted_at IS NULL` + updates = append(updates, "updated_at = $"+strconv.Itoa(len(args)+1)) + args = append(args, time.Now(), organizationID, ledgerID, acc.Id, acc.Version) - result, err := tx.ExecContext(ctx, query, args...) - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to update account", err) + query := `UPDATE account SET ` + strings.Join(updates, ", ") + + ` WHERE organization_id = $` + strconv.Itoa(len(args)-3) + + ` AND ledger_id = $` + strconv.Itoa(len(args)-2) + + ` AND id = $` + strconv.Itoa(len(args)-1) + + ` AND version = $` + strconv.Itoa(len(args)) + + ` AND deleted_at IS NULL` - rollbackErr := tx.Rollback() - if rollbackErr != nil { - mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + result, err := tx.ExecContext(ctx, query, args...) + if err != nil { + errChan <- err + return } - return err - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - mopentelemetry.HandleSpanError(&span, "Failed to get rows affected", err) - - rollbackErr := tx.Rollback() - if rollbackErr != nil { - mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + rowsAffected, err := result.RowsAffected() + if err != nil || rowsAffected == 0 { + if err == nil { + err = sql.ErrNoRows + } + errChan <- err } + }(acc) + } - return err - } - - if rowsAffected == 0 { - err := pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, reflect.TypeOf(mmodel.Account{}).Name()) - - mopentelemetry.HandleSpanError(&span, "Failed to update account", err) + wg.Wait() + close(errChan) + for err := range errChan { + if err != nil { rollbackErr := tx.Rollback() if rollbackErr != nil { - mopentelemetry.HandleSpanError(&span, "Failed to rollback transaction", rollbackErr) + return rollbackErr } - return err } } diff --git a/pkg/mpostgres/postgres.go b/pkg/mpostgres/postgres.go index 6527fb2e..e81f06ff 100644 --- a/pkg/mpostgres/postgres.go +++ b/pkg/mpostgres/postgres.go @@ -6,6 +6,8 @@ import ( "go.uber.org/zap" "net/url" "path/filepath" + "time" + // File system migration source. We need to import it to be able to use it as source in migrate.NewWithSourceInstance "github.com/LerianStudio/midaz/pkg/mlog" @@ -39,12 +41,20 @@ func (pc *PostgresConnection) Connect() error { return nil } + dbPrimary.SetMaxOpenConns(100) + dbPrimary.SetMaxIdleConns(100) + dbPrimary.SetConnMaxLifetime(time.Minute * 5) + dbReadOnlyReplica, err := sql.Open("pgx", pc.ConnectionStringReplica) if err != nil { pc.Logger.Fatal("failed to open connect to replica database", zap.Error(err)) return nil } + dbReadOnlyReplica.SetMaxOpenConns(100) + dbReadOnlyReplica.SetMaxIdleConns(100) + dbReadOnlyReplica.SetConnMaxLifetime(time.Minute * 5) + connectionDB := dbresolver.New( dbresolver.WithPrimaryDBs(dbPrimary), dbresolver.WithReplicaDBs(dbReadOnlyReplica), From a7df566659892e15eddc0486087d93a74cf707d4 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Tue, 14 Jan 2025 14:26:59 +0100 Subject: [PATCH 33/34] fix: lint; add version; :bug: --- .../adapters/postgres/account/account.postgresql.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go index 540d7c6b..3488a1b0 100644 --- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go +++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go @@ -226,6 +226,7 @@ func (r *AccountPostgreSQLRepository) FindAll(ctx context.Context, organizationI &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -299,6 +300,7 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID, &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -367,6 +369,7 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -435,6 +438,7 @@ func (r *AccountPostgreSQLRepository) FindAlias(ctx context.Context, organizatio &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -549,6 +553,7 @@ func (r *AccountPostgreSQLRepository) ListByIDs(ctx context.Context, organizatio &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -620,6 +625,7 @@ func (r *AccountPostgreSQLRepository) ListByAlias(ctx context.Context, organizat &acc.AllowReceiving, &acc.Alias, &acc.Type, + &acc.Version, &acc.CreatedAt, &acc.UpdatedAt, &acc.DeletedAt, @@ -1033,6 +1039,7 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi } var wg sync.WaitGroup + errChan := make(chan error, len(accounts)) for _, acc := range accounts { @@ -1040,8 +1047,10 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi go func(acc *account.Account) { defer wg.Done() + var updates []string - var args []interface{} + + var args []any updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1)) args = append(args, acc.Balance.Available) @@ -1091,6 +1100,7 @@ func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organi if rollbackErr != nil { return rollbackErr } + return err } } From acc57a3fc91ebbe4d4a7492bbd4bd49d23945e78 Mon Sep 17 00:00:00 2001 From: MartinezAvellan Date: Wed, 15 Jan 2025 20:21:50 +0100 Subject: [PATCH 34/34] feat: add magic numbers to constant :sparkles: --- .../internal/services/command/create-lock-race-condition.go | 2 +- pkg/constant/account.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go index 1d40689a..66789fc1 100644 --- a/components/transaction/internal/services/command/create-lock-race-condition.go +++ b/components/transaction/internal/services/command/create-lock-race-condition.go @@ -62,7 +62,7 @@ func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, return } - time.Sleep(200 * time.Millisecond) + time.Sleep(constant.CheckAndReleaseLock * time.Millisecond) } } diff --git a/pkg/constant/account.go b/pkg/constant/account.go index 81ea9144..187b25ac 100644 --- a/pkg/constant/account.go +++ b/pkg/constant/account.go @@ -7,4 +7,5 @@ const ( TimeSetLockBalance = 5 LockRetry = 100 RedisTimesRetry = 3 + CheckAndReleaseLock = 200 )