-
Notifications
You must be signed in to change notification settings - Fork 81
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
Initial MPT implementation (2.x) #990
Conversation
Codecov Report
@@ Coverage Diff @@
## neox-2.x #990 +/- ##
============================================
+ Coverage 67.97% 68.94% +0.97%
============================================
Files 145 153 +8
Lines 14320 14859 +539
============================================
+ Hits 9734 10245 +511
- Misses 4126 4143 +17
- Partials 460 471 +11
Continue to review full report at Codecov.
|
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 absolutely need some tests that compare root hashes with ones from C# implementation (there are some UTs there that have samples of these)
|
||
// toBytes is a helper for serializing node. | ||
func toBytes(n Node) []byte { | ||
buf := io.NewBufBinWriter() |
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 be OK for a start, but this probably will be allocating like crazy.
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 and we can't use a single buffer, because this conversion is performed recursively, e.g. on Branch
nodes. I decided not to use any kind of pools it for now, to keep this simple.
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.
pkg/core/mpt/trie.go
Outdated
} | ||
|
||
b := NewBranchNode() | ||
r, err := t.putWithPath(b.Children[path[0]], path[1:], v) |
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.
You're a bit inconsistent here, putBranch
uses splitPath
in similar situation and given that you're using path[0]
down below again, probably it's better to do so here too.
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's because we compare length of the path
with 0 earlier.
pkg/core/mpt/proof.go
Outdated
} | ||
|
||
// VerifyProof verifies that path indeed belongs to a MPT with the specified root hash. | ||
func VerifyProof(rh util.Uint256, path []byte, proofs [][]byte) ([]byte, bool) { |
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's an external interface, it should accept simple path and convert it to nibbles. BTW, that's the way VerifyProof()
in C# works.
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.
To be fair, I don't really like its interface with []byte
returned, but maybe it's ok for now. I expect a verification of KV pair, at the moment it looks more like a GetVerifiedContentForTheKey
. But probably it's not a problem as of yet.
@@ -74,6 +90,38 @@ func TestNode_Serializable(t *testing.T) { | |||
}) | |||
} | |||
|
|||
func TestInvalidJSON(t *testing.T) { |
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.
Encoding/decoding and invalid JSON tests are all nice, but we also need some known-good JSON strings to decode. The ones generated by encoding are not appropriate as we need to follow a specific encoding standard set by C# implementation and internal enc/dec doesn't prove we're compliant with that.
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.
Are there any that can be taken from C# tests or generated by the respective node?
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.
Found only this https://github.com/neo-project/neo/blob/neox-2.x/neo.UnitTests/UT_MPTTrie.cs#L198
Added it.
В письме от воскресенье, 31 мая 2020 г. 11:28:07 MSK пользователь fyrchik
написал:
This is a separate function, because we _must_ perform `Flush` every block,
however `Collapse` can be called once per N blocks if needed. Not a big
deal now probably, but can be useful e.g. during restore.
At the very minimum this should be explicitly documented.
|
В письме от воскресенье, 31 мая 2020 г. 11:29:08 MSK пользователь fyrchik
написал:
What about a `bool` flag to `Collapse` specifying if everything should be
flushed?
I don't really like such flags, maybe a separate function will do better or
maybe we can just document it for now.
This function is obviously dangerous from an API point of view, probably
that's what bugs me most, but at the same time there won't be a lot of users
of this API and if we're to be careful with clearly written rules then maybe
we're 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.
Things obviously missing:
- key grouping
- p2p processing
I think we can concentrate here on base MPT structure and merge p2p/storage/consensus changes in other PRs.
pkg/core/dao/dao.go
Outdated
@@ -86,7 +90,9 @@ func (dao *Simple) GetBatch() *storage.MemBatch { | |||
// GetWrapped returns new DAO instance with another layer of wrapped | |||
// MemCachedStore around the current DAO Store. | |||
func (dao *Simple) GetWrapped() DAO { | |||
return NewSimple(dao.Store) | |||
d := NewSimple(dao.Store) | |||
d.MPT = dao.MPT |
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.
An interesting part is that this should work for Cached
because of its storage item cache. Though it probably won't for the master branch.
pkg/network/server.go
Outdated
@@ -507,6 +508,8 @@ func (s *Server) handleGetDataCmd(p Peer, inv *payload.Inventory) error { | |||
if err == nil { | |||
msg = s.MkMsg(CMDBlock, b) | |||
} | |||
case payload.StateRootType: | |||
return nil // TODO |
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.
Not there yet.
MPT is a trie with a branching factor = 16, i.e. it consists of sequences in 16-element alphabet.
Because there is no distinct type field in JSONized nodes, distinction is made via payload itself, thus all unmarshaling is done via NodeObject.
Because trie size is rather big, it can't be stored in memory. Thus some form of caching should also be implemented. To avoid marshaling/unmarshaling of items which are close to root and are used very frequenly we can save them across the persists. This commit implements pruning items at the specified depth, replacing them by hash nodes.
This is initial MPT implementation, containing most of the necessary methods.
Storage key transformation to NEO format will be done during integration.