Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Fetch values immediately, updates #39

Merged
merged 8 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

A Go library that implements a Sparse Merkle tree for a key-value map. The tree implements the same optimisations specified in the [Libra whitepaper](https://developers.libra.org/docs/assets/papers/the-libra-blockchain/2020-05-26.pdf), to reduce the number of hash operations required per tree operation to O(k) where k is the number of non-empty elements in the tree.

[![Tests](https://github.com/lazyledger/smt/actions/workflows/test.yml/badge.svg)](https://github.com/lazyledger/smt/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/lazyledger/smt/badge.svg?branch=master)](https://coveralls.io/github/lazyledger/smt?branch=master)
[![GoDoc](https://godoc.org/github.com/lazyledger/smt?status.svg)](https://godoc.org/github.com/lazyledger/smt)
[![Tests](https://github.com/celestiaorg/smt/actions/workflows/test.yml/badge.svg)](https://github.com/celestiaorg/smt/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/celestiaorg/smt/badge.svg?branch=master)](https://coveralls.io/github/celestiaorg/smt?branch=master)
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
[![GoDoc](https://godoc.org/github.com/celestiaorg/smt?status.svg)](https://godoc.org/github.com/celestiaorg/smt)

## Example

Expand All @@ -14,14 +14,15 @@ package main
import(
"fmt"
"crypto/sha256"
"github.com/lazyledger/smt"
"github.com/celestiaorg/smt"
)

func main() {
// Initialise a new key-value store to store the nodes of the tree
store := smt.NewSimpleMap()
// Initialise two new key-value store to store the nodes and values of the tree
nodestore := smt.NewSimpleMap()
valuestore := smt.NewSimpleMap()
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
// Initialise the tree
tree := smt.NewSparseMerkleTree(store, sha256.New())
tree := smt.NewSparseMerkleTree(nodestore, valuestore, sha256.New())

// Update the key "foo" with the value "bar"
tree.Update([]byte("foo"), []byte("bar"))
Expand All @@ -41,5 +42,4 @@ func main() {

## Future wishlist

- **Garbage collection for obsolete nodes.** When tree is updated, obsolete nodes are not garbage collected, and so storage growth is unbounded. This is desirable for accessing previous revisions of the tree (for example, if you need to revert to a previous block in a blockchain due to a chain reorganisation caused by the chain's consensus algorithm), but otherwise undesirable for storage size. A future wishlist item is to extend the library to allow for an optional garbage collected version of the tree, though this requires further research.
- **Tree sharding to process updates in parallel.** At the moment, the tree can only safely handle one update at a time. It would be desirable to shard the tree into multiple subtrees and allow parallel updates to the subtrees.
65 changes: 65 additions & 0 deletions deepsubtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,68 @@ func (dsmst *DeepSparseMerkleSubTree) AddBranch(proof SparseMerkleProof, key []b

return nil
}

// GetDescend gets the value of a key from the tree by descending it.
// Use if a key was _not_ previously added with AddBranch, otherwise use Get.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would someone try to get a key that wasn't added with AddBranch? In what circumstances would that succeed?

Copy link
Member Author

@adlerjohn adlerjohn Jul 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From #39 (comment)

the code wasn't able to get a key that is expected to have a [non-default] value.

☝️ that exact case. When a key in the original SMT has a non-default value, but wasn't added via AddBranch to the DSMST, then was queried. This happens when a malicious fraud proof doesn't provide enough pre-state. You don't want to return defaultValue in that case because the key doesn't actually have default value; it has a non-default value but that value wasn't proven.

In other words, with an untrusted fraud proof, only GetDescend and HasDescend should be used with the DSMST.

// Errors if the key cannot be reached by descending.
func (smt *SparseMerkleTree) GetDescend(key []byte) ([]byte, error) {
// Get tree's root
root := smt.Root()

if bytes.Equal(root, smt.th.placeholder()) {
// The tree is empty, return the default value.
return defaultValue, nil
}

path := smt.th.path(key)
currentHash := root
for i := 0; i < smt.depth(); i++ {
currentData, err := smt.nodes.Get(currentHash)
if err != nil {
return nil, err
} else if smt.th.isLeaf(currentData) {
// We've reached the end. Is this the actual leaf?
p, _ := smt.th.parseLeaf(currentData)
if !bytes.Equal(path, p) {
// Nope. Therefore the key is actually empty.
return defaultValue, nil
}
// Otherwise, yes. Return the value.
value, err := smt.values.Get(path)
if err != nil {
return nil, err
}
return value, nil
}

leftNode, rightNode := smt.th.parseNode(currentData)
if getBitAtFromMSB(path, i) == right {
currentHash = rightNode
} else {
currentHash = leftNode
}

if bytes.Equal(currentHash, smt.th.placeholder()) {
// We've hit a placeholder value; this is the end.
return defaultValue, nil
}
}

// The following lines of code should only be reached if the path is 256
// nodes high, which should be very unlikely if the underlying hash function
// is collision-resistant.
value, err := smt.values.Get(path)
if err != nil {
return nil, err
}
return value, nil
}

// HasDescend returns true if the value at the given key is non-default, false
// otherwise.
// Use if a key was _not_ previously added with AddBranch, otherwise use Has.
// Errors if the key cannot be reached by descending.
func (smt *SparseMerkleTree) HasDescend(key []byte) (bool, error) {
val, err := smt.GetDescend(key)
return !bytes.Equal(defaultValue, val), err
}
23 changes: 22 additions & 1 deletion deepsubtree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,42 @@ func TestDeepSparseMerkleSubTreeBasic(t *testing.T) {
if !bytes.Equal(value, []byte("testValue1")) {
t.Error("did not get correct value in deep subtree")
}
value, err = dsmst.GetDescend([]byte("testKey1"))
if err != nil {
t.Errorf("returned error when getting value in deep subtree: %v", err)
}
if !bytes.Equal(value, []byte("testValue1")) {
t.Error("did not get correct value in deep subtree")
}
value, err = dsmst.Get([]byte("testKey2"))
if err != nil {
t.Errorf("returned error when getting value in deep subtree: %v", err)
}
if !bytes.Equal(value, []byte("testValue2")) {
t.Error("did not get correct value in deep subtree")
}
value, err = dsmst.GetDescend([]byte("testKey2"))
if err != nil {
t.Errorf("returned error when getting value in deep subtree: %v", err)
}
if !bytes.Equal(value, []byte("testValue2")) {
t.Error("did not get correct value in deep subtree")
}
value, err = dsmst.Get([]byte("testKey5"))
if err != nil {
t.Errorf("returned error when getting value in deep subtree: %v", err)
}
if !bytes.Equal(value, defaultValue) {
t.Error("did not get correct value in deep subtree")
}
value, err = dsmst.Get([]byte("testKey6"))
value, err = dsmst.GetDescend([]byte("testKey5"))
if err != nil {
t.Errorf("returned error when getting value in deep subtree: %v", err)
}
if !bytes.Equal(value, defaultValue) {
t.Error("did not get correct value in deep subtree")
}
_, err = dsmst.GetDescend([]byte("testKey6"))
if err == nil {
t.Error("did not error when getting non-added value in deep subtree")
}
Expand Down
56 changes: 6 additions & 50 deletions smt.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,75 +64,31 @@ func (smt *SparseMerkleTree) depth() int {
return smt.th.pathSize() * 8
}

// Get gets a key from the tree.
// Get gets the value of a key from the tree.
func (smt *SparseMerkleTree) Get(key []byte) ([]byte, error) {
value, err := smt.GetForRoot(key, smt.Root())
return value, err
}
// Get tree's root
root := smt.Root()

// GetForRoot gets a key from the tree at a specific root.
func (smt *SparseMerkleTree) GetForRoot(key []byte, root []byte) ([]byte, error) {
if bytes.Equal(root, smt.th.placeholder()) {
// The tree is empty, return the default value.
return defaultValue, nil
}

path := smt.th.path(key)
currentHash := root
for i := 0; i < smt.depth(); i++ {
currentData, err := smt.nodes.Get(currentHash)
if err != nil {
return nil, err
} else if smt.th.isLeaf(currentData) {
// We've reached the end. Is this the actual leaf?
p, _ := smt.th.parseLeaf(currentData)
if !bytes.Equal(path, p) {
// Nope. Therefore the key is actually empty.
return defaultValue, nil
}
// Otherwise, yes. Return the value.
value, err := smt.values.Get(path)
if err != nil {
return nil, err
}
return value, nil
}

leftNode, rightNode := smt.th.parseNode(currentData)
if getBitAtFromMSB(path, i) == right {
currentHash = rightNode
} else {
currentHash = leftNode
}

if bytes.Equal(currentHash, smt.th.placeholder()) {
// We've hit a placeholder value; this is the end.
return defaultValue, nil
}
}

// The following lines of code should only be reached if the path is 256
// nodes high, which should be very unlikely if the underlying hash function
// is collision-resistant.
value, err := smt.values.Get(path)
if err != nil {
return nil, err
return defaultValue, nil
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}
return value, nil
}

// Has returns true if tree contains given key, false otherwise.
// Has returns true if the value at the given key is non-default, false
// otherwise.
func (smt *SparseMerkleTree) Has(key []byte) (bool, error) {
val, err := smt.Get(key)
return !bytes.Equal(defaultValue, val), err
}

// HasForRoot returns true if tree contains given key at a specific root, false otherwise.
func (smt *SparseMerkleTree) HasForRoot(key, root []byte) (bool, error) {
val, err := smt.GetForRoot(key, root)
return !bytes.Equal(defaultValue, val), err
}

// Update sets a new value for a key in the tree, and sets and returns the new root of the tree.
func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) {
newRoot, err := smt.UpdateForRoot(key, value, smt.Root())
Expand Down