-
Notifications
You must be signed in to change notification settings - Fork 45
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
add new function Walk() to trie #146
Conversation
This is not 100% correct... Sometimes the test will fail and I don't know why (yet). Maybe someone with more knowledge on this SMT implementation can find the problem. |
So with this last implementation, |
Walk allows iteration over all key/values stored into the SMT. If the callback function returns true, walk will stop. Signed-off-by: p4u <pau@dabax.net>
Signed-off-by: p4u <pau@dabax.net>
Thank you @p4u for your input. Have you checked this repository which does the export of an Aergo state snapshot ? |
I did not know about the existence of that repository, I'll take a look. However, from a SMT API consumer perspective with the experience of using several goLang Merkletree implementations for blockchain development, I think the following methods are missing:
Does it makes sense to you? This is our interface and our current implementations (where I want to add Aergo SMT): https://gitlab.com/vocdoni/go-dvote/-/tree/master/statedb |
Definitely makes sense. I know some projects used the same trie but usually adapt it to their needs. So if there is consensus on an interface/usage that is useful to many, I'm happy to add features. We might have to refactor outside of the Aergo client code. There is also the new approach https://github.com/ledgerwatch/turbo-geth which stores the leaves directly instead of querying from the root in logN, and re-constructs the root when a leaf is updated. It brings performance benefits but changes how we think about the trie (need to keep track of what is the current version of the trie). |
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.
Thank you for your contribution ! Nice work navigating the trie serialization 👍
Please see comments in the code for solving the random bug.
pkg/trie/trie_test.go
Outdated
// Walk over the whole tree and compare the values | ||
i := 0 | ||
if err := smt.Walk(func(v *WalkResult) bool { | ||
if string(v.Value) != string(values[i]) { |
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.
bytes can be compared with bytes.Equal()
pkg/trie/trie_tools.go
Outdated
return err | ||
} | ||
if isShortcut { | ||
var key []byte |
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.
Key can be accessed directly as:
lnode[:HashLength]
pkg/trie/trie_tools.go
Outdated
// walk fetches the value of a key given a trie root | ||
func (s *Trie) walk(walkc chan (*WalkResult), stop *bool, root []byte, batch [][]byte, iiBatch, height int) error { | ||
if len(root) == 0 || *stop { | ||
// the trie does not contain the key or stop bool is set tu 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 sub tree is empty or stop walking
pkg/trie/trie_tools.go
Outdated
return nil | ||
} | ||
// Fetch the children of the node | ||
nbatch, iBatch, lnode, rnode, isShortcut, err := s.loadChildren(root, height, iiBatch, batch) |
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.
for consistency, nbatch
and iiBatch
can be batch
and iBatch
pkg/trie/trie_tools.go
Outdated
for { | ||
select { | ||
case <-close: | ||
break |
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 tracked down the random bug you mentioned to this function. There are cases where the Walk() finishes iterating the tree and returns while the callback hasn't finished executing. So if calling Walk() 2 times in a row, the i++
from the 1st walk can override the i = 1
reset for the 2nd walk.
To prevent this, you need confirmation that the goroutine has finished executing before Walk() returns, by using a blocking channel that waits for the goroutine to return for example.
Also, this break
should be a return
otherwise only select is broken and not the for
loop.
Using WaitGroup doesn't seem necessary here.
close
channel can be renamed as it is a golang builtin
For example:
s.lock.RLock()
defer s.lock.RUnlock()
s.atomicUpdate = false
walkc := make(chan *WalkResult)
exit := make(chan (bool))
finishedWalk := make(chan (bool))
stop := false
go func() {
for {
select {
case <-finishedWalk:
exit <- true
return
case value := <-walkc:
if stop = callback(value); stop {
// break and loop to case <- finishedWalk
break
}
}
}
}()
err := s.walk(walkc, &stop, s.Root, nil, 0, s.TrieHeight)
finishedWalk <- true
<- exit. // wait for goroutine to return
return err
Hey @paouvrard I have been out for some days but now I'm back. Thank you for your review, I am going to incorporate the changes. |
I applied your suggestions and now I added another function named I keep everything on the same PR because in my local repository I did not split it, I hope that's not a problem. Looking forward for your review :) |
Damn it, |
Fixed! Now |
Now Walk() is data race safe and does not return until all callbacks have finished. Signed-off-by: p4u <pau@dabax.net>
The last version solves concurrency problems and it is data race safe. Tested with |
If this PR gets finally merged, next step could be to add a String() function, Export() and Import(), which IMO would make the API more powerful. Something like: func (t *trie) String() string {
buf := bytes.Buffer{}
t.Walk(nil, func(k, v []byte) int32 {
buf.WriteString(fmt.Sprintf("%x => %x\n", k, v))
return 0
})
return buf.String()
} |
So I see there is probably no interest on having this new methods on the SMT. That's fine. I forked the code and applied some changes (including a new abstraction layer). If anyone is interested, the code is here: https://github.com/p4u/asmt |
The aergoio/SMT repo appears to be better for this The aergoio/state-tools is also related |
Walk allows iteration over all values stored into the SMT.
This is a useful method for exporting the tree (and importing afterwards) and also for search specific values.
Signed-off-by: p4u pau@dabax.net