User Storage

Describes how GoodDAPP uses Gun p2p database, to store user owned data.

Source

https://github.com/GoodDollar/GoodDAPP/blob/master/src/lib/gundb/UserStorage.js

GunDB

We chose Gun since it's a javascript based, decentralized (p2p) with built in encryption database. Check out their website for more info

Usage

You can import the UserStorage instance anywhere in the code by doing

import userStorage from '/lib/gundb/UserStorage'
//you can make sure its initialized by waiting on 'ready'
await userStorage.ready

Initialization

The constructor calls init(), which logs in the user into Gun. The user's password+pass are generated deterministically by signing a message with one of his HD wallet private keys.

source

Profile

The profile holds information of the user (signed with Gun SEA, so only he can modify it) such as:

  • Name

  • Email

  • Mobile

  • Wallet Address

  • Username

Structure

Each profile field is an object of type:

export type ProfileField = {
value: EncryptedField,
display: string,
privacy: FieldPrivacy
}
  • value - is the SEA encrypted value, so only the user can read it.

  • display - is the string displayed on the DAPP to other users

  • privacy - is the privacy level of the field (masked, public, private)

Fields such email and mobile can be set to be public, private or masked, this is in order to let the user control what information he wishes to disclose. If they are public then users will be able to send funds to the user directly by simply typing his mobile, email or username in the app.

Index

We keep the users' profiles indexed by email, mobile (in case they are public), wallet address and username. This enables to connect blockchain transactions to user profiles. Specifically it is used in the user feed and in the "Send" flow to enable directly sending GoodDollars by mobile, email and username.

https://github.com/GoodDollar/GoodDAPP/blob/472b22a24dafac154409c2579dbbfcf4cf4e9922/src/lib/gundb/UserStorage.js#L504-L544
/**
* Generates index by field if privacy is public, or empty index if it's not public
*
* @param {string} field - Profile attribute
* @param {string} value - Profile attribute value
* @param {string} privacy - (private | public | masked)
* @returns Gun result promise after index is generated
* @todo This is world writable so theoritically a malicious user could delete the indexes
* need to develop for gundb immutable keys to non first user
*/
async indexProfileField(field: string, value: string, privacy: FieldPrivacy): Promise<ACK> {
if (!UserStorage.indexableFields[field]) return Promise.resolve({ err: 'Not indexable field', ok: 0 })
const cleanValue = UserStorage.cleanFieldForIndex(field, value)
if (!cleanValue) return Promise.resolve({ err: 'Indexable field cannot be null or empty', ok: 0 })
const indexNode = gun.get(`users/by${field}`).get(cleanValue)
logger.debug('indexProfileField', { field, cleanValue, value, privacy, indexNode })

An issue with the index is that currently any user can overwrite any entry in the index, since nodes in GunDB are writable by everyone. We are working on an extension to GunDB to create append only nodes so an index key, once set by a user can not be changed by anyone else besides him

examples

//returns johndoe@example.com
let value:string = await userStorage.getProfileFieldValue('email')
//the gun way
let field:ProfileField = await userStorage.profile.get('email').then()

Feed

The feed holds all the blockchain transactions the user did but also other system and messaging events.

Feed Indexes

We keep 3 indexes for easy access and display purposes:

Index

Purpose

Storage

Structure

By ID

Fast access to event details

Each events is encrypted

gun.user().get('feed'). get('byid').get(<eventId>)

FeedEvent

By Date(daily)

Display sorted by time to user with a reasonable paging scheme

gun.user().get('feed') .get(<date granularity day>)

Array<[<datetime>,<eventId>]

Events count by date

Helper for pager to fetch next X events

gun.user().get('feed') .get('index').get(<date granularity day>)

Number

Sorted events count by date

GunDB is based on objects so ordering isn't possible. We keep the 'Events count by date' as an array sorted by date.

this.feedIndex

Array<[<day>,<Number>]

The in memory index is updated on every change to 'index' by add a gundb listener in the method initFeed: https://github.com/GoodDollar/GoodDAPP/blob/472b22a24dafac154409c2579dbbfcf4cf4e9922/src/lib/gundb/UserStorage.js#L339

Good first Issues