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

refactor: build delegate list from memory #1134

Merged
merged 48 commits into from Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8d40c23
chore: remove unused round queries
faustbrian Oct 16, 2018
82991ea
refactor: load delegates from memory
faustbrian Oct 16, 2018
d4dd3bb
docs: update types
faustbrian Oct 16, 2018
f032d1a
refactor: update vote balance for transfers
faustbrian Oct 17, 2018
ff8cadc
fix: remove non-existent query files
faustbrian Oct 17, 2018
38e71e5
fix: compare numbers, not objects
faustbrian Oct 17, 2018
42d4b72
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 17, 2018
2158326
refactor: handle filler wallets
faustbrian Oct 17, 2018
72954ff
chore: update yarn.lock
faustbrian Oct 17, 2018
733bfd9
fix: handle voteBalance as BigNumber
faustbrian Oct 17, 2018
0e4efb0
refactor: add public key sorting for testing
faustbrian Oct 17, 2018
4ed5105
misc: add pubkey sort fixme
faustbrian Oct 17, 2018
3dfe02a
fix: change sort direction to previous (b-a vs. a-b)
faustbrian Oct 17, 2018
d34160f
refactor: log equal balances as warnings
faustbrian Oct 17, 2018
365c30d
misc: wording
faustbrian Oct 17, 2018
b92664b
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 17, 2018
5936fce
refactor: throw if not enough delegates are found
faustbrian Oct 17, 2018
732a594
refactor: handle apply/revert in __updateVoteBalance
faustbrian Oct 17, 2018
27bbfd6
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 17, 2018
8603d40
docs: wording
faustbrian Oct 17, 2018
feb0077
refactor: make buildDelegates sync
faustbrian Oct 17, 2018
922b7b0
refactor: drop __updateVoteBalance for now and instead update all votes
faustbrian Oct 17, 2018
7449a67
misc: try all delegates with equal balance
spkjp Oct 17, 2018
7b4b12f
Merge branch 'develop' into memory-delegates-2
spkjp Oct 17, 2018
f2dd03f
Merge branch 'develop' into memory-delegates-2
spkjp Oct 17, 2018
abd77f4
refactor: remove buildDelegates
spkjp Oct 17, 2018
d01df04
fix: round to number
spkjp Oct 18, 2018
d1e1875
fix: sort by voteBalance
spkjp Oct 18, 2018
60bfa48
fix: map round balance to voteBalance
spkjp Oct 18, 2018
9f2903b
misc: reduce noise
spkjp Oct 18, 2018
05f6474
misc: remove ugly workaround
spkjp Oct 18, 2018
e5deb34
refactor: map voteBalance in model
spkjp Oct 18, 2018
9f8ca22
fix: test
spkjp Oct 18, 2018
901dc66
fix: test
spkjp Oct 18, 2018
dadf49a
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
62eeaa4
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
bb0f54a
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
b80d196
refactor: enable public key sorting
faustbrian Oct 18, 2018
a80e6b8
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
6ff1e62
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
db08358
misc: bit of clean up
faustbrian Oct 18, 2018
01282d5
misc: bit of clean up
faustbrian Oct 18, 2018
655fc50
Merge branch 'memory-delegates-2' of https://github.com/ArkEcosystem/…
faustbrian Oct 18, 2018
a89718c
misc: remove #1152 from this branch
faustbrian Oct 18, 2018
222e2e3
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
0e4dd45
Merge branch 'develop' into memory-delegates-2
faustbrian Oct 18, 2018
df786a6
refactor: enable logging and throw an error on duplicates
faustbrian Oct 18, 2018
037eb2a
refactor: use comparedTo from bignumber.js instead of numbers
faustbrian Oct 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,6 +4,6 @@ module.exports = (delegates, round) => {
return delegates.map(delegate => ({
round,
publicKey: delegate,
balance: bignumify('245098000000000')
voteBalance: bignumify('245098000000000')
}))
}
45 changes: 4 additions & 41 deletions packages/core-database-postgres/lib/connection.js
Expand Up @@ -174,8 +174,6 @@ module.exports = class PostgresConnection extends ConnectionInterface {
let currentSeed = crypto.createHash('sha256').update(seedSource, 'utf8').digest()

for (let i = 0, delCount = data.length; i < delCount; i++) {
data[i].round = +round

for (let x = 0; x < 4 && i < delCount; i++, x++) {
const newIndex = currentSeed[x] % delCount
const b = data[newIndex]
Expand All @@ -185,7 +183,10 @@ module.exports = class PostgresConnection extends ConnectionInterface {
currentSeed = crypto.createHash('sha256').update(currentSeed).digest()
}

this.activeDelegates = data
this.activeDelegates = data.map(delegate => {
delegate.round = +round
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the rationale for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a comparison somewhere that was always false because it compared string === integer, this fixes the comparison to integer === integer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean why adding delegate.round property?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah you mean you override the round property of delegates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fix See #1143 and #1152

return delegate
})

return this.activeDelegates
}
Expand All @@ -210,44 +211,6 @@ module.exports = class PostgresConnection extends ConnectionInterface {
return this.db.rounds.delete(round)
}

/**
* Load a list of delegates into memory.
* @param {Number} maxDelegates
* @param {Number} height
* @return {Array}
*/
async buildDelegates (maxDelegates, height) {
if (height > 1 && height % maxDelegates !== 1) {
throw new Error('Trying to build delegates outside of round change')
}

let data = await this.db.rounds.delegates()

// NOTE: At the launch of the blockchain we may not have enough delegates.
// In order to have enough forging delegates we complete the list in a
// deterministic way (alphabetical order of publicKey).
if (data.length < maxDelegates) {
const chosen = data.map(delegate => delegate.publicKey)

const fillerWallets = chosen.length
? await this.db.rounds.placeholdersWithout(maxDelegates - data.length, chosen)
: await this.db.rounds.placeholders(maxDelegates - data.length)

data = data.concat(fillerWallets)
}

// logger.info(`got ${data.length} voted delegates`)
const round = Math.floor((height - 1) / maxDelegates) + 1
data = data
.sort((a, b) => b.balance - a.balance)
.slice(0, maxDelegates)
.map(delegate => ({ ...{ round }, ...delegate }))

logger.debug(`Loaded ${data.length} active delegates`)

return data
}

/**
* Load a list of wallets into memory.
* @param {Number} height
Expand Down
1 change: 1 addition & 0 deletions packages/core-database-postgres/lib/models/round.js
Expand Up @@ -20,6 +20,7 @@ module.exports = class Round extends Model {
prop: 'publicKey'
}, {
name: 'balance',
prop: 'voteBalance',
init: col => {
return +bignumify(col.value).toFixed()
}
Expand Down
3 changes: 0 additions & 3 deletions packages/core-database-postgres/lib/queries/index.js
Expand Up @@ -14,9 +14,6 @@ module.exports = {
top: loadQueryFile(__dirname, './blocks/top.sql')
},
rounds: {
delegates: loadQueryFile(__dirname, './rounds/delegates.sql'),
placeholders: loadQueryFile(__dirname, './rounds/placeholders.sql'),
placeholdersWithout: loadQueryFile(__dirname, './rounds/placeholders-without.sql'),
delete: loadQueryFile(__dirname, './rounds/delete.sql'),
find: loadQueryFile(__dirname, './rounds/find.sql')
},
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

28 changes: 0 additions & 28 deletions packages/core-database-postgres/lib/repositories/rounds.js
Expand Up @@ -12,34 +12,6 @@ module.exports = class RoundsRepository extends Repository {
return this.db.manyOrNone(sql.find, { round })
}

/**
* Get all of the delegates for the current round.
* @return {Promise}
*/
async delegates () {
return this.db.manyOrNone(sql.delegates)
}

/**
* Get all of the delegate placeholders for the current round.
* @param {Number} limit
* @return {Promise}
*/
async placeholders (limit) {
return this.db.many(sql.placeholders, { limit })
}

/**
* Get all of the delegate placeholders for the current round. This excludes
* already selected delegates from the initial pick & choose.
* @param {Number} limit
* @param {Array} publicKeys
* @return {Promise}
*/
async placeholdersWithout (limit, publicKeys) {
return this.db.many(sql.placeholdersWithout, { limit, publicKeys })
}

/**
* Delete the round from the database.
* @param {Number} round
Expand Down
10 changes: 0 additions & 10 deletions packages/core-database/__tests__/interface.test.js
Expand Up @@ -67,16 +67,6 @@ describe('Connection Interface', () => {
})
})

describe('buildDelegates', () => {
it('should be a function', () => {
expect(connectionInterface.buildDelegates).toBeFunction()
})

it('should throw an exception', async () => {
await expect(connectionInterface.buildDelegates()).rejects.toThrowError('Method [buildDelegates] not implemented!')
})
})

describe('buildWallets', () => {
it('should be a function', () => {
expect(connectionInterface.buildWallets).toBeFunction()
Expand Down
13 changes: 1 addition & 12 deletions packages/core-database/lib/interface.js
Expand Up @@ -73,17 +73,6 @@ module.exports = class ConnectionInterface {
throw new Error('Method [getActiveDelegates] not implemented!')
}

/**
* Load a list of delegates into memory.
* @param {Number} maxDelegates
* @param {Number} height
* @return {void}
* @throws Error
*/
async buildDelegates (maxDelegates, height) {
throw new Error('Method [buildDelegates] not implemented!')
}

/**
* Load a list of wallets into memory.
* @param {Number} height
Expand Down Expand Up @@ -291,7 +280,7 @@ module.exports = class ConnectionInterface {
this.walletManager.updateDelegates()
this.updateDelegateStats(height, this.activeDelegates)
await this.saveWallets(false) // save only modified wallets during the last round
const delegates = await this.buildDelegates(maxDelegates, nextHeight) // active build delegate list from database state
const delegates = this.walletManager.activeDelegation(maxDelegates, nextHeight) // get active delegate list from in-memory wallet manager
await this.saveRound(delegates) // save next round delegate list
await this.getActiveDelegates(nextHeight) // generate the new active delegates list

Expand Down
56 changes: 50 additions & 6 deletions packages/core-database/lib/wallet-manager.js
Expand Up @@ -5,6 +5,7 @@ const Promise = require('bluebird')
const { Bignum, crypto } = require('@arkecosystem/crypto')
const { Wallet } = require('@arkecosystem/crypto').models
const { TRANSACTION_TYPES } = require('@arkecosystem/crypto').constants
const { roundCalculator } = require('@arkecosystem/core-utils')
const container = require('@arkecosystem/core-container')
const config = container.resolvePlugin('config')
const logger = container.resolvePlugin('logger')
Expand Down Expand Up @@ -184,18 +185,61 @@ module.exports = class WalletManager {
Object.values(this.byAddress).map(wallet => (wallet.dirty = false))
}

/**
* Load a list of all active delegates.
* @param {Number} maxDelegates
* @return {Array}
*/
activeDelegation (maxDelegates, height) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about loadActiveDelegateList as a function name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

if (height > 1 && height % maxDelegates !== 1) {
throw new Error('Trying to build delegates outside of round change')
}

const { round } = roundCalculator.calculateRound(height, maxDelegates)
let delegates = this.allByUsername()

if (delegates.length < maxDelegates) {
throw new Error(`Expected to find ${maxDelegates} delegates but only found ${delegates.length}. This indicates an issue with the genesis block & delegates.`)
}

delegates = delegates.sort((a, b) => {
const aBalance = +a.voteBalance.toFixed()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way what is the point of this? i guess keeping a.voteBalance is enough.

  • they are numbers
  • if there is a rounding issue, since they are all integers, it won't change the order

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a.voteBalance is a bignumber object inside a wallet object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah true that, i should keep that in mind now. Is there some kind of comparison stuff in bignumber library, sounds weird to cast down to normal number in order to compare again

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const bBalance = +b.voteBalance.toFixed()

if (aBalance === bBalance) {
// logger.warn(`Delegate ${a.username} (${a.publicKey}) and ${b.username} (${b.publicKey}) have a matching vote balance of ${a.voteBalance.dividedBy(Math.pow(10, 8)).toLocaleString()}.`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can keep this imo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it because it issues warnings for all standby delegates as well unless we make it more strict.


if (a.publicKey === b.publicKey) {
logger.error(`The balance and public key of both delegates are identical! Delegate "${a.username}" appears twice in the list.`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should throw here, because it means something even wronger happened

}

return a.publicKey.localeCompare(b.publicKey, 'en-US-u-kf-lower')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this 'en-US-u-kf-lower' ? 'en' should be ok as there is only '0123456789abcdef' as letters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was mainly to make sure the string is treated as a lowercase string in case there ever is a mixed-case public key for whatever reason but changed it to en.

}

return bBalance - aBalance
}).slice(0, maxDelegates)

logger.debug(`Loaded ${delegates.length} active delegates`)

return delegates.map(delegate => ({ ...{ round }, ...delegate }))
}

/**
* Update the vote balances of delegates.
* @return {void}
*/
updateDelegates () {
Object.values(this.byUsername).forEach(delegate => (delegate.voteBalance = Bignum.ZERO))
Object
.values(this.byUsername)
.forEach(delegate => (delegate.voteBalance = Bignum.ZERO))

Object.values(this.byPublicKey)
.filter(voter => !!voter.vote)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is slower than using forEach directly and then testing if(voter.vote)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

.forEach(voter => {
const delegate = this.byPublicKey[voter.vote]
delegate.voteBalance = delegate.voteBalance.plus(voter.balance)
})

Object.values(this.byUsername)
.sort((a, b) => +(b.voteBalance.minus(a.voteBalance)).toFixed())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why b.voteBalance.minus(a.voteBalance) is not enough or even b.voteBalance - a.voteBalance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure we are actually sorting by the numerical value instead of string that is returned by toFixed, and consistency.

.forEach((delegate, index) => (delegate.rate = index + 1))
Expand Down Expand Up @@ -354,18 +398,18 @@ module.exports = class WalletManager {

/**
* Remove the given transaction from a delegate.
* @param {Number} type
* @param {Object} data
* @param {Transaction} transaction
* @return {Transaction}
*/
revertTransaction ({ type, data }) {
revertTransaction (transaction) {
const { type, data } = transaction
const sender = this.findByPublicKey(data.senderPublicKey) // Should exist
const recipient = this.byAddress[data.recipientId]

sender.revertTransactionForSender(data)

// removing the wallet from the delegates index
if (data.type === TRANSACTION_TYPES.DELEGATE_REGISTRATION) {
if (type === TRANSACTION_TYPES.DELEGATE_REGISTRATION) {
delete this.byUsername[data.asset.delegate.username]
}

Expand All @@ -382,6 +426,7 @@ module.exports = class WalletManager {
*/
__isDelegate (publicKey) {
const delegateWallet = this.byPublicKey[publicKey]

if (delegateWallet && delegateWallet.username) {
return !!this.byUsername[delegateWallet.username]
}
Expand All @@ -397,5 +442,4 @@ module.exports = class WalletManager {
__canBePurged (wallet) {
return wallet.balance.isZero() && !wallet.secondPublicKey && !wallet.multisignature && !wallet.username
}

}
5 changes: 3 additions & 2 deletions packages/core-utils/lib/round-calculator.js
Expand Up @@ -5,11 +5,12 @@ const container = require('@arkecosystem/core-container')
/**
* Calculate the round and nextRound based on the height and active delegates.
* @param {Number} height
* @param {Number} maxDelegates
* @return {Object}
*/
exports.calculateRound = (height) => {
exports.calculateRound = (height, maxDelegates = undefined) => {
const config = container.resolvePlugin('config')
const maxDelegates = config.getConstants(height).activeDelegates
maxDelegates = maxDelegates || config.getConstants(height).activeDelegates

const round = Math.floor((height - 1) / maxDelegates) + 1
const nextRound = Math.floor((height) / maxDelegates) + 1
Expand Down
2 changes: 1 addition & 1 deletion packages/crypto/lib/utils/sort-transactions.js
Expand Up @@ -6,7 +6,7 @@
module.exports = (transactions) => {
// Map to create a new array (sort is done in place)
// TODO does it matter modifying the order of the original array
return transactions.map(t => t).sort((a, b) => {
return transactions.sort((a, b) => {
if (a.type < b.type) {
return -1
}
Expand Down