Skip to content

Commit

Permalink
adjusted kyc handling logic to support step chaining
Browse files Browse the repository at this point in the history
  • Loading branch information
ice-ares committed Nov 14, 2023
1 parent 4568249 commit 771fe0c
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 73 deletions.
19 changes: 11 additions & 8 deletions bookkeeper/storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package storage

import (
"context"
"fmt"
"sort"
"testing"
stdlibtime "time"
Expand Down Expand Up @@ -86,7 +87,8 @@ func TestStorage(t *testing.T) {
BalanceLastUpdatedAtField: model.BalanceLastUpdatedAtField{BalanceLastUpdatedAt: time.New(t1)},
BalanceTotalMintedField: model.BalanceTotalMintedField{BalanceTotalMinted: 33},
BalanceTotalSlashedField: model.BalanceTotalSlashedField{BalanceTotalSlashed: 44},
}}
},
}
require.NoError(t, cl.Insert(context.Background(), columns, input, usrs))

usrs = []*model.User{
Expand All @@ -101,7 +103,8 @@ func TestStorage(t *testing.T) {
BalanceLastUpdatedAtField: model.BalanceLastUpdatedAtField{BalanceLastUpdatedAt: time.New(t2)},
BalanceTotalMintedField: model.BalanceTotalMintedField{BalanceTotalMinted: 3333},
BalanceTotalSlashedField: model.BalanceTotalSlashedField{BalanceTotalSlashed: 4444},
}}
},
}
require.NoError(t, cl.Insert(context.Background(), columns, input, usrs))

h1, err := cl.SelectBalanceHistory(context.Background(), id1, []stdlibtime.Time{t1, t2})
Expand All @@ -127,25 +130,25 @@ func TestStorage_SelectAdjustUserInformation_NoError_On_LongValues(t *testing.T)

limit := int64(1000)
offset := int64(0)
userIDs := make(map[int64]struct{}, 1_000_000)
userIDs := make([]string, 0, 1_000_000)
for ix := 1_000_000; ix < 2_000_000; ix++ {
userIDs[int64(ix)] = struct{}{}
userIDs = append(userIDs, fmt.Sprint(ix))
}
_, err := cl.GetAdjustUserInformation(context.Background(), userIDs, limit, offset)
require.NoError(t, err)

userIDs = nil
userIDs = make(map[int64]struct{}, 1_000_000)
userIDs = make([]string, 0, 1_000_000)
for ix := 10_000_000; ix < 11_000_000; ix++ {
userIDs[int64(ix)] = struct{}{}
userIDs = append(userIDs, fmt.Sprint(ix))
}
_, err = cl.GetAdjustUserInformation(context.Background(), userIDs, limit, offset)
require.NoError(t, err)

