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
Conversation
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
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) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unary plus (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus_()) turns the toFixed
result into an integer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
On the overall,
|
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() |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@supaiku0 added it for here https://github.com/ArkEcosystem/core/blob/develop/packages/core-database-postgres/lib/connection.js#L167
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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') |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
} | ||
|
||
delegates = delegates.sort((a, b) => { | ||
const aBalance = +a.voteBalance.toFixed() |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use https://mikemcl.github.io/bignumber.js/#cmp
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.`) |
There was a problem hiding this comment.
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()}.`) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
ok sounds good me. If it rebuilds the first 1000s blocks on mainnet/devnet, we can merge |
@supaiku0 you still had a thing you would like to add in this PR right? |
Will open a separate PR. |
Proposed changes
Resolves #850
Types of changes
Checklist