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

refactor: build delegate list from memory #1134

merged 48 commits into from Oct 18, 2018

Conversation

faustbrian
Copy link
Contributor

@faustbrian faustbrian commented Oct 17, 2018

Proposed changes

Resolves #850

Types of changes

  • Refactoring (improve a current implementation without adding a new feature or fixing a bug)

Checklist

  • I have read the CONTRIBUTING documentation
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective

@codecov-io
Copy link

codecov-io commented Oct 17, 2018

Codecov Report

Merging #1134 into develop will increase coverage by 0.04%.
The diff coverage is 89.28%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1134      +/-   ##
===========================================
+ Coverage    78.08%   78.13%   +0.04%     
===========================================
  Files          425      425              
  Lines         6923     6925       +2     
  Branches       891      895       +4     
===========================================
+ Hits          5406     5411       +5     
+ Misses        1343     1341       -2     
+ Partials       174      173       -1
Impacted Files Coverage Δ
...ackages/core-database-postgres/lib/models/round.js 100% <ø> (ø) ⬆️
...ckages/core-database-postgres/lib/queries/index.js 100% <ø> (ø) ⬆️
.../core-database-postgres/lib/repositories/rounds.js 100% <ø> (+20%) ⬆️
packages/core-database-postgres/lib/connection.js 74.39% <ø> (+0.3%) ⬆️
packages/core-utils/lib/round-calculator.js 100% <100%> (ø) ⬆️
packages/core-database/lib/interface.js 74.24% <100%> (-0.2%) ⬇️
packages/crypto/lib/utils/sort-transactions.js 72.72% <100%> (ø) ⬆️
packages/core-database/lib/wallet-manager.js 74.83% <87.5%> (+1.34%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 82f6f4f...037eb2a. Read the comment docs.

@faustbrian
Copy link
Contributor Author

faustbrian commented Oct 17, 2018

178 and 441 now pass on devnet but 866 blows up because 2 delegates have the same balance.

Haven't applied any public key sorting to the final list yet.

// 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 (delegates.length < maxDelegates) {
Copy link
Contributor

Choose a reason for hiding this comment

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

it should throw if there is not enough delegates (ie genesisBlock should register at least maxDelegates)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we shutdown the node in case there are not enough delegates? Doesn't seem to make sense to keep it running without enough delegates.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, shut down is required, you could say genesis block is illformed with not enough registered delegates


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

@fix fix Oct 17, 2018

Choose a reason for hiding this comment

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

mmm toFixed gives you a string, this aBalance - bBalance is alphabetical order

Copy link
Contributor

Choose a reason for hiding this comment

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

  • is supposed to cast to integer then, true

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.

ok

Copy link
Contributor

Choose a reason for hiding this comment

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

i expect however a performance hit, that might be an issue in the future (lots of registered delegates)

delegates = delegates.concat(fillerWallets.slice(0, maxDelegates - delegates.length))
}

return delegates.sort((a, b) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

before doing that i would make a function that recalculate all vote balance:

this.allByPublicKeys().forEach((w)=> {
  if(w.vote) this.byPublicKey[w.vote].voteBalance += w.balance //missing initialisation
})

* @param {Transaction} transaction
* @return {void}
*/
__updateVoteBalance (sender, transaction) {
Copy link
Contributor

Choose a reason for hiding this comment

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

that is quite complex operation.

  • this is different if the tx is applied or reverted
  • the votes change for sender AND receiver

@fix
Copy link
Contributor

fix commented Oct 17, 2018

On the overall,

  • we need to be sure that the call to allActiveDelegates is done ONLY at turn round. Otherwise the active delegate list is NOT the right one.
  • as first iteration we can use recompute the voteBalance from scratch at each turn round. This has the property to stay the same at each call of listActive delegates:
    applyRound -> if round change then walletManager.updateDelegates and store this list in walletManager on a different storage
    revertRound -> if round change this is complex, needs to get the list from database (table rounds).
  • as second iteration, the voteBalance is updated at each block being applied. This will cut the performance issue of recomputing everything at turn round that will only backup the list for the next round.

@faustbrian
Copy link
Contributor Author

Syncing on devnet seems to work without any issues now but mainnet is still having some issues so will continue to look into it.


return delegates.sort((a, b) => {
const aBalance = +a.voteBalance.toFixed()
const bBalance = +b.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.

ok

// 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 (delegates.length < maxDelegates) {
Copy link
Contributor

Choose a reason for hiding this comment

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

yes, shut down is required, you could say genesis block is illformed with not enough registered delegates

* @param {Number} maxDelegates
* @return {Array}
*/
allActiveDelegates (maxDelegates) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The list is FIXED for 1 round

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


return delegates.sort((a, b) => {
const aBalance = +a.voteBalance.toFixed()
const bBalance = +b.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.

i expect however a performance hit, that might be an issue in the future (lots of registered delegates)

delete this.byUsername[data.asset.delegate.username]
}

if (recipient && type === TRANSACTION_TYPES.TRANSFER) {
recipient.revertTransactionForRecipient(data)
}

this.__updateVoteBalance('revert', sender, transaction)
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 having 2 functions:
__applyVoteBalance(sender, transaction)
__revertVoteBalance(receiver, transaction)

Copy link
Contributor

Choose a reason for hiding this comment

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

well this is a bit more complex than that actually

Copy link
Contributor

Choose a reason for hiding this comment

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

the logic is:

  • apply block reward to delegate -> add this vote reward to his vote delegate he is voting
  • apply transaction to receiver -> add the tx.amount to the vote if receiver is voting
  • apply transaction to sender -> remove the tx.fees to the vote if sender is voting
  • apply unvote transaction to sender -> remove sender.balance to the vote
  • apply vote transaction to sender -> add sender.balance to the new vote

Then same 5 logic in case of revert

Object.values(this.byPublicKey)
.filter(voter => !!voter.vote)
.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.

* @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

@@ -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

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

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.

@faustbrian faustbrian changed the title [WIP] refactor: build delegate list from memory refactor: build delegate list from memory Oct 18, 2018
}

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.

// 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()}.`)

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

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.

@fix
Copy link
Contributor

fix commented Oct 18, 2018

ok sounds good me. If it rebuilds the first 1000s blocks on mainnet/devnet, we can merge

@faustbrian
Copy link
Contributor Author

@supaiku0 you still had a thing you would like to add in this PR right?

@spkjp
Copy link
Contributor

spkjp commented Oct 18, 2018

Will open a separate PR.

@spkjp spkjp merged commit c95e170 into develop Oct 18, 2018
@faustbrian faustbrian deleted the memory-delegates-2 branch October 18, 2018 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants