Skip to content

Commit

Permalink
feat: panic on Sum() call and generalise for all hasher sizes
Browse files Browse the repository at this point in the history
  • Loading branch information
h5law committed Jan 9, 2024
1 parent ad3911c commit b8ada3a
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 68 deletions.
10 changes: 5 additions & 5 deletions docs/merkle-sum-trie.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@ sum as a `uint64`.

## Roots

Roots are `[]byte` types aliases by the `MerkleRoot` type. This type has one
method `Sum(sumTrie bool) uint64`. For the SMST this method is used by its own
`Sum()` method to return the total sum of the trie.
The root of the tree is a slice of bytes. `MerkleRoot` is an alias for `[]byte`.
This design enables easily passing around the data (e.g. on-chain)
while maintaining primitive usage in different use cases (e.g. proofs).

The `MerkleRoot` type being an alias means it can be used in place of the
`[]byte` type. Specifically for proofs.
`MerkleRoot` provides helpers, such as retrieving the `Sum(sumTrie bool)uint64`
to interface with data it captures.

## Nil Values

Expand Down
44 changes: 7 additions & 37 deletions docs/smt.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
- [Database](#database)
* [Database Submodules](#database-submodules)
+ [SimpleMap](#simplemap)
+ [Badger](#badger)
* [Data Loss](#data-loss)
- [Sparse Merkle Sum Trie](#sparse-merkle-sum-trie)

<!-- tocstop -->

Expand Down Expand Up @@ -345,11 +342,13 @@ graph TD

## Roots

Roots are `[]byte` types aliases by the `MerkleRoot` type. This type has one
method `Sum(sumTrie bool) uint64`. For the SMT this method **always** returns 0.
The root of the tree is a slice of bytes. `MerkleRoot` is an alias for `[]byte`.
This design enables easily passing around the data (e.g. on-chain)
while maintaining primitive usage in different use cases (e.g. proofs).

The `MerkleRoot` type being an alias means it can be used in place of the
`[]byte` type. Specifically for proofs.
`MerkleRoot` provides helpers, such as retrieving the `Sum(sumTrie bool)uint64`
to interface with data it captures. However, for the SMT it **always** panics,
as there is no sum.

## Proofs

Expand Down Expand Up @@ -478,33 +477,4 @@ the [`kvstore`](../kvstore/) directory.

#### SimpleMap

This library defines the `SimpleMap` interface which is implemented as an
extremely simple in-memory key-value store.

Although it is a submodule, it is ideal for simple, testing or non-production
use cases. It is used in the tests throughout the library.

See [simplemap.go](../kvstore/simplemap/simplemap.go) for the implementation
details.

#### Badger

This library defines the `BadgerStore` interface which is implemented as a
wrapper around the [BadgerDB](https://github.com/dgraph-io/badger) v4 key-value
database. It's interface exposes numerous extra methods not used by the trie,
However it can still be used as a node-store with both in-memory and persistent
options.

See [badger-store.md](./badger-store.md.md) for the details of the implementation.

### Data Loss

In the event of a system crash or unexpected failure of the program utilising
the SMT, if the `Commit()` function has not been called, any changes to the trie
will be lost. This is due to the underlying database not being changed **until**
the `Commit()` function is called and changes are persisted.

## Sparse Merkle Sum Trie

This library also implements a Sparse Merkle Sum Trie (SMST), the documentation
for which can be found [here](./merkle-sum-trie.md).
This l
77 changes: 60 additions & 17 deletions root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package smt_test

import (
"crypto/sha256"
"crypto/sha512"
"encoding/binary"
"fmt"
"hash"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -12,28 +14,69 @@ import (
"github.com/pokt-network/smt/kvstore/simplemap"
)

func TestMerkleRoot_SumTrie(t *testing.T) {
nodeStore := simplemap.NewSimpleMap()
trie := smt.NewSparseMerkleSumTrie(nodeStore, sha256.New())
for i := uint64(0); i < 10; i++ {
require.NoError(t, trie.Update([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i)), i))
func TestMerkleRoot_TrieTypes(t *testing.T) {
tests := []struct {
desc string
sumTree bool
hasher hash.Hash
expectedPanic string
}{
{
desc: "successfully: gets sum of sha256 hasher SMST",
sumTree: true,
hasher: sha256.New(),
expectedPanic: "",
},
{
desc: "successfully: gets sum of sha512 hasher SMST",
sumTree: true,
hasher: sha512.New(),
expectedPanic: "",
},
{
desc: "failure: panics for sha256 hasher SMT",
sumTree: false,
hasher: sha256.New(),
expectedPanic: "roo#sum: not a merkle sum trie",
},
{
desc: "failure: panics for sha512 hasher SMT",
sumTree: false,
hasher: sha512.New(),
expectedPanic: "roo#sum: not a merkle sum trie",
},
}
root := trie.Root()
require.Equal(t, root.Sum(true), getSumBzHelper(t, root))
}

func TestMerkleRoot_Trie(t *testing.T) {
nodeStore := simplemap.NewSimpleMap()
trie := smt.NewSparseMerkleTrie(nodeStore, sha256.New())
for i := 0; i < 10; i++ {
require.NoError(t, trie.Update([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i))))
for _, tt := range tests {
tt := tt
t.Run(tt.desc, func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, nodeStore.ClearAll())
})
if tt.sumTree {
trie := smt.NewSparseMerkleSumTrie(nodeStore, tt.hasher)
for i := uint64(0); i < 10; i++ {
require.NoError(t, trie.Update([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i)), i))
}
root := trie.Root()
require.Equal(t, root.Sum(), getSumBzHelper(t, root))
return
}
trie := smt.NewSparseMerkleTrie(nodeStore, tt.hasher)
for i := 0; i < 10; i++ {
require.NoError(t, trie.Update([]byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("value%d", i))))
}
if panicStr := recover(); panicStr != nil {
require.Equal(t, tt.expectedPanic, panicStr)
}
})
}
root := trie.Root()
require.Equal(t, root.Sum(false), uint64(0))
}

func getSumBzHelper(t *testing.T, r []byte) uint64 {
var sumbz [8]byte // Using sha256
copy(sumbz[:], []byte(r)[len([]byte(r))-8:]) // Using sha256 so - 8 bytes
return binary.BigEndian.Uint64(sumbz[:])
sumSize := len(r) % 32
sumBz := make([]byte, sumSize)
copy(sumBz[:], []byte(r)[len([]byte(r))-sumSize:])
return binary.BigEndian.Uint64(sumBz[:])
}
2 changes: 1 addition & 1 deletion smst.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,5 @@ func (smst *SMST) Root() MerkleRoot {
// Sum returns the uint64 sum of the entire trie
func (smst *SMST) Sum() uint64 {
digest := smst.Root()
return digest.Sum(true)
return digest.Sum()
}
17 changes: 9 additions & 8 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ var (
// MerkleRoot is a type alias for a byte slice returned from the Root method
type MerkleRoot []byte

// Sum returns the uint64 sum of the merkle root, if sumTrie is true
// otherwise it returns 0, as it would result in undefined behaviour.
func (r MerkleRoot) Sum(sumTrie bool) uint64 {
if sumTrie {
var sumbz [sumSize]byte
copy(sumbz[:], []byte(r)[len([]byte(r))-sumSize:])
return binary.BigEndian.Uint64(sumbz[:])
// Sum returns the uint64 sum of the merkle root, it checks the length of the
// merkle root and if it is no the same as the size of the SMST's expected
// root hash it will panic.
func (r MerkleRoot) Sum() uint64 {
if len(r)%32 == 0 {
panic("roo#sum: not a merkle sum trie")
}
return 0
var sumbz [sumSize]byte
copy(sumbz[:], []byte(r)[len([]byte(r))-sumSize:])
return binary.BigEndian.Uint64(sumbz[:])
}

// SparseMerkleTrie represents a Sparse Merkle Trie.
Expand Down

0 comments on commit b8ada3a

Please sign in to comment.