add delegates model and syncing strategy

This commit is contained in:
Barry Gitarts 2019-03-14 18:11:16 -04:00 committed by Barry G
parent 6a1eb882dd
commit f091a72f61
18 changed files with 233 additions and 39 deletions

75
app/actions/delegates.js Normal file
View File

@ -0,0 +1,75 @@
import database from '../db'
import { Q } from '@nozbe/watermelondb'
import { when } from 'ramda'
import { getPledgesWithDelegates } from './pledges'
import { getProfilesById } from './profiles'
const delegatesCollection = database.collections.get('delegates')
export const getDelegateProfiles = async pledges => {
const ids = []
pledges.forEach(pledge => {
const { delegates } = pledge
delegates.forEach(d => ids.push(d.idDelegate))
})
return getProfilesById(ids)
}
const createDelegate = (newDelegate, delegateInfo, pledge, profile, idx) => {
newDelegate.profile.set(profile)
newDelegate.pledge.set(pledge)
newDelegate.idPledge = pledge.idPledge
newDelegate.delegateIndex = idx
}
const delegateRecordExists = (profile, pledge, idx, existing) => {
const record = existing.find(delegate => {
const { delegateIndex } = delegate
if (
profile.id == delegate.profile.id &&
pledge.idPledge == delegate.idPledge &&
idx == delegateIndex
) return true
return false
})
return record
}
const batchAddDelegates = async (pledges, profiles, existing) => {
const batch = []
pledges.forEach(pledge => {
const { delegates } = pledge
delegates.forEach((delegateInfo, idx) => {
const profile = profiles.find(p => p.idProfile == delegateInfo.idDelegate)
const exists = delegateRecordExists(profile, pledge, idx+1, existing)
if (!exists) {
batch.push(
delegatesCollection.prepareCreate(
newDelegate => createDelegate(newDelegate, delegateInfo, pledge, profile, idx+1)
)
)
}
})
})
return database.action(async () => await database.batch(...batch))
}
export const updateDelegates = async () => {
const pledges = await getPledgesWithDelegates()
const profiles = await getDelegateProfiles(pledges)
const delegates = await getExistingDelegates()
batchAddDelegates(pledges, profiles, delegates)
}
export const getExistingDelegates = async () => {
const delegates = await delegatesCollection.query().fetch()
return delegates
}
export const delegateExists = async (profileId, idPledge, idx) => {
const delegates = await delegatesCollection.query(
Q.where('profile_id', profileId),
Q.where('id_pledge', idPledge),
Q.where('delegate_index', idx)
).fetch()
return delegates
}

View File

