storj/docs/blueprints/referral-manager-v1.md
Yingrong Zhao d29946d3d7
docs/blueprints: referral manager v1 (#3038)
* archive old referral offer doc

* init referral manager

* add Referral Manager CLI definition

* add draft implementation steps

* use existing registration_token table for storing invitation tokens

* fix numbering

* add open question about auth

* wip pseudocode

* add user interface design

* separate token generation step from getting users step

* change button to be a referral tab on the UI

* simplify some sentences. clarified CLI referral manager interaction

* add prevent two processes running at the same time

* add cli code

* clear users when CLI exites earlier or referral manager finishes sending tokens to satellites

* use transaction for saving tokens on satellite so we can roll back if error occurs

* add auth token and GetSatelliteURLs

* add schema for satellites table

* update to only have satelite to talk to referral manager

* add definition for eligible users and owner's satellite

* fix indentation

* add saving user info during redeem pseudocode

* wip

* address reviews

* add unredeemed token in users table

* address comments
2019-10-07 09:38:05 -04:00

8.9 KiB

Referral Manager V1: Cross-Satellite Referral Link

Abstract

This document describes how to handle user referrals across satellites.

Background

The previous referral program only works per satellite. This means a user on us-east-1 satellite can only refer people to join us-east-1.

We want referrals to be valid on all Tardigrade satellites. This new design will allow the user to select any satellite, regardless of the satellite the referral was generated for.

Non-Goals

A registration page where a user can select their preferred Satellite during registration.

Design

Definitions

Referral Manager
A standalone process running separately from satellites. Only Tardigrade-branded satellites should be able to talk to it.
Referral Manager CLI
A CLI that will allow operators to invoke referral link generation.
Invitation Token
A random number generated by Referral Manager for each user in the referral program.
Referral Link
A one-time-use URL that contains a unique invitation token.
Eligible Users
Users who have fewer than the desired number of unredeemed referral links.
Owner's Satellite
The satellite that the owner of a particular token belongs to.
Redeemed Satellite
The satellite where the new user redeeming a token has registered on.

How it will work

User Interface

  1. Users will have a referral tab on the UI. When clicked, it will trigger the satellite to send a request to retrieve referral links from the Referral Manager:
    • The Referral Manager will attempt to generate and/or fetch unredeemed tokens in the tokens table for this user ID and satellite.
      • If the user ID doesn't exist in the Referral Manager's users table, the Referral Manager will add an entry for this user ID and satellite in the users table and return an empty response to the satellite.
      • If the users table contains a value larger than 0 for new_tokens for this user ID, the Referral Manager will generate that number of new tokens, add the new unredeemed tokens to the tokens table, and set the value of new_tokens for that user to 0 in the users table.
      • If there are unredeemed tokens in the tokens table, the Referral Manager will respond to the satellite's request with the tokens after generating new tokens. Otherwise, it will return an empty response.
    • The satellite receives the response from Referral Manager.
      • If the response payload contains tokens, the satellite will display them in the UI.
      • If it's an empty response payload, the satellite will display a message No available referral links. Try again later.

Referral Manager CLI

  1. referral-manager start --tokens-per-user=3 --max-unredeemed-tokens-per-user=1 <satelliteURL1> <satelliteURL2> ... initiates the invitation token generation process. The --dry-run flag can optionally be added to run the process without actually generating tokens. The dry run allows for estimating how many new tokens would be created by the command.
  2. The Referral Manager queries both users and tokens tables for all users where new_tokens + unredeemed_tokens < flags.tokens_per_user and where the satellite matches one of the CLI-provided satellite URLs. These are the "eligible users".
  3. The Referral Manager will count the number of eligible users and calculate the number of tokens needed to bring each user up to tokens_per_user. If --dry-run is set, these values will be returned to the CLI for output and the process will end. Otherwise, the users table will be updated to set new_tokens to tokens_per_user - unredeemed_tokens for each eligible user, and the values will be returned to the CLI for output.

Referral Link Redemption

  1. User Alice tries to register a new account through a referral link, which triggers the redeemed satellite to verify invitation token using Referral manager. The satellite preemptively generates a new user ID and sends this ID along with the token to the Referral Manager.
  2. Referral Manager checks the status of the token:
    • If the token exists in tokens table:
      • If it is not redeemed, Referral Manager sends back a success response to the redeemed satellite and marks the token as redeemed by the new user ID in the Referral Manager's tokens table. It also adds an entry for the new user ID in the users table.
      • If the token is already redeemed, the Referral Manager sends back an invalid token response to the redeemed satellite.
    • If the token doesn't exist in tokens table, Referral Manager sends back an invalid token response to the redeemed satellite.
  3. Redeemed satellite receives the response, which:
    • If it is a success, the satellite will proceed with the account creation
    • If it is an invalid token, the satellite will display a proper message in the UI.

Rationale

We will create an admin port that's only listening on localhost so CLI could be only used by Referral Manager operators.

We could also set the admin port to only accept requests coming from storj vpn.

Implementation

  • Create a private repository for Referral Manager.
  • Create tokens and users table in Referral Manager database.
  • Create an admin endpoint on Referral Manager.
    • Implementing a method GenerateTokens for updating the users table on Referral Manager to contain new tokens for eligible users.
  • Implementing an endpoint GetTokens for requesting unredeemed tokens from Referral Manager.
  • Implementing an endpoint Redeem on Referral Manager for verifying invitation tokens and storing newly created user ID into users table.
  • Replace existing registration token logic.

Pseudocode

The Token struct represents both a Go struct (on satellite and Referral Manager) as well as the schema for the tokens table on the Referral Manager

type Token struct {
    Secret [32]byte
    OwnerID uuid.UUID
    RedeemedID uuid.UUID
    OwnerSatelliteURL string
    RedeemedSatelliteURL string
    // we should store it as an integer in the db and cast it into enum when retrieve it
    Status enum
}

Statuses: unsent (owner's satellite doesn't know about it yet), unredeemed (owner's satellite knows about it but it has not been used), redeemed (someone has used this referral link to register alreadys)

The User struct represent both a Go struct as well as the schema for the users table on Referral Manager.

type User struct {
    ID uuid.UUID
    satelliteURL string
    NewTokens int
    UnredeemedTokens int
}

Endpoints for satellites:

// GetTokens retrieves a list of unredeemed tokens for a user.
GetTokens(userID uuid.UUID, satelliteURL string) []tokens {
    tx := db.Tx
    user, err := tx.GetUser(userID, satelliteURL)
    if err == UserNotFound {
        tx.CreateUser(userID, satelliteURL)
        return nil
    }
    if user.NewTokens > 0 {
        for i:=0; i<user.NewTokens; i++ {
            newToken := Token{data: generateRandomToken(), user: user.ID, satellite: user.satelliteURL}
            db.CreateToken(newToken)
        }
        tx.UpdateUserNewTokens(userID, satelliteURL, 0)
    }
    tokens := db.GetTokensByUserIDAndSatelliteURL(userID, satelliteURL)

    return tokens
}

// Redeem marks a token as redeemed and stores user info into database
func Redeem(ctx, token, userID, satelliteURL) error {
    tx := db.Tx
	// only update the status if the status of a token is unredeemed
	tokenStatus, err := tx.RedeemToken(ctx, token, userID)
	if err != nil {
		return err
	}

    // decrease unredeemed token count in users table by 1
    err := tx.UpdateUserInfo(ctx, user)
    if err != nil {
        return err
    }

    // save user info into users table
    err := tx.CreateUser(ctx, userID, satelliteURL)
    if err != nil {
        // log the error, but we shouldn't return an error to stop user registration process if we don't get their info here
        log(err)
        return
    }
}

Endpoints for CLI:


// GenerateTokens generates tokens, saves those in the referral manager db
GenerateTokens(tokensPerUser int, satelliteURLs []string, dryRun bool) (tokenCount, eligibleUserCount int, error) {
    eligibleUsers := db.GetEligibleUsers(satelliteURLs, tokensPerUser)
    if eligibleUsers == nil {
        return nil, Error.New("No users to generate tokens for.")
    }
    newTokenCount := 0
    eligibleUserCount := len(eligibleUsers)
    for _, user := range eligibleUsers {
        for i:=(user.NewTokens + user.UnredeemedTokens); i<tokensPerUser; i++ {
            newTokenCount++
        }
    }

    if !dryRun {
        db.UpdateUserNewTokensBatch(eligibleUsers, tokensPerUser)
    }

    return newTokenCount, eligibleUserCount, nil
}

CLI code:

startCmd() {
    tokenCount, userCount := referralManager.GenerateTokens(tokensPerUser, args.SatelliteURLs, flags.dryRun)

    fmt.Printf("Successfully created %d tokens for %d users.\n", tokenCount, userCount)
    if flags.dryRun {
        fmt.Println("This was a dry run. Run again without the --dry-run flag to actually generate tokens.")
    }
}

Wrapup

Open issues