diff --git a/internal/service/handlers/activate_balance.go b/internal/service/handlers/activate_balance.go index 748916d..392a614 100644 --- a/internal/service/handlers/activate_balance.go +++ b/internal/service/handlers/activate_balance.go @@ -6,7 +6,6 @@ import ( "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" - "github.com/rarimo/geo-points-svc/internal/data/evtypes" "github.com/rarimo/geo-points-svc/internal/data/evtypes/models" "github.com/rarimo/geo-points-svc/internal/service/requests" "gitlab.com/distributed_lab/ape" @@ -20,8 +19,11 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { return } - nullifier := req.Data.ID - referralCode := req.Data.Attributes.ReferredBy + var ( + nullifier = req.Data.ID + referralCode = req.Data.Attributes.ReferredBy + log = Log(r).WithField("nullifier", nullifier) + ) if !auth.Authenticates(UserClaims(r), auth.UserGrant(nullifier)) { ape.RenderErr(w, problems.Unauthorized()) @@ -30,38 +32,38 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { balance, err := BalancesQ(r).FilterByNullifier(nullifier).Get() if err != nil { - Log(r).WithError(err).Error("Failed to get balance by nullifier") + log.WithError(err).Error("Failed to get balance by nullifier") ape.RenderErr(w, problems.InternalError()) return } if balance == nil { - Log(r).Debugf("Balance %s not exist", nullifier) + log.Debug("Balance not found") ape.RenderErr(w, problems.NotFound()) return } if balance.ReferredBy != nil { - Log(r).Infof("Balance already activated with code '%s'", *balance.ReferredBy) + log.Infof("Balance already activated with code %s", *balance.ReferredBy) ape.RenderErr(w, problems.Conflict()) return } - referral, err := ReferralsQ(r).FilterInactive().Get(req.Data.Attributes.ReferredBy) + referral, err := ReferralsQ(r).FilterInactive().Get(referralCode) if err != nil { - Log(r).WithError(err).Error("Failed to get referral by ID") + log.WithError(err).Error("Failed to get referral by ID") ape.RenderErr(w, problems.InternalError()) return } if referral == nil { - Log(r).Debugf("Referral code '%s' not found", referralCode) + log.Debugf("Referral code %s not found", referralCode) ape.RenderErr(w, problems.NotFound()) return } refBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() if err != nil || refBalance == nil { // must exist due to FK constraint - Log(r).WithError(err).Error("Failed to get referrer balance by nullifier") + log.WithError(err).Error("Failed to get referrer balance by nullifier") ape.RenderErr(w, problems.InternalError()) return } @@ -73,7 +75,7 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { } err = BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(map[string]any{ - data.ColReferredBy: balance.ReferredBy, + data.ColReferredBy: referralCode, data.ColLevel: level, }) if err != nil { @@ -81,6 +83,7 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { } if refBalance.ReferredBy != nil { + log.Debug("Be referred event will be fulfilled for referee") err = EventsQ(r).Insert(data.Event{ Nullifier: nullifier, Type: models.TypeBeReferred, @@ -95,33 +98,16 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { return fmt.Errorf("failed to consume referral: %w", err) } - if balance.IsVerified { - // Be referred event is a welcome bonus when you created balance with non-genesis referral code - if err = claimBeReferredEvent(r, *balance); err != nil { - return fmt.Errorf("failed to claim be referred event: %w", err) - } - } - - evTypeRef := EventTypes(r).Get(models.TypeReferralSpecific, evtypes.FilterInactive) - if evTypeRef == nil { - Log(r).Debug("Referral specific event type is inactive") + if !balance.IsVerified { + log.Debug("Balance is not verified, events will not be claimed") return nil } - if balance.IsVerified { - if err = claimReferralSpecificEvents(r, *evTypeRef, balance.Nullifier); err != nil { - return fmt.Errorf("failed to claim referral specific events: %w", err) - } - } - - if err = addEventForReferrer(r, *evTypeRef, *balance); err != nil { - return fmt.Errorf("add event for referrer: %w", err) - } - - return nil + balance.ReferredBy = &referral.ID + return doVerificationEventUpdates(r, *balance) }) if err != nil { - Log(r).WithError(err).Error("Failed to insert events and consume referral for balance") + log.WithError(err).Error("Failed to insert events and consume referral for balance") ape.RenderErr(w, problems.InternalError()) return } @@ -131,7 +117,7 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { // while with RETURNING we operate a single one. // Balance will exist cause of previous logic. if balance, err = BalancesQ(r).GetWithRank(nullifier); err != nil { - Log(r).WithError(err).Error("Failed to get created balance by nullifier") + log.WithError(err).Error("Failed to get created balance by nullifier") ape.RenderErr(w, problems.InternalError()) return } @@ -141,7 +127,7 @@ func ActivateBalance(w http.ResponseWriter, r *http.Request) { WithStatus(). Select() if err != nil { - Log(r).WithError(err).Error("Failed to get referrals by nullifier with rewarding field") + log.WithError(err).Error("Failed to get referrals by nullifier with rewarding field") ape.RenderErr(w, problems.InternalError()) return } diff --git a/internal/service/handlers/create_balance.go b/internal/service/handlers/create_balance.go index 4883e4e..a2efb0e 100644 --- a/internal/service/handlers/create_balance.go +++ b/internal/service/handlers/create_balance.go @@ -40,12 +40,14 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { var ( refCode = req.Data.Attributes.ReferredBy isGenesisRef = false + log = Log(r).WithField("nullifier", nullifier) ) if refCode != nil { + log.Debug("Balance will be activated with referral code") referral, err := ReferralsQ(r).FilterInactive().Get(*refCode) if err != nil { - Log(r).WithError(err).Error("Failed to get referral by ID") + log.WithError(err).Error("Failed to get referral by ID") ape.RenderErr(w, problems.InternalError()) return } @@ -56,7 +58,7 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { refBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() if err != nil || refBalance == nil { // must exist due to FK constraint - Log(r).WithError(err).Error("Failed to get referrer balance by nullifier") + log.WithError(err).Error("Failed to get referrer balance by nullifier") ape.RenderErr(w, problems.InternalError()) return } @@ -67,18 +69,19 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { if refCode == nil { balance, err = createBalanceWithEvents(nullifier, events, r) if err != nil { - Log(r).WithError(err).Error("Failed to create disabled balance with events") + log.WithError(err).Error("Failed to create disabled balance with events") ape.RenderErr(w, problems.InternalError()) return } + log.Debug("Created disabled balance with events") ape.Render(w, newBalanceResponse(*balance, nil, 0, 0)) return } err = createBalanceWithEventsAndReferrals(nullifier, *refCode, events, r) if err != nil { - Log(r).WithError(err).Error("Failed to create balance with events and referrals") + log.WithError(err).Error("Failed to create balance with events and referrals") ape.RenderErr(w, problems.InternalError()) return } @@ -89,7 +92,7 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { // Balance will exist cause of previous logic. balance, err = BalancesQ(r).GetWithRank(nullifier) if err != nil { - Log(r).WithError(err).Error("Failed to get created balance by nullifier") + log.WithError(err).Error("Failed to get created balance by nullifier") ape.RenderErr(w, problems.InternalError()) return } @@ -99,7 +102,7 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { WithStatus(). Select() if err != nil { - Log(r).WithError(err).Error("Failed to get referrals by nullifier with rewarding field") + log.WithError(err).Error("Failed to get referrals by nullifier with rewarding field") ape.RenderErr(w, problems.InternalError()) return } diff --git a/internal/service/handlers/verify_passport.go b/internal/service/handlers/verify_passport.go index 3dbc64e..50e6392 100644 --- a/internal/service/handlers/verify_passport.go +++ b/internal/service/handlers/verify_passport.go @@ -59,10 +59,6 @@ func VerifyPassport(w http.ResponseWriter, r *http.Request) { return } - if proof == nil { - log.Debug("Proof is not provided: performing logic of joining program instead of full verification") - } - balance, errs := getAndVerifyBalanceEligibility(r, req.Data.ID, proof) if len(errs) > 0 { ape.RenderErr(w, errs...) @@ -90,7 +86,7 @@ func VerifyPassport(w http.ResponseWriter, r *http.Request) { } h := sig.Get(zk.PersonalNumberHash) if h == "" { - log.Errorf("Proof verification succeeded, but shared hash was not obtained: proof: %+v", proof) + log.Errorf("Shared hash was not obtained for valid proof: %+v", proof) ape.RenderErr(w, problems.InternalError()) return } @@ -118,10 +114,12 @@ func VerifyPassport(w http.ResponseWriter, r *http.Request) { "data/attributes/anonymous_id": validation.Validate(anonymousID, validation.Required, validation.In(balAID)), }.Filter() if err != nil { + log.Warnf("Anonymous ID was changed, got %s, want %s", anonymousID, balAID) ape.RenderErr(w, problems.BadRequest(err)...) return } + log.Debug("Balance has joined program previously, updating shared hash") err = BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(map[string]any{ data.ColSharedHash: *sharedHash, }) @@ -136,10 +134,20 @@ func VerifyPassport(w http.ResponseWriter, r *http.Request) { } err = EventsQ(r).Transaction(func() error { - return doPassportScanUpdates(r, *balance, anonymousID, sharedHash) + if err = updateBalanceVerification(r, *balance, anonymousID, sharedHash); err != nil { + return fmt.Errorf("update balance verification info: %w", err) + } + + if balance.ReferredBy == nil { + log.Debug("Balance is disabled, events will not be claimed") + return nil + } + + return doVerificationEventUpdates(r, *balance) }) + if err != nil { - log.WithError(err).Error("Failed to execute transaction") + log.WithError(err).Error("Failed to do passport scan updates") ape.RenderErr(w, problems.InternalError()) return } @@ -174,6 +182,7 @@ func getAndVerifyBalanceEligibility( proof *zkptypes.ZKProof, ) (balance *data.Balance, errs []*jsonapi.ErrorObject) { + log := Log(r).WithField("balance.nullifier", nullifier) if !auth.Authenticates(UserClaims(r), auth.UserGrant(nullifier)) { return nil, append(errs, problems.Unauthorized()) } @@ -185,10 +194,11 @@ func getAndVerifyBalanceEligibility( } if balance == nil { - Log(r).Debug("Balance absent") + log.Debug("Balance not found") return nil, append(errs, problems.NotFound()) } if proof == nil { + log.Debug("Proof is not provided and will not be verified") return balance, nil } @@ -198,70 +208,16 @@ func getAndVerifyBalanceEligibility( err = Verifier(r).VerifyProof(*proof) if err != nil { if errors.Is(err, identity.ErrContractCall) { - Log(r).WithError(err).Error("Failed to verify proof") + log.WithError(err).Error("Failed to verify proof") return nil, append(errs, problems.InternalError()) } return nil, problems.BadRequest(err) } + log.Debug("Passport proof successfully verified") return balance, nil } -// doPassportScanUpdates performs all the necessary updates when the passport -// scan proof is provided -func doPassportScanUpdates(r *http.Request, balance data.Balance, anonymousID string, sharedHash *string) error { - err := updateBalanceVerification(r, balance, anonymousID, sharedHash) - if err != nil { - return fmt.Errorf("update balance country: %w", err) - } - // must not auto-claim or add referral events for disabled balance - if balance.ReferredBy == nil { - return nil - } - - // Fulfill passport scan event for user if event active - // Event can be automatically claimed if auto-claim is enabled - if err = fulfillOrClaimPassportScanEvent(r, balance); err != nil { - return fmt.Errorf("fulfill passport scan event: %w", err) - } - - if balance.ReferredBy == nil { - Log(r).Debugf("Balance disabled: %s", balance.Nullifier) - return nil - } - - evTypeRef := EventTypes(r).Get(models.TypeReferralSpecific, evtypes.FilterInactive) - if evTypeRef == nil { - Log(r).Debug("Referral specific event type is inactive") - return nil - } - - // Claim events for invited friends who scanned the passport. - // This is possible when the user registered in the referral - // program and invited friends, the friends scanned the passport, - // but since the user hadn't a supported passport, the event - // could not be claimed. And now that user has scanned the passport, - // it is necessary to claim events for user's friends if auto-claim - // is enabled - if err = claimReferralSpecificEvents(r, *evTypeRef, balance.Nullifier); err != nil { - return fmt.Errorf("failed to claim referral specific events: %w", err) - } - - // Be referred event is a welcome bonus when you created balance with non-genesis referral code - if err = claimBeReferredEvent(r, balance); err != nil { - return fmt.Errorf("failed to claim be referred event: %w", err) - } - - // Adds a friend event for the referrer. If the event - // is inactive, then nothing happens. If active, the - // fulfilled event is added and, if possible, the event claimed - if err = addEventForReferrer(r, *evTypeRef, balance); err != nil { - return fmt.Errorf("add event for referrer: %w", err) - } - - return nil -} - func updateBalanceVerification(r *http.Request, balance data.Balance, anonymousID string, sharedHash *string) error { toUpd := map[string]any{ data.ColIsVerified: true, @@ -273,12 +229,34 @@ func updateBalanceVerification(r *http.Request, balance data.Balance, anonymousI err := BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(toUpd) if err != nil { - return fmt.Errorf("update balance country: %w", err) + return fmt.Errorf("update balance: %w", err) } return nil } +func doVerificationEventUpdates(r *http.Request, balance data.Balance) error { + if err := fulfillOrClaimPassportScanEvent(r, balance); err != nil { + return fmt.Errorf("fulfill passport scan event: %w", err) + } + + if err := claimBeReferredEvent(r, balance); err != nil { + return fmt.Errorf("failed to claim be referred event: %w", err) + } + if err := claimReferralSpecificEvents(r, balance.Nullifier); err != nil { + return fmt.Errorf("failed to claim referral specific events: %w", err) + } + if err := addEventForReferrer(r, balance); err != nil { + return fmt.Errorf("add event for referrer: %w", err) + } + + Log(r).WithField("balance.nullifier", balance.Nullifier). + Debug("All verification-related events successfully updated") + return nil +} + +// fulfillOrClaimPassportScanEvent Fulfill passport scan event for user if event +// active. Event can be automatically claimed if auto-claim is enabled. func fulfillOrClaimPassportScanEvent(r *http.Request, balance data.Balance) error { evTypePassport := EventTypes(r).Get(models.TypePassportScan, evtypes.FilterInactive) if evTypePassport == nil { @@ -286,18 +264,21 @@ func fulfillOrClaimPassportScanEvent(r *http.Request, balance data.Balance) erro return nil } - event, err := EventsQ(r).FilterByNullifier(balance.Nullifier). + // event could also be fulfilled when balance was activated + event, err := EventsQ(r). + FilterByNullifier(balance.Nullifier). FilterByType(models.TypePassportScan). - FilterByStatus(data.EventOpen).Get() + FilterByStatus(data.EventOpen, data.EventFulfilled). + Get() if err != nil { return fmt.Errorf("get open passport scan event: %w", err) } - if event == nil { - return errors.New("inconsistent state: balance is not verified, event type is active, but no open event was found") + return errors.New("inconsistent state: balance is not verified, event type is active, but no open or fulfilled event was found") } - if !evTypePassport.AutoClaim { + if !evTypePassport.AutoClaim || balance.ReferredBy == nil { + Log(r).Debug("Passport scan event is not auto-claimed or balance is disabled, event will be fulfilled") _, err = EventsQ(r). FilterByID(event.ID). Update(data.EventFulfilled, nil, nil) @@ -326,10 +307,16 @@ func fulfillOrClaimPassportScanEvent(r *http.Request, balance data.Balance) erro return nil } -// evTypeRef must not be nil -func claimReferralSpecificEvents(r *http.Request, evTypeRef models.EventType, nullifier string) error { - if !evTypeRef.AutoClaim { - Log(r).Debugf("Auto claim for referral specific disabled") +// claimReferralSpecificEvents Claim events for invited friends who scanned the +// passport. This is possible when the user registered in the referral program +// and invited friends, the friends scanned the passport, but since the user +// hadn't a supported passport, the event could not be claimed. And now that user +// has scanned the passport, it is necessary to claim events for user's friends +// if auto-claim is enabled. +func claimReferralSpecificEvents(r *http.Request, nullifier string) error { + evTypeRef := EventTypes(r).Get(models.TypeReferralSpecific, evtypes.FilterInactive) + if evTypeRef == nil || !evTypeRef.AutoClaim { + Log(r).Debugf("Referral specific event is inactive or cannot be auto-claimed") return nil } @@ -374,6 +361,8 @@ func claimReferralSpecificEvents(r *http.Request, evTypeRef models.EventType, nu return nil } +// claimBeReferredEvent get fulfilled be_referred event and claims it if user is +// invited with non-genesis referral code func claimBeReferredEvent(r *http.Request, balance data.Balance) error { evTypeBeRef := EventTypes(r).Get(models.TypeBeReferred, evtypes.FilterInactive) if evTypeBeRef == nil || !evTypeBeRef.AutoClaim { @@ -385,7 +374,7 @@ func claimBeReferredEvent(r *http.Request, balance data.Balance) error { FilterByStatus(data.EventFulfilled). Get() if err != nil { - return fmt.Errorf("get fulfilled be referred event: %w", err) + return fmt.Errorf("get fulfilled be_referred event: %w", err) } if event == nil { Log(r).Debug("User is not eligible for be_referred event") @@ -410,8 +399,21 @@ func claimBeReferredEvent(r *http.Request, balance data.Balance) error { return nil } -func addEventForReferrer(r *http.Request, evTypeRef models.EventType, balance data.Balance) error { - // ReferredBy always non-nil because of the previous logic +// addEventForReferrer adds a friend event for the referrer. If the event is +// inactive, then nothing happens. If active, the fulfilled event is added and, +// if possible, the event claimed. +func addEventForReferrer(r *http.Request, balance data.Balance) error { + evTypeRef := EventTypes(r).Get(models.TypeReferralSpecific, evtypes.FilterInactive) + if evTypeRef == nil { + Log(r).Debug("Referral specific event type is inactive") + return nil + } + + if balance.ReferredBy == nil { + Log(r).Debug("Balance is disabled, event for referrer will not be added") + return nil + } + referral, err := ReferralsQ(r).Get(*balance.ReferredBy) if err != nil { return fmt.Errorf("get referral by ID: %w", err) @@ -420,22 +422,22 @@ func addEventForReferrer(r *http.Request, evTypeRef models.EventType, balance da return fmt.Errorf("critical: referred_by not null, but row in referrals absent") } - referrerBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() + refBalance, err := BalancesQ(r).FilterByNullifier(referral.Nullifier).Get() if err != nil { return fmt.Errorf("failed to get referrer balance: %w", err) } - if referrerBalance == nil { + if refBalance == nil { return fmt.Errorf("critical: referrer balance not exist [%s], while referral code exist", referral.Nullifier) } - if referrerBalance.ReferredBy == nil { - Log(r).Debug("Referrer is genesis balance") + if refBalance.ReferredBy == nil { + Log(r).Debug("Referrer is genesis balance, referee will not be rewarded") return nil } - if !evTypeRef.AutoClaim || !referrerBalance.IsVerified { - if !referrerBalance.IsVerified { - Log(r).Debug("Referrer not scan passport yet! Add fulfilled events") + if !evTypeRef.AutoClaim || !refBalance.IsVerified { + if !refBalance.IsVerified { + Log(r).Debug("Referrer has not scanned passport yet, adding fulfilled events") } err = EventsQ(r).Insert(data.Event{ Nullifier: referral.Nullifier, @@ -465,7 +467,7 @@ func addEventForReferrer(r *http.Request, evTypeRef models.EventType, balance da Levels(r), ReferralsQ(r), BalancesQ(r), - *referrerBalance, + *refBalance, evTypeRef.Reward) if err != nil { return fmt.Errorf("failed to do claim event updates for referrer referral specific events: %w", err) diff --git a/internal/service/requests/activate_balance.go b/internal/service/requests/activate_balance.go index fcfe301..b3beb3a 100644 --- a/internal/service/requests/activate_balance.go +++ b/internal/service/requests/activate_balance.go @@ -21,7 +21,7 @@ func NewActivateBalance(r *http.Request) (req resources.ActivateBalanceRequest, errs := validation.Errors{ "data/id": validation.Validate(req.Data.ID, validation.Required, validation.Match(nullifierRegexp), validation.In(nullifier)), - "data/type": validation.Validate(req.Data.Type, validation.Required, validation.In(resources.CREATE_BALANCE)), + "data/type": validation.Validate(req.Data.Type, validation.Required, validation.In(resources.ACTIVATE_BALANCE)), "data/attributes/referred_by": validation.Validate(req.Data.Attributes.ReferredBy, validation.Required), }