Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/balance activation #20

Merged
merged 15 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/spec/components/schemas/ActivateBalance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
allOf:
- $ref: '#/components/schemas/ActivateBalanceKey'
- type: object
x-go-is-request: true
required:
- attributes
properties:
attributes:
type: object
required:
- referred_by
properties:
referred_by:
type: string
description: Referral code from the link
example: "rCx18MZ4"
13 changes: 13 additions & 0 deletions docs/spec/components/schemas/ActivateBalanceKey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: object
required:
- id
- type
properties:
id:
type: string
description: Nullifier of the points owner
example: "0x123...abc"
pattern: '^0x[0-9a-fA-F]{64}$'
type:
type: string
enum: [ activate_balance ]
11 changes: 5 additions & 6 deletions docs/spec/components/schemas/Balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ allOf:
is_disabled:
type: boolean
description: |
Whether the user was not referred by anybody, but the balance with some
events was reserved. It happens when the user fulfills some event
before the balance creation.
Whether the user was not referred with some code. If it wasn't - balance
is disabled and very limited in functionality.
example: false
created_at:
type: integer
Expand All @@ -39,18 +38,18 @@ allOf:
example: 294
referral_codes:
type: array
description: Referral codes. Returned only for the single user.
description: Referral codes. Returned only for the single active balance.
items:
$ref: '#/components/schemas/ReferralCode'
referred_users_count:
type: integer
format: int
description: Number of invited users
description: Number of invited users. Returned only for the single active balance.
example: 13
rewarded_referred_users_count:
type: integer
format: int
description: Number of users for whom the reward was received
description: Number of users for whom the reward was received. Returned only for the single active balance.
example: 5
level:
type: integer
Expand Down
9 changes: 5 additions & 4 deletions docs/spec/components/schemas/CreateBalance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ allOf:
properties:
attributes:
type: object
required:
- referred_by
properties:
referred_by:
type: string
description: referrer code from the link
description: |
Referral code from the link. Supply it to create the active balance,
otherwise disabled balance is created, and it can be activated later.

Disabled balance is only allowed to verify passport and get.
example: "rCx18MZ4"

2 changes: 1 addition & 1 deletion docs/spec/components/schemas/VerifyPassport.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ allOf:
format: types.ZKProof
description: |
Query ZK passport verification proof.
Required for endpoint `/v2/balances/{nullifier}/verifypassport`.
Required for passport verification endpoint.
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@ post:
- Points balance
summary: Create points balance
description: |
Create an empty balance for authorized user who makes the request. Rank is included
in response.

This operation might be time-consuming, because `open` events should be added for
the new account synchronously (to display them right after the request).

Create an empty balance for authorized user who makes the request.

If balance already exists, but it is disabled (it was not referred by another user,
but has fulfilled some event), you should use PATCH balances/{nullifier} endpoint as well.
but has fulfilled some event), you should activate balance instead.
operationId: createPointsBalance
requestBody:
content:
Expand All @@ -34,10 +30,10 @@ post:
properties:
data:
$ref: '#/components/schemas/Balance'
400:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
404:
$ref: '#/components/responses/notFound'
409:
description: Balance already exists for provided nullifier
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ get:
description: |
Balance of authorized user who makes the request. Rank in leaderboard is included.
You should create new balance for the new user by making POST request.

If balance is disabled (was created without referral code and not activated),
some fields are not returned.
operationId: getPointsBalance
parameters:
- $ref: '#/components/parameters/pathNullifier'
- in: query
name: 'rank'
description: 'Specifies whether to return the rank'
description: Specifies whether to return the rank. Has no effect on disabled balance.
required: false
schema:
type: boolean
example: true
- in: query
name: 'referral_codes'
description: 'Specifies whether to return the referral codes'
description: Specifies whether to return the referral codes. Has no effect on disabled balance.
required: false
schema:
type: boolean
Expand All @@ -39,3 +42,53 @@ get:
$ref: '#/components/responses/invalidAuth'
500:
$ref: '#/components/responses/internalError'

patch:
tags:
- Points balance
summary: Activate points balance
description: |
Activate points balance for authorized user who makes the request.
Rank is included in response.

Only balances created without referral code can be activated,
check this with `is_disabled` field.

If balance's passport was verified earlier, some events will be auto-claimed.
operationId: activatePointsBalance
requestBody:
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/ActivateBalance'
responses:
200:
description: Balance activated
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/Balance'
400:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
404:
$ref: '#/components/responses/notFound'
409:
description: Balance already activated
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
500:
$ref: '#/components/responses/internalError'
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ post:
- Points balance
summary: Verify passport
description: |
Verify passport with ZKP, fulfilling the event.
Verify passport to unlock event claiming and get reward.
One passport can't be verified twice.

There are two verification flows:
1) Legacy flow is done through Query ZK-proof
2) New flow is done via JWT, received from Auth service after providing ZK-proof

