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
Optimize MerkleTree.contains from O(n) to O(1) #775
Conversation
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.
From a code review standpoint, looks good -- I think @NullSoldier wants to add a perf test to this before it merges, and it'd be nice to run it on a laptop node for a bit to see if any issues happen to pop up.
ironfish/src/consensus/verifier.ts
Outdated
@@ -167,10 +167,8 @@ export class Verifier { | |||
tx?: IDatabaseTransaction, | |||
): Promise<VerificationResult> { | |||
return this.chain.db.withTransaction(tx, async (tx) => { | |||
const noteSize = await this.chain.notes.size(tx) |
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, looks like this might have been a bug, good spot! If possible it might be nice to pull this out into a separate PR so any issues with it aren't conflated with the LeavesIndex cache.
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.
Moved that fix here, #798
It looks like there are some test failures in here as well |
a342cae
to
8474295
Compare
Here is the perf test @dguenther https://github.com/iron-fish/ironfish/pull/799/files |
ironfish/src/consensus/verifier.ts
Outdated
@@ -296,16 +294,15 @@ export class Verifier { | |||
async hasValidSpends(block: Block, tx?: IDatabaseTransaction): Promise<VerificationResult> { |
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 removed code that was buggy here was intending to check for the note being spent really existed in the tree at the time it was spent
but was not. Should this still check for that some how? The spend size is the size of the nullifier tree when the commitment was created, not the note tree right? @dguenther
4fc7839
to
e5c131c
Compare
cfb9b4a
to
d10c055
Compare
had a horribly inefficient linear scan which brought up avg time to add a block with multiple transactions to multiple seconds making syncing painfully slow. This change introduces another db store to keep track of merkle tree elements for O(1) retrieval if the element is in the tree or not.
6bd70f7
to
a557b5a
Compare
We still need to clean up the index in the |
@@ -327,22 +331,21 @@ export class Verifier { | |||
* spend's spend root. | |||
* | |||
* @param spend the spend to be verified | |||
* @param size the size of the nullifiers tree at which the spend must not exist |
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 should probably be added back, or we should kill the whole comment so it's at least somewhat consistent
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.
Fixed
d68148f
to
2d8c8ad
Compare
* In merkletree's contained & contains method we had a horribly inefficient linear scan which brought up avg time to add a block with multiple transactions to multiple seconds making syncing painfully slow. This change introduces another db store to keep track of merkle tree elements for O(1) retrieval if the element is in the tree or not. * removing pastSize from the contained method * Use existing merkle hash * Fix dead lock * Minor cleanup inside of verifier (#811) * Rename hasValidSpends to verifyConnectedSpends (#809) * Only verify spend root in hasValidSpends (#810) * Truncate index during MerkleTree truncate (#820) Co-authored-by: NullSoldier <nullprogrammer@gmail.com>
* Use nullifier tree size to verify spend (#798) We were using the notes tree size to verifier the spend, which is not correct but shouldn't generally cause an issue because the note tree is bigger than the nullifier tree. * Fix use of clearLine in miners:mined (#816) * chore: add graffiti logging on start (#818) * Set default expiration delta to 15 from 450 (#822) * Use @napi-rs/blake-hash instead of blake3 (#815) The blake3 package doesn't seem to ship a native addon that works properly in node 16, so let's try blake-hash instead. * Optimize MerkleTree.contains from O(n) to O(1) (#775) * In merkletree's contained & contains method we had a horribly inefficient linear scan which brought up avg time to add a block with multiple transactions to multiple seconds making syncing painfully slow. This change introduces another db store to keep track of merkle tree elements for O(1) retrieval if the element is in the tree or not. * removing pastSize from the contained method * Use existing merkle hash * Fix dead lock * Minor cleanup inside of verifier (#811) * Rename hasValidSpends to verifyConnectedSpends (#809) * Only verify spend root in hasValidSpends (#810) * Truncate index during MerkleTree truncate (#820) Co-authored-by: NullSoldier <nullprogrammer@gmail.com> * Bump version to 0.1.15 * Update docker-compose configurations * Fix path and change services name * Fix readme * Add volume to miner service * Proofreading Docker.md Co-authored-by: Jason Spafford <nullprogrammer@gmail.com> Co-authored-by: Derek Guenther <derek@ironfish.network> Co-authored-by: jintao <cdv1013@gmail.com> Co-authored-by: Elena Nadolinski <elena@ironfish.network> Co-authored-by: Rohan Jadvani <5459049+rohanjadvani@users.noreply.github.com> Co-authored-by: Derek Guenther <dguenther9@gmail.com>
Summary
In the merkle tree file, we have a
contained
method. Currently, there's a linear scan that goes through all of the existing nullifiers when determining if a transaction is valid (e.g. not a double spend). As we get more transactions, this linear scan becomes more and more inefficient (e.g. beefy later blocks would take several seconds).This change adds another db store that lets you check in O(1) if an element exists in a merkle tree.
Testing Plan
Tested it locally and avg block time is about 20ms per transaction per block
Breaking Change
This requires a db upgrade.