@ -5,9 +5,9 @@ import { getPledges, getAllPledges } from '../utils/pledges'
import { getProfilesById } from './profiles'
const createPledge = (pledge, data, profiles) => {
const { id, owner, amount, blockNumber, token, commitTime, nDelegates, pledgeState, intendedProject } = data
const { id, owner, amount, blockNumber, token, commitTime, nDelegates, pledgeState, intendedProject, delegates } = data
const profile = profiles.find(p => p.idProfile == owner)
pledge.pledgeId = Number(id)
pledge.idPledge = Number(id)
pledge.owner = Number(owner)
pledge.amount = amount
pledge.token = token
@ -16,6 +16,7 @@ const createPledge = (pledge, data, profiles) => {
pledge.pledgeState = Number(pledgeState)
pledge.intendedProject = Number(intendedProject)
pledge.blockNumber = Number(blockNumber)
pledge.delegates = delegates
pledge.profile.set(profile)
}
@ -36,17 +37,17 @@ export const batchAddPledges = async (pledges, profiles = []) => {
}
const getLastPledge = pledges => {
const pledgeId = pledges.length
? pledges.sort((a,b) => b.pledgeId - a.pledgeId)[0].pledgeId
const idPledge = pledges.length
? pledges.sort((a,b) => b.idPledge - a.idPledge)[0].idPledge
: 0
return pledgeId
return idPledge
}
export const getAndAddPledges = async () => {
const pledges = await getLocalPledges()
const pledgeId = getLastPledge(pledges)
const newPledges = await getAllPledges(pledgeId + 1)
const pledgeIds = newPledges.map(p => p.owner)
const profiles = await getProfilesById(pledgeIds)
const idPledge = getLastPledge(pledges)
const newPledges = await getAllPledges(idPledge + 1)
const idPledges = newPledges.map(p => p.owner)
const profiles = await getProfilesById(idPledges)
batchAddPledges(newPledges, profiles)
}
@ -55,7 +56,7 @@ export const updateStalePledges = async () => {
const stalePledges = await getStalePledges()
const updatedPledges = await getPledges(stalePledges)
const batch = stalePledges.map(p => {
const updated = updatedPledges[p.pledgeId - 1]
const updated = updatedPledges[p.idPledge - 1]
return p.prepareUpdate(p => {
const { amount, nDelegates, pledgeState, blockNumber } = updated
p.amount = amount
@ -87,3 +88,10 @@ export const getPledgeById = async id => {
).fetch()
return event
}
export const getPledgesWithDelegates = async () => {
const event = await pledgesCollection.query(
Q.where('n_delegates', Q.gt(0))
).fetch()
return event
}

View File

@ -76,7 +76,7 @@ class PledgesTable extends Component {
<div>
<MaterialTable
columns={[
{ title: 'Pledge Id', field: 'pledgeId', type: 'numeric' },
{ title: 'Pledge Id', field: 'idPledge', type: 'numeric' },
{ title: 'Owner', field: 'owner' },
{ title: 'Amount Funded', field: 'amount', type: 'numeric' },
{ title: 'Token', field: 'token' },

View File

@ -17,9 +17,9 @@ const TransferDialog = ({ row, handleClose }) => (
<Formik
initialValues={{}}
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { pledgeId, pledge } = row
const { idPledge, pledge } = row
const { idSender, amount, idReceiver } = values
const args = [idSender, pledgeId, toWei(amount.toString()), idReceiver]
const args = [idSender, idPledge, toWei(amount.toString()), idReceiver]
const toSend = transfer(...args)
const estimatedGas = await toSend.estimateGas()
@ -68,7 +68,7 @@ const TransferDialog = ({ row, handleClose }) => (
<DialogTitle id="form-dialog-title">Transfer Funds</DialogTitle>
<DialogContent>
<DialogContentText>
{`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.pledgeId} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
{`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.idPledge} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
</DialogContentText>
<TextField
autoFocus

View File

@ -31,11 +31,11 @@ const pledgesChartData = pledges => {
const labels = []
const backgroundColor = []
pledges.forEach((pledge, idx) => {
const { pledgeId, amount, token } = pledge
const { idPledge, amount, token } = pledge
const converted = toEther(amount)
data.push(converted)
labels.push(
`pledge ${pledgeId} - ${getTokenLabel(token)}`
`pledge ${idPledge} - ${getTokenLabel(token)}`
)
backgroundColor.push(getColor('Dark2-8', idx))
})

View File

@ -1,5 +1,4 @@
import React from 'react'
import web3 from 'Embark/web3'
import { Formik } from 'formik'
import withObservables from '@nozbe/with-observables'
import { Q } from '@nozbe/watermelondb'
@ -89,10 +88,10 @@ const SubmissionSection = ({ classes, profiles }) => (
</Formik>
)
function BackProject({classes, match, profile, projectAddedEvents}) {
function BackProject({classes, match, profile, projectAddedEvents, delegateAddedEvents}) {
const projectId = match.params.id
const { projectAge, projectAssets, manifest, delegateProfiles } = useProjectData(projectId, profile, projectAddedEvents)
console.log({delegateProfiles})
console.log({delegateAddedEvents})
return (
<div className={classes.root}>
<Title className={classes.title} manifest={manifest} />
@ -108,5 +107,8 @@ export default withDatabase(withObservables([], ({ database, match }) => ({
).observe(),
projectAddedEvents: database.collections.get('lp_events').query(
Q.where('event', 'ProjectAdded')
).observe(),
delegateAddedEvents: database.collections.get('lp_events').query(
Q.where('event', 'DelegateAdded')
).observe()
}))(StyledProject))

View File

@ -8,6 +8,8 @@ import { databaseExists } from '../../utils/db'
import { FundingContext } from '../../context'
import { getDelegateProfiles } from '../../actions/profiles'
console.log({LiquidPledging})
async function getProjectAge(id, events, setState){
const event = events.find(e => e.returnValues.idProject === id)
const { timestamp } = await web3.eth.getBlock(event.blockNumber)
@ -17,6 +19,8 @@ async function getProjectAge(id, events, setState){
async function getProjectAssets(projectId, setState){
EmbarkJS.onReady(async (err) => {
const projectInfo = await LiquidPledging.methods.getPledgeAdmin(projectId).call()
const pledgeInfo = await LiquidPledging.methods.getPledgeDelegate(5).call()
console.log({pledgeInfo})
const CID = projectInfo.url.split('/').slice(-1)[0]
console.log({CID, projectInfo, ipfs})
getFiles(CID)

View File

@ -23,9 +23,9 @@ function TransferCard({ row, handleClose, classes }) {
<Formik
initialValues={{}}
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { pledgeId, pledge } = row
const { idPledge, pledge } = row
const { idSender, amount, idReceiver } = values
const args = [idSender, pledgeId, toWei(amount.toString()), idReceiver]
const args = [idSender, idPledge, toWei(amount.toString()), idReceiver]
const toSend = transfer(...args)
const estimatedGas = await toSend.estimateGas()
@ -71,7 +71,7 @@ function TransferCard({ row, handleClose, classes }) {
<CardContent>
<Typography variant="h6" component="h2">Transfer Funds</Typography>
<Typography variant="subheading">
{`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.pledgeId} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
{`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.idPledge} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`}
</Typography>
<TextField
autoFocus

View File

@ -30,7 +30,7 @@ function Withdraw({ handleClose, classes, rowData, authorizedPayment }) {
initialValues={{}}
onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => {
const { amount } = values
const paymentId = isPaying ? authorizedPayment[0]['returnValues']['idPayment'] : rowData.pledgeId
const paymentId = isPaying ? authorizedPayment[0]['returnValues']['idPayment'] : rowData.idPledge
const args = isPaying ? [paymentId] : [paymentId, toWei(amount)]
const sendFn = isPaying ? confirmPayment : withdraw
try {
@ -68,7 +68,7 @@ function Withdraw({ handleClose, classes, rowData, authorizedPayment }) {
<Card className={classes.card} elevation={0}>
<CardContent>
<Typography variant="h6" component="h2">
{`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData.pledge.token) : ''} from Pledge ${rowData.pledgeId}`}
{`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData.pledge.token) : ''} from Pledge ${rowData.idPledge}`}
</Typography>
{!isPaying && <TextField
className={classes.amount}
@ -105,6 +105,6 @@ Withdraw.propTypes = {
const styledWithdraw = withStyles(styles)(Withdraw)
export default withDatabase(withObservables(['rowData'], ({ database, rowData }) => ({
authorizedPayment : database.collections.get('vault_events').query(
Q.where('ref', rowData.pledgeId)
Q.where('ref', rowData.idPledge)
).observe()
}))(styledWithdraw))

View File

@ -2,17 +2,17 @@ import { useState, useEffect } from 'react'
export function useRowData(rowData, handleClose) {
const [show, setShow] = useState(null)
const [rowId, setRowId] = useState(rowData.pledgeId)
const [rowId, setRowId] = useState(rowData.idPledge)
useEffect(() => {
setShow(true)
}, [])
useEffect(() => {
const { pledgeId } = rowData
const samePledge = rowId === pledgeId
const { idPledge } = rowData
const samePledge = rowId === idPledge
if (show && samePledge) close()
else setRowId(pledgeId)
else setRowId(idPledge)
}, [rowData.timeStamp])
const close = () => {

View File

@ -11,6 +11,7 @@ import { getAndAddLpEvents } from './actions/lpEvents'
import { getAndAddVaultEvents } from './actions/vaultEvents'
import { addFormattedProfiles } from './actions/profiles'
import { updateStalePledges, getAndAddPledges } from './actions/pledges'
import { updateDelegates } from './actions/delegates'
const { getNetworkType } = web3.eth.net
@ -57,6 +58,7 @@ class App extends React.Component {
await addFormattedProfiles()
await getAndAddPledges()
await updateStalePledges()
await updateDelegates()
this.setState({ loading: false })
}

View File

@ -6,6 +6,7 @@ import LpEvent from './model/lpEvents'
import VaultEvent from './model/vaultEvent'
import Profile from './model/profile'
import Pledge from './model/pledge'
import Delegate from './model/delegate'
const dbName = 'LiquidFunding'
const adapter = new LokiJSAdapter({
@ -19,7 +20,8 @@ const database = new Database({
LpEvent,
VaultEvent,
Profile,
Pledge
Pledge,
Delegate
],
actionsEnabled: true,
})

16
app/model/delegate.js Normal file
View File

@ -0,0 +1,16 @@
import { field, relation } from '@nozbe/watermelondb/decorators'
import { LiquidModel } from '../utils/models'
export default class Delegate extends LiquidModel {
static table = 'delegates'
static associations = {
profiles:{ type: 'belongs_to', key: 'profile_id' },
pledges: { type: 'belongs_to', key: 'pledge_id' }
}
@field('delegate_index') delegateIndex
@field('id_pledge') idPledge
@relation('profiles', 'profile_id') profile
@relation('pledges', 'pledge_id') pledge
}

View File

@ -1,15 +1,15 @@
import { action, field, relation } from '@nozbe/watermelondb/decorators'
import { action, field, relation, json } from '@nozbe/watermelondb/decorators'
import { Q } from '@nozbe/watermelondb'
import { LiquidModel } from '../utils/models'
const sanitizeValues = json => json
export default class Pledge extends LiquidModel {
static table = 'pledges'
static associations = {
profiles: { type: 'belongs_to', key: 'profile_id' },
}
@field('pledge_id') pledgeId
@field('id_pledge') idPledge
@field('owner_id') owner
@field('amount') amount
@field('token') token
@ -19,6 +19,7 @@ export default class Pledge extends LiquidModel {
@field('pledge_state') pledgeState
@field('block_number') blockNumber
@relation('profiles', 'profile_id') profile
@json('delegates', sanitizeValues) delegates
@action async transferTo(to, amount) {
const toPledgeQuery = await this.collections.get('pledges').query(

View File

@ -41,16 +41,26 @@ export default appSchema({
tableSchema({
name: 'pledges',
columns: [
{ name: 'pledge_id', type: 'number', isIndexed: true },
{ name: 'id_pledge', type: 'number', isIndexed: true },
{ name: 'owner_id', type: 'number', isIndexed: true },
{ name: 'amount', type: 'string' },
{ name: 'token', type: 'string' },
{ name: 'commit_time', type: 'number' },
{ name: 'n_delegates', type: 'number' },
{ name: 'n_delegates', type: 'number', isIndexed: true },
{ name: 'intended_project', type: 'number' },
{ name: 'pledge_state', type: 'number' },
{ name: 'profile_id', type: 'string', isIndexed: true },
{ name: 'block_number', type: 'number', isIndexed: true }
{ name: 'block_number', type: 'number', isIndexed: true },
{ name : 'delegates', type: 'string', isOptional: true }
]
}),
tableSchema({
name: 'delegates',
columns: [
{ name: 'profile_id', type: 'string', isIndexed: true },
{ name: 'pledge_id', type: 'string', isIndexed: true },
{ name: 'id_pledge', type: 'number', isIndexed: true },
{ name: 'delegate_index', type: 'number', isIndexed: true }
]
})
]

View File

@ -45,7 +45,7 @@ const formatAndSumDepositWithdraws = (deposits, pledges, withdraws) => {
let incomplete = false
deposits.forEach(deposit => {
const { amount, to } = deposit.returnValues
const pledge = pledges.find(p => Number(p.pledgeId) === Number(to))
const pledge = pledges.find(p => Number(p.idPledge) === Number(to))
if (!pledge) {
incomplete = true
return

View File

@ -1,13 +1,25 @@
import web3 from 'Embark/web3'
import LiquidPledging from 'Embark/contracts/LiquidPledging'
const { getPledgeAdmin, numberOfPledges, getPledge } = LiquidPledging.methods
const { getPledgeDelegate, numberOfPledges, getPledge } = LiquidPledging.methods
const getPledgeDelegates = (idPledge, numDelegates) => {
const delegates = []
const num = Number(numDelegates)
if (!num) return delegates
for (let i = 1; i <= num; i++) {
delegates.push(getPledgeDelegate(idPledge, i).call())
}
return Promise.all(delegates)
}
export const formatPledge = async (pledgePromise, idx) => {
const pledge = await pledgePromise
const blockNumber = await web3.eth.getBlockNumber()
const delegates = await getPledgeDelegates(idx+1, pledge.nDelegates)
return {
...pledge,
blockNumber,
delegates,
id: idx + 1
}
}
@ -24,7 +36,7 @@ export const getAllPledges = async (start = 1) => {
export const getPledges = async (pledges = []) => {
const updated = []
pledges.forEach(p => {
updated[p.pledgeId - 1] = getPledge(p.pledgeId).call()
updated[p.idPledge - 1] = getPledge(p.idPledge).call()
})
return Promise.all(updated.map(formatPledge))
}

62
config/storage.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = {
// default applies to all environments
default: {
enabled: true,
ipfs_bin: "ipfs",
available_providers: ["ipfs"],
upload: {
provider: "ipfs",
host: "localhost",
port: 5001
},
dappConnection: [
{
provider:"ipfs",
host: "localhost",
port: 5001,
getUrl: "http://localhost:8080/ipfs/"
}
]
// Configuration to start Swarm in the same terminal as `embark run`
/*,account: {
address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm
password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account
},
swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run`
development: {
enabled: true,
upload: {
provider: "ipfs",
host: "localhost",
port: 5001,
getUrl: "http://localhost:8080/ipfs/"
}
},
// merges with the settings in default
// used with "embark run privatenet"
privatenet: {
},
rinkeby: {
},
// merges with the settings in default
// used with "embark run testnet"
testnet: {
},
// merges with the settings in default
// used with "embark run livenet"
livenet: {
},
// you can name an environment with specific settings and then specify with
// "embark run custom_name"
//custom_name: {
//}
};