Some events will be automatically claimed in case if balance is active.
operationId: verifyPassport
parameters:
- $ref: '#/components/parameters/pathNullifier'
Expand Down Expand Up @@ -42,14 +48,20 @@ post:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
403:
description: Invalid signature
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
404:
description: Balance not exists.
description: Balance not exists
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
409:
description: Passport already verified or event absent for user.
description: Passport already verified or anonymous ID exists
content:
application/vnd.api+json:
schema:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/google/jsonapi v1.0.0
github.com/iden3/go-rapidsnark/types v0.0.3
github.com/labstack/gommon v0.4.0
github.com/rarimo/geo-auth-svc v0.2.0
github.com/rarimo/geo-auth-svc v1.1.0
github.com/rarimo/saver-grpc-lib v1.0.0
github.com/rarimo/zkverifier-kit v1.1.1
github.com/rubenv/sql-migrate v1.6.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,8 @@ github.com/rarimo/cosmos-sdk v0.46.7 h1:jU2PiWzc+19SF02cXM0O0puKPeH1C6Q6t2lzJ9s1
github.com/rarimo/cosmos-sdk v0.46.7/go.mod h1:fqKqz39U5IlEFb4nbQ72951myztsDzFKKDtffYJ63nk=
github.com/rarimo/geo-auth-svc v0.2.0 h1:yQvcIBNx+Tc1jJdtpWDfyLc0HogU+okA08HEZ55wv5U=
github.com/rarimo/geo-auth-svc v0.2.0/go.mod h1:SB4bo1xHYDAsBaQGX2+FoEgD3xxqYmcgr4XTTjy4/OM=
github.com/rarimo/geo-auth-svc v1.1.0 h1:3k1tTWAjtCBsnzlMb3aB+xgsFLEPUSmB3woME+q6tfk=
github.com/rarimo/geo-auth-svc v1.1.0/go.mod h1:JrpCGdT0xtAcWIKgPhxPHf7QCW4h845BXuh6M7NdQFw=
github.com/rarimo/saver-grpc-lib v1.0.0 h1:MGUVjYg7unmodYczVsLqlqZNkT4CIgKqdo6aQtL1qdE=
github.com/rarimo/saver-grpc-lib v1.0.0/go.mod h1:DpugWK5B7Hi0bdC3MPe/9FD2zCxaRwsyykdwxtF1Zgg=
github.com/rarimo/zkverifier-kit v1.1.0-rc.1 h1:xtmrFEl7eLAE6mi7IQYOOMKFdwXC3gbe39fYQdvKVZg=
Expand Down
1 change: 1 addition & 0 deletions internal/data/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

const (
ColAmount = "amount"
ColReferredBy = "referred_by"
ColLevel = "level"
ColAnonymousID = "anonymous_id"
ColSharedHash = "shared_hash"
Expand Down
4 changes: 1 addition & 3 deletions internal/data/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ type EventsQ interface {
FilterByStatus(...EventStatus) EventsQ
FilterByType(...string) EventsQ
FilterByNotType(types ...string) EventsQ
FilterByUpdatedAtBefore(int64) EventsQ
FilterByExternalID(string) EventsQ
FilterInactiveNotClaimed(types ...string) EventsQ
// FilterByUpdatedAtBefore must be only used with SelectReopenable, because it
// depends on table alias.
FilterByUpdatedAtBefore(int64) EventsQ
}
15 changes: 7 additions & 8 deletions internal/data/pg/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewEvents(db *pgdb.DB) data.EventsQ {
updater: squirrel.Update(eventsTable),
deleter: squirrel.Delete(eventsTable),
counter: squirrel.Select("COUNT(*) AS count").From(eventsTable),
reopenable: squirrel.Select("e1.nullifier", "e1.type").Distinct().From(eventsTable + " e1"),
reopenable: squirrel.Select("nullifier", "type").Distinct().From(eventsTable + " e1"),
}
}

Expand Down Expand Up @@ -141,15 +141,14 @@ func (q *events) Count() (int, error) {
}

func (q *events) SelectReopenable() ([]data.ReopenableEvent, error) {
// including disabled balances to open events intentionally
// join balances and filter by referred_by if you need the opposite
subq := fmt.Sprintf(`NOT EXISTS (
SELECT 1 FROM %s e2
WHERE e2.nullifier = e1.nullifier
AND e2.type = e1.type
AND e2.status IN (?, ?))`, eventsTable)
stmt := q.reopenable.
Where(subq, data.EventOpen, data.EventFulfilled).
Join(balancesTable + " b ON b.nullifier = e1.nullifier").
Where("b.referred_by IS NOT NULL")
stmt := q.reopenable.Where(subq, data.EventOpen, data.EventFulfilled)

var res []data.ReopenableEvent
if err := q.db.Select(&res, stmt); err != nil {
Expand All @@ -165,13 +164,14 @@ func (q *events) SelectAbsentTypes(allTypes ...string) ([]data.ReopenableEvent,
values[i] = fmt.Sprintf("('%s')", t)
}

// including disabled balances to open events intentionally
query := fmt.Sprintf(`
WITH types(type) AS (
VALUES %s
)
SELECT u.nullifier, t.type
FROM (
SELECT nullifier FROM %s WHERE referred_by IS NOT NULL
SELECT nullifier FROM %s
) u
CROSS JOIN types t
LEFT JOIN %s e ON e.nullifier = u.nullifier AND e.type = t.type
Expand Down Expand Up @@ -223,8 +223,7 @@ func (q *events) FilterByExternalID(id string) data.EventsQ {
}

func (q *events) FilterByUpdatedAtBefore(unix int64) data.EventsQ {
q.reopenable = q.reopenable.Where(squirrel.Lt{"e1.updated_at": unix})
return q
return q.applyCondition(squirrel.Lt{"updated_at": unix})
}

func (q *events) FilterInactiveNotClaimed(types ...string) data.EventsQ {
Expand Down
Loading
Loading