-
Notifications
You must be signed in to change notification settings - Fork 771
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
feat(trie): implement root hash persistence #2067
Conversation
@@ -107,6 +107,7 @@ export class SecureTrie extends CheckpointTrie { | |||
db: this.dbStorage.copy(), | |||
root: this.root, | |||
deleteFromDB: (this as any)._deleteFromDB, | |||
persistRoot: (this as any)._persistRoot, |
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.
Something I noticed here is that hash: this.hash
seems to be missing. The Trie
passes it down when it calls copy
but CheckpointTrie
and SecureTrie
don't. Is this intentional or an oversight?
copy(): Trie {
return new Trie({
db: this.db.copy(),
root: this.root,
deleteFromDB: this._deleteFromDB,
hash: this.hash,
persistRoot: this._persistRoot,
})
}
I assume this could result in corrupted data if you copy a CheckpointTrie
or SecureTrie
that for example uses sha256
but then it doesn't pass down the hash function and the copy works with keccak256
hashes because const _hash = opts?.hash ?? keccak256
will fall back to the keccak256
function?
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.test('created copy uses the correct hash function', async function (t) {
const trie = new SecureTrie({
db: new LevelDB(),
hash: (value) => createHash('sha256').update(value).digest(),
})
await trie.put(Buffer.from('key1'), Buffer.from('value1'))
trie.checkpoint()
await trie.put(Buffer.from('key2'), Buffer.from('value2'))
const trieCopy = trie.copy()
const value = await trieCopy.get(Buffer.from('key1'))
t.equal(value!.toString(), 'value1')
t.end()
})
# created copy uses the correct hash function
not ok 183 TypeError: Cannot read properties of null (reading 'toString')
---
operator: error
stack: |-
TypeError: Cannot read properties of null (reading 'toString')
at Test.<anonymous> (/Users/brianfaust/Work/bearmint/ethereumjs-monorepo/packages/trie/test/trie/secure.spec.ts:167:20)
I think the above reproduces the issue. If I then change to the following copy
function for SecureTrie
it'll pass.
copy(includeCheckpoints = true): SecureTrie {
const secureTrie = new SecureTrie({
db: this.dbStorage.copy(),
root: this.root,
deleteFromDB: (this as any)._deleteFromDB,
persistRoot: (this as any)._persistRoot,
hash: this.hash,
})
if (includeCheckpoints && this.isCheckpoint) {
secureTrie.db.checkpoints = [...this.db.checkpoints]
}
return secureTrie
}
The above test will fail because it will store key1
hashed via sha256
and key2
hashed via keccak256
but then the copy of the trie will use keccak256
again because this.hash
wasn't passed down which results in it looking up key1
hashed via keccak256
instead of the sha256
it was stored as. Does that make sense @acolytec3 and can you reproduce it?
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.
Nice catch. It makes sense and is likely a miss from #2043. I'm AFK most of today but will try and reproduce later.
@@ -69,6 +71,7 @@ export class CheckpointTrie extends Trie { | |||
db: this.dbStorage.copy(), | |||
root: this.root, | |||
deleteFromDB: (this as any)._deleteFromDB, | |||
persistRoot: (this as any)._persistRoot, |
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.
Codecov Report
Flags with carried forward coverage won't be shown. Click here to find out more. |
I was thinking something like this
|
Opened #2068 to fix an issue that needs resolving before I can write proper tests here. |
Please don't update the branch via UI because I'll be squashing my commits as I keep pushing.
A few question before I continue this and add tests:
One of the requirements isIt should default to true if a DB is passed into the constructor and be hardcoded to false if not.
so what should the conditional be. Something likethis._persistRoot = (opts?.db ? true : opts?.persistRoot) ?? false
? The requirement sounds a bit confusing because what if the user doesn't want root persistence but they still passed in a database? The boolean flag seems a bit unnecessary if we make it dependent onopts.db
being defined.const ROOT_DB_KEY = Buffer.from('__root__')
and wanted to add another constant for the keccak256 key but then realised that the hashing function is now modular. Should I do the hashing on the fly?put
methods of any trie are called with theROOT_DB_KEY
key, meaning a user manually wanted to modify that value?__root__
instead of__persistedRootHash__
since the__
prefix and suffix should already distinguish it enough from user space wording.