@ -0,0 +1,11 @@ |
|||||
|
module github.com/arnaucube/kesto/treeinterface/arbotree |
||||
|
|
||||
|
go 1.16 |
||||
|
|
||||
|
// replace github.com/arnaucube/arbo => ../../../arbo |
||||
|
|
||||
|
require ( |
||||
|
github.com/arnaucube/arbo v0.0.0-20210508150807-6dcbbdf4a51a // indirect |
||||
|
github.com/iden3/go-merkletree v0.0.0-20210308143313-8b63ca866189 |
||||
|
go.vocdoni.io/dvote v0.6.1-0.20210508104027-72380d23da7f |
||||
|
) |
@ -0,0 +1,142 @@ |
|||||
|
// Package arbotree provides the functions for creating and managing an arbo
|
||||
|
// merkletree adapted to the CensusTree interface
|
||||
|
package arbotree |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"encoding/hex" |
||||
|
"fmt" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
"sync/atomic" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/arnaucube/arbo" |
||||
|
"github.com/iden3/go-merkletree/db/pebble" |
||||
|
"go.vocdoni.io/dvote/censustree" |
||||
|
) |
||||
|
|
||||
|
type Tree struct { |
||||
|
Tree *arbo.Tree |
||||
|
public uint32 |
||||
|
lastAccessUnix int64 // a unix timestamp, used via sync/atomic
|
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Init(name, storageDir string) error { |
||||
|
dbDir := filepath.Join(storageDir, "arbotree.db."+strings.TrimSpace(name)) |
||||
|
storage, err := pebble.NewPebbleStorage(dbDir, false) // TODO TMP
|
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
mt, err := arbo.NewTree(storage, 140, arbo.HashFunctionBlake2b) // TODO here the hash function would depend on the usage
|
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
t.Tree = mt |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) MaxKeySize() int { |
||||
|
return t.Tree.HashFunction().Len() |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) LastAccess() int64 { |
||||
|
return atomic.LoadInt64(&t.lastAccessUnix) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) updateAccessTime() { |
||||
|
atomic.StoreInt64(&t.lastAccessUnix, time.Now().Unix()) |
||||
|
} |
||||
|
|
||||
|
// Publish makes a merkle tree available for queries.
|
||||
|
// Application layer should check IsPublish() before considering the Tree available.
|
||||
|
func (t *Tree) Publish() { |
||||
|
atomic.StoreUint32(&t.public, 1) |
||||
|
} |
||||
|
|
||||
|
// UnPublish makes a merkle tree not available for queries
|
||||
|
func (t *Tree) UnPublish() { |
||||
|
atomic.StoreUint32(&t.public, 0) |
||||
|
} |
||||
|
|
||||
|
// IsPublic returns true if the tree is available
|
||||
|
func (t *Tree) IsPublic() bool { |
||||
|
return atomic.LoadUint32(&t.public) == 1 |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Add(index, value []byte) error { |
||||
|
t.updateAccessTime() |
||||
|
return t.Tree.Add(index, value) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) AddBatch(indexes, values [][]byte) ([]int, error) { |
||||
|
t.updateAccessTime() |
||||
|
return t.Tree.AddBatch(indexes, values) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) GenProof(index, value []byte) ([]byte, error) { |
||||
|
t.updateAccessTime() |
||||
|
v, siblings, err := t.Tree.GenProof(index) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
if !bytes.Equal(v, value) { |
||||
|
return nil, fmt.Errorf("value does not match %s!=%s", hex.EncodeToString(v), hex.EncodeToString(value)) |
||||
|
} |
||||
|
return siblings, nil |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) CheckProof(index, value, root, mproof []byte) (bool, error) { |
||||
|
t.updateAccessTime() |
||||
|
if root == nil { |
||||
|
root = t.Root() |
||||
|
} |
||||
|
return arbo.CheckProof(t.Tree.HashFunction(), index, value, root, mproof) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Root() []byte { |
||||
|
t.updateAccessTime() |
||||
|
return t.Tree.Root() |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Dump(root []byte) ([]byte, error) { |
||||
|
return t.Tree.Dump() // TODO pass root once arbo is updated
|
||||
|
} |
||||
|
|
||||
|
func (t *Tree) DumpPlain(root []byte) ([][]byte, [][]byte, error) { |
||||
|
t.updateAccessTime() |
||||
|
var indexes, values [][]byte |
||||
|
// TODO pass root once arbo is updated
|
||||
|
err := t.Tree.Iterate(func(k, v []byte) { |
||||
|
if v[0] != arbo.PrefixValueLeaf { |
||||
|
return |
||||
|
} |
||||
|
leafK, leafV := arbo.ReadLeafValue(v) |
||||
|
indexes = append(indexes, leafK) |
||||
|
values = append(values, leafV) |
||||
|
}) |
||||
|
return indexes, values, err |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) ImportDump(data []byte) error { |
||||
|
t.updateAccessTime() |
||||
|
return t.Tree.ImportDump(data) |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Size(root []byte) (int64, error) { |
||||
|
// TODO
|
||||
|
return 0, nil |
||||
|
} |
||||
|
|
||||
|
func (t *Tree) Snapshot(root []byte) (censustree.Tree, error) { |
||||
|
// TODO
|
||||
|
return t, nil |
||||
|
} |
||||
|
func (t *Tree) HashExists(hash []byte) (bool, error) { |
||||
|
_, _, err := t.Tree.Get(hash) |
||||
|
if err != nil { |
||||
|
return false, err |
||||
|
} |
||||
|
return true, nil |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package arbotree |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
|
||||
|
"go.vocdoni.io/dvote/censustree" |
||||
|
) |
||||
|
|
||||
|
func TestInterface(t *testing.T) { |
||||
|
storage := t.TempDir() |
||||
|
tree := &Tree{} |
||||
|
err := tree.Init("test", storage) |
||||
|
if err != nil { |
||||
|
t.Fatal(err) |
||||
|
} |
||||
|
|
||||
|
var i interface{} = tree |
||||
|
_, ok := i.(censustree.Tree) |
||||
|
if !ok { |
||||
|
t.Fatal("censustree interface not matched by arbotree wrapper") |
||||
|
} |
||||
|
} |