userIDs = nil
userIDs = make(map[int64]struct{}, 1_000_000)
userIDs = make([]string, 0, 1_000_000)
for ix := 54_000_000; ix < 55_000_000; ix++ {
userIDs[int64(ix)] = struct{}{}
userIDs = append(userIDs, fmt.Sprint(ix))
}
_, err = cl.GetAdjustUserInformation(context.Background(), userIDs, limit, offset)
require.NoError(t, err)
Expand Down
18 changes: 10 additions & 8 deletions cmd/freezer-refrigerant/api/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,16 @@ const docTemplate = `{
"type": "boolean",
"example": true
},
"skipKYCStep": {
"description": "Specify this if you want to skip a specific KYC step before starting a new mining session or extending an existing one.\nSome KYC steps are not skippable.",
"allOf": [
{
"$ref": "#/definitions/users.KYCStep"
}
],
"example": 0
"skipKYCSteps": {
"description": "Specify this if you want to skip one or more specific KYC steps before starting a new mining session or extending an existing one.\nSome KYC steps are not skippable.",
"type": "array",
"items": {
"$ref": "#/definitions/users.KYCStep"
},
"example": [
0,
1
]
}
}
},
Expand Down
18 changes: 10 additions & 8 deletions cmd/freezer-refrigerant/api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,16 @@
"type": "boolean",
"example": true
},
"skipKYCStep": {
"description": "Specify this if you want to skip a specific KYC step before starting a new mining session or extending an existing one.\nSome KYC steps are not skippable.",
"allOf": [
{
"$ref": "#/definitions/users.KYCStep"
}
],
"example": 0
"skipKYCSteps": {
"description": "Specify this if you want to skip one or more specific KYC steps before starting a new mining session or extending an existing one.\nSome KYC steps are not skippable.",
"type": "array",
"items": {
"$ref": "#/definitions/users.KYCStep"
},
"example": [
0,
1
]
}
}
},
Expand Down
13 changes: 8 additions & 5 deletions cmd/freezer-refrigerant/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ definitions:
`true` recovers all the lost balance, `false` deletes it forever, `null/undefined` does nothing. Default is `null/undefined`.
example: true
type: boolean
skipKYCStep:
allOf:
- $ref: '#/definitions/users.KYCStep'
skipKYCSteps:
description: |-
Specify this if you want to skip a specific KYC step before starting a new mining session or extending an existing one.
Specify this if you want to skip one or more specific KYC steps before starting a new mining session or extending an existing one.
Some KYC steps are not skippable.
example: 0
example:
- 0
- 1
items:
$ref: '#/definitions/users.KYCStep'
type: array
type: object
main.StartOrUpdatePreStakingRequestBody:
properties:
Expand Down
12 changes: 6 additions & 6 deletions cmd/freezer-refrigerant/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ type (
StartNewMiningSessionRequestBody struct {
// Specify this if you want to resurrect the user.
// `true` recovers all the lost balance, `false` deletes it forever, `null/undefined` does nothing. Default is `null/undefined`.
Resurrect *bool `json:"resurrect" example:"true"`
// Specify this if you want to skip a specific KYC step before starting a new mining session or extending an existing one.
Resurrect *bool `json:"resurrect" example:"true"`
UserID string `uri:"userId" swaggerignore:"true" required:"true" example:"did:ethr:0x4B73C58370AEfcEf86A6021afCDe5673511376B2"`
XClientType string `form:"x_client_type" swaggerignore:"true" required:"false" example:"web"`
// Specify this if you want to skip one or more specific KYC steps before starting a new mining session or extending an existing one.
// Some KYC steps are not skippable.
SkipKYCStep *users.KYCStep `json:"skipKYCStep" example:"0"`
UserID string `uri:"userId" swaggerignore:"true" required:"true" example:"did:ethr:0x4B73C58370AEfcEf86A6021afCDe5673511376B2"`
XClientType string `form:"x_client_type" swaggerignore:"true" required:"false" example:"web"`
SkipKYCSteps []users.KYCStep `json:"skipKYCSteps" example:"0,1"`
}
ClaimExtraBonusRequestBody struct {
UserID string `uri:"userId" swaggerignore:"true" required:"true" example:"did:ethr:0x4B73C58370AEfcEf86A6021afCDe5673511376B2"`
Expand All @@ -44,7 +44,7 @@ const (
miningInProgressErrorCode = "MINING_IN_PROGRESS"
raceConditionErrorCode = "RACE_CONDITION"
resurrectionDecisionRequiredErrorCode = "RESURRECTION_DECISION_REQUIRED"
kycStepRequiredErrorCode = "KYC_STEP_REQUIRED"
kycStepsRequiredErrorCode = "KYC_STEPS_REQUIRED"
miningDisabledErrorCode = "MINING_DISABLED"
noExtraBonusAvailableErrorCode = "NO_EXTRA_BONUS_AVAILABLE"
extraBonusAlreadyClaimedErrorCode = "EXTRA_BONUS_ALREADY_CLAIMED"
Expand Down
4 changes: 2 additions & 2 deletions cmd/freezer-refrigerant/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (s *service) StartNewMiningSession( //nolint:gocritic // False negative.
) (*server.Response[tokenomics.MiningSummary], *server.Response[server.ErrorResponse]) {
ms := &tokenomics.MiningSummary{MiningSession: &tokenomics.MiningSession{UserID: &req.Data.UserID}}
enhancedCtx := tokenomics.ContextWithClientType(contextWithHashCode(ctx, req), req.Data.XClientType)
if err := s.tokenomicsProcessor.StartNewMiningSession(enhancedCtx, ms, req.Data.Resurrect, req.Data.SkipKYCStep); err != nil {
if err := s.tokenomicsProcessor.StartNewMiningSession(enhancedCtx, ms, req.Data.Resurrect, req.Data.SkipKYCSteps); err != nil {
err = errors.Wrapf(err, "failed to start a new mining session for userID:%v, data:%#v", req.Data.UserID, req.Data)
switch {
case errors.Is(err, tokenomics.ErrNegativeMiningProgressDecisionRequired):
Expand All @@ -58,7 +58,7 @@ func (s *service) StartNewMiningSession( //nolint:gocritic // False negative.
fallthrough
case errors.Is(err, tokenomics.ErrKYCRequired):
if tErr := terror.As(err); tErr != nil {
return nil, server.Conflict(err, kycStepRequiredErrorCode, tErr.Data)
return nil, server.Conflict(err, kycStepsRequiredErrorCode, tErr.Data)
}

fallthrough
Expand Down
12 changes: 6 additions & 6 deletions miner/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,21 @@ type (
}

backupUserUpdated struct {
model.DeserializedBackupUsersKey
model.BalancesBackupUsedAtField
model.UserIDField
model.BalanceT1Field
model.BalanceT2Field
model.SlashingRateT1Field
model.SlashingRateT2Field
model.ActiveT1ReferralsField
model.ActiveT2ReferralsField
model.FirstRecalculatedBalanceT1Field
model.FirstRecalculatedBalanceT2Field
model.FirstRecalculatedActiveT1ReferralsField
model.FirstRecalculatedActiveT2ReferralsField
model.FirstRecalculatedSlashingRateT1Field
model.FirstRecalculatedSlashingRateT2Field
model.BalancesBackupUsedAtField
model.DeserializedBackupUsersKey
model.ActiveT1ReferralsField
model.ActiveT2ReferralsField
model.FirstRecalculatedActiveT1ReferralsField
model.FirstRecalculatedActiveT2ReferralsField
}

referral struct {
Expand Down
2 changes: 1 addition & 1 deletion miner/recalculate_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type (
ReferralType string
}
pgUserCreated struct {
ID string
CreatedAt *time.Time
ID string
}

splittedAdoptionByRange struct {
Expand Down
16 changes: 8 additions & 8 deletions tokenomics/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var (
ErrRelationNotFound = errors.New("relationship not found")
ErrDuplicate = errors.New("duplicate")
ErrNegativeMiningProgressDecisionRequired = errors.New("you have negative mining progress, please decide what to do with it")
ErrKYCRequired = errors.New("user needs to complete a kyc step or skip it(if allowed)")
ErrKYCRequired = errors.New("user needs to complete one or more kyc steps or skip any of them(if allowed)")
ErrMiningDisabled = errors.New("mining is disabled")
ErrRaceCondition = errors.New("race condition")
ErrGlobalRankHidden = errors.New("global rank is hidden")
Expand Down Expand Up @@ -188,7 +188,7 @@ type (
GetAdoptionSummary(context.Context) (*AdoptionSummary, error)
}
WriteRepository interface {
StartNewMiningSession(ctx context.Context, ms *MiningSummary, rollbackNegativeMiningProgress *bool, skipKYCStep *users.KYCStep) error
StartNewMiningSession(ctx context.Context, ms *MiningSummary, rollbackNegativeMiningProgress *bool, skipKYCSteps []users.KYCStep) error
ClaimExtraBonus(ctx context.Context, ebs *ExtraBonusSummary) error
StartOrUpdatePreStaking(context.Context, *PreStakingSummary) error
}
Expand Down Expand Up @@ -265,8 +265,12 @@ type (
}

Config struct {
disableAdvancedTeam *atomic.Pointer[[]string]
kycConfigJSON *atomic.Pointer[kycConfigJSON]
disableAdvancedTeam *atomic.Pointer[[]string]
kycConfigJSON *atomic.Pointer[kycConfigJSON]
KYC struct {
ConfigJSONURL string `yaml:"config-json-url" mapstructure:"config-json-url"`
LivenessDelay stdlibtime.Duration `yaml:"liveness-delay" mapstructure:"liveness-delay"`
} `yaml:"kyc" mapstructure:"kyc"`
AdoptionMilestoneSwitch struct {
ActiveUserMilestones []struct {
Users uint64 `yaml:"users"`
Expand Down Expand Up @@ -301,9 +305,5 @@ type (
Parent stdlibtime.Duration `yaml:"parent"`
Child stdlibtime.Duration `yaml:"child"`
} `yaml:"globalAggregationInterval"`
KYC struct {
ConfigJSONURL string `yaml:"config-json-url" mapstructure:"config-json-url"`
LivenessDelay stdlibtime.Duration `yaml:"liveness-delay" mapstructure:"liveness-delay"`
} `yaml:"kyc" mapstructure:"kyc"`
}
)
36 changes: 27 additions & 9 deletions tokenomics/kyc.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ func (r *repository) syncKYCConfigJSON(ctx context.Context) error {
}
}

func (r *repository) validateKYC(ctx context.Context, state *getCurrentMiningSession, skipKYCStep *users.KYCStep) error { //nolint:funlen // .
if skipKYCStep != nil {
if *skipKYCStep == users.FacialRecognitionKYCStep || *skipKYCStep == users.LivenessDetectionKYCStep {
return errors.Errorf("you can't skip kycStep:%v", *skipKYCStep)
func (r *repository) validateKYC(ctx context.Context, state *getCurrentMiningSession, skipKYCSteps []users.KYCStep, doFallbackRecursion bool) error { //nolint:funlen,lll // .
for _, skipKYCStep := range skipKYCSteps {
if skipKYCStep == users.FacialRecognitionKYCStep || skipKYCStep == users.LivenessDetectionKYCStep {
return errors.Errorf("you can't skip kycStep:%v", skipKYCStep)
}
if *skipKYCStep == users.NoneKYCStep { // TODO implement this properly. This is used for mocking atm.
if skipKYCStep == users.NoneKYCStep { // TODO implement this properly. This is used for mocking atm.
if rand.Intn(2) == 0 {
return terror.New(ErrKYCRequired, map[string]any{
"kycStep": []users.KYCStep{users.Social1KYCStep, users.QuizKYCStep, users.QuizKYCStep, users.Social2KYCStep}[rand.Intn(4)],
"kycSteps": []users.KYCStep{[]users.KYCStep{users.Social1KYCStep, users.QuizKYCStep, users.QuizKYCStep, users.Social2KYCStep}[rand.Intn(4)]},
})
}
}
Expand All @@ -107,22 +107,30 @@ func (r *repository) validateKYC(ctx context.Context, state *getCurrentMiningSes
switch state.KYCStepPassed {
case users.NoneKYCStep:
if !state.MiningSessionSoloLastStartedAt.IsNil() && r.isKYCEnabled(ctx, users.FacialRecognitionKYCStep) {
if doFallbackRecursion {
if err := r.overrideKYCStateWithEskimoKYCState(ctx, &state.KYCState); err != nil {
return errors.Wrapf(err, "failed to overrideKYCStateWithEskimoKYCState for %#v", state)
}

return r.validateKYC(ctx, state, skipKYCSteps, false)
}

return terror.New(ErrKYCRequired, map[string]any{
"kycStep": users.FacialRecognitionKYCStep,
"kycSteps": []users.KYCStep{users.FacialRecognitionKYCStep, users.LivenessDetectionKYCStep},
})
}
case users.FacialRecognitionKYCStep:
if r.isKYCEnabled(ctx, users.LivenessDetectionKYCStep) {
return terror.New(ErrKYCRequired, map[string]any{
"kycStep": users.LivenessDetectionKYCStep,
"kycSteps": []users.KYCStep{users.LivenessDetectionKYCStep},
})
}
default:
isAfterDelay := time.Now().Sub(*(*state.KYCStepsLastUpdatedAt)[users.LivenessDetectionKYCStep-1].Time) >= r.cfg.KYC.LivenessDelay
isReservedForToday := int64((time.Now().Sub(*r.livenessLoadDistributionStartDate.Time)%r.cfg.KYC.LivenessDelay)/r.cfg.MiningSessionDuration.Max) == state.ID%int64(r.cfg.KYC.LivenessDelay/r.cfg.MiningSessionDuration.Max) //nolint:lll // .
if (isAfterDelay || isReservedForToday) && r.isKYCEnabled(ctx, users.LivenessDetectionKYCStep) {
return terror.New(ErrKYCRequired, map[string]any{
"kycStep": users.LivenessDetectionKYCStep,
"kycSteps": []users.KYCStep{users.LivenessDetectionKYCStep},
})
}
}
Expand All @@ -147,6 +155,16 @@ func (r *repository) isKYCEnabled(ctx context.Context, _ users.KYCStep) bool {
return true
}

/*
Because existing users have empty KYCState in dragonfly cuz usersTableSource might not have updated it yet.
So we need to query Eskimo for the valid kyc state of the user to be sure.
*/
func (r *repository) overrideKYCStateWithEskimoKYCState(ctx context.Context, state *KYCState) error {
// TODO impl this or remove it if not needed.

return nil
}

func mustGetLivenessLoadDistributionStartDate(ctx context.Context, db storage.DB) (livenessLoadDistributionStartDate *time.Time) {
livenessLoadDistributionStartDateString, err := db.Get(ctx, "liveness_load_distribution_start_date").Result()
if err != nil && errors.Is(err, redis.Nil) {
Expand Down
17 changes: 10 additions & 7 deletions tokenomics/mining_sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ type (
model.MiningSessionSoloPreviouslyEndedAtField
model.DeserializedUsersKey
}
KYCState struct {
model.KYCStepsCreatedAtField
model.KYCStepsLastUpdatedAtField
model.KYCStepPassedField
model.KYCStepBlockedField
}
getCurrentMiningSession struct {
KYCState
StartOrExtendMiningSession
model.UserIDField
model.SlashingRateSoloField
Expand All @@ -41,15 +48,11 @@ type (
model.IDTMinus1Field
model.PreStakingAllocationField
model.PreStakingBonusField
model.KYCStepPassedField
model.KYCStepBlockedField
model.KYCStepsCreatedAtField
model.KYCStepsLastUpdatedAtField
}
)

func (r *repository) StartNewMiningSession( //nolint:funlen,gocognit // A lot of handling.
ctx context.Context, ms *MiningSummary, rollbackNegativeMiningProgress *bool, skipKYCStep *users.KYCStep,
ctx context.Context, ms *MiningSummary, rollbackNegativeMiningProgress *bool, skipKYCSteps []users.KYCStep,
) error {
userID := *ms.MiningSession.UserID
id, err := GetOrInitInternalID(ctx, r.db, userID)
Expand All @@ -75,7 +78,7 @@ func (r *repository) StartNewMiningSession( //nolint:funlen,gocognit // A lot of
if err != nil {
return err
}
if err = r.validateKYC(ctx, old[0], skipKYCStep); err != nil {
if err = r.validateKYC(ctx, old[0], skipKYCSteps, true); err != nil {
return err
}
if err = r.updateTMinus1(ctx, id, old[0].IDT0, old[0].IDTMinus1); err != nil {
Expand Down Expand Up @@ -287,8 +290,8 @@ func (s *miningSessionsTableSource) incrementActiveReferralCountForT0AndTMinus1(
return errors.Wrapf(err, "failed to getOrInitInternalID for userID:%v", *ms.UserID)
}
backupUsr, err := storage.Get[struct {
model.DeserializedBackupUsersKey
model.UserIDField
model.DeserializedBackupUsersKey
}](ctx, s.db, model.SerializedBackupUsersKey(id))
if err != nil {
return errors.Wrapf(err, "failed to get backupUser for id:%v, userID:%v", id, *ms.UserID)
Expand Down
Loading

0 comments on commit 771fe0c

Please sign in to comment.