package arbo
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"math"
|
|
"math/big"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
qt "github.com/frankban/quicktest"
|
|
"go.vocdoni.io/dvote/db"
|
|
"go.vocdoni.io/dvote/db/badgerdb"
|
|
"go.vocdoni.io/dvote/db/pebbledb"
|
|
)
|
|
|
|
func checkRootBIString(c *qt.C, tree *Tree, expected string) {
|
|
root, err := tree.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
rootBI := BytesToBigInt(root)
|
|
c.Check(rootBI.String(), qt.Equals, expected)
|
|
}
|
|
|
|
func TestDBTx(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
dbBadger, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
testDBTx(c, dbBadger)
|
|
|
|
dbPebble, err := pebbledb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
testDBTx(c, dbPebble)
|
|
}
|
|
|
|
func testDBTx(c *qt.C, database db.Database) {
|
|
wTx := database.WriteTx()
|
|
defer wTx.Discard()
|
|
|
|
_, err := wTx.Get([]byte("a"))
|
|
c.Assert(err, qt.Equals, db.ErrKeyNotFound)
|
|
|
|
err = wTx.Set([]byte("a"), []byte("b"))
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
v, err := wTx.Get([]byte("a"))
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(v, qt.DeepEquals, []byte("b"))
|
|
}
|
|
|
|
func TestAddTestVectors(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
// Poseidon test vectors generated using https://github.com/iden3/circomlib smt.js
|
|
testVectorsPoseidon := []string{
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
"13578938674299138072471463694055224830892726234048532520316387704878000008795",
|
|
"5412393676474193513566895793055462193090331607895808993925969873307089394741",
|
|
"14204494359367183802864593755198662203838502594566452929175967972147978322084",
|
|
}
|
|
testAdd(c, HashFunctionPoseidon, testVectorsPoseidon)
|
|
|
|
testVectorsSha256 := []string{
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
"46910109172468462938850740851377282682950237270676610513794735904325820156367",
|
|
"59481735341404520835410489183267411392292882901306595567679529387376287440550",
|
|
"20573794434149960984975763118181266662429997821552560184909083010514790081771",
|
|
}
|
|
testAdd(c, HashFunctionSha256, testVectorsSha256)
|
|
}
|
|
|
|
func testAdd(c *qt.C, hashFunc HashFunction, testVectors []string) {
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, hashFunc)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
root, err := tree.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(hex.EncodeToString(root), qt.Equals, testVectors[0])
|
|
|
|
bLen := 32
|
|
err = tree.Add(
|
|
BigIntToBytes(bLen, big.NewInt(1)),
|
|
BigIntToBytes(bLen, big.NewInt(2)))
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, tree, testVectors[1])
|
|
|
|
err = tree.Add(
|
|
BigIntToBytes(bLen, big.NewInt(33)),
|
|
BigIntToBytes(bLen, big.NewInt(44)))
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, tree, testVectors[2])
|
|
|
|
err = tree.Add(
|
|
BigIntToBytes(bLen, big.NewInt(1234)),
|
|
BigIntToBytes(bLen, big.NewInt(9876)))
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, tree, testVectors[3])
|
|
}
|
|
|
|
func TestAddBatch(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(0))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
checkRootBIString(c, tree,
|
|
"296519252211642170490407814696803112091039265640052570497930797516015811235")
|
|
|
|
database, err = badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree2, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree2.db.Close() //nolint:errcheck
|
|
|
|
var keys, values [][]byte
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(0))
|
|
keys = append(keys, k)
|
|
values = append(values, v)
|
|
}
|
|
indexes, err := tree2.AddBatch(keys, values)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(len(indexes), qt.Equals, 0)
|
|
|
|
checkRootBIString(c, tree2,
|
|
"296519252211642170490407814696803112091039265640052570497930797516015811235")
|
|
}
|
|
|
|
func TestAddDifferentOrder(t *testing.T) {
|
|
c := qt.New(t)
|
|
database1, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree1, err := NewTree(database1, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree1.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
for i := 0; i < 16; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(0))
|
|
if err := tree1.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
database2, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree2, err := NewTree(database2, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree2.db.Close() //nolint:errcheck
|
|
|
|
for i := 16 - 1; i >= 0; i-- {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(0))
|
|
if err := tree2.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
root1, err := tree1.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
root2, err := tree2.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(hex.EncodeToString(root2), qt.Equals, hex.EncodeToString(root1))
|
|
c.Check(hex.EncodeToString(root1), qt.Equals,
|
|
"3b89100bec24da9275c87bc188740389e1d5accfc7d88ba5688d7fa96a00d82f")
|
|
}
|
|
|
|
func TestAddRepeatedIndex(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(3)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(12)))
|
|
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
err = tree.Add(k, v) // repeating same key-value
|
|
c.Check(err, qt.Equals, ErrKeyAlreadyExists)
|
|
}
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(20)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(12)))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
v = BigIntToBytes(bLen, big.NewInt(int64(11)))
|
|
err = tree.Update(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
gettedKey, gettedValue, err := tree.Get(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(gettedKey, qt.DeepEquals, k)
|
|
c.Check(gettedValue, qt.DeepEquals, v)
|
|
|
|
// add more leafs to the tree to do another test
|
|
for i := 0; i < 16; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i*2)))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(3)))
|
|
v = BigIntToBytes(bLen, big.NewInt(int64(11)))
|
|
// check that before the Update, value for 3 is !=11
|
|
gettedKey, gettedValue, err = tree.Get(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(gettedKey, qt.DeepEquals, k)
|
|
c.Check(gettedValue, qt.Not(qt.DeepEquals), v)
|
|
c.Check(gettedValue, qt.DeepEquals, BigIntToBytes(bLen, big.NewInt(6)))
|
|
|
|
err = tree.Update(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// check that after Update, the value for 3 is ==11
|
|
gettedKey, gettedValue, err = tree.Get(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(gettedKey, qt.DeepEquals, k)
|
|
c.Check(gettedValue, qt.DeepEquals, v)
|
|
c.Check(gettedValue, qt.DeepEquals, BigIntToBytes(bLen, big.NewInt(11)))
|
|
}
|
|
|
|
func TestAux(t *testing.T) { // TODO split in proper tests
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(1)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(0)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(256)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(257)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(515)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(770)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(388)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
k = BigIntToBytes(bLen, big.NewInt(int64(900)))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
//
|
|
// err = tree.PrintGraphviz(nil)
|
|
// c.Assert(err, qt.IsNil)
|
|
}
|
|
|
|
func TestGet(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
for i := 0; i < 10; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i*2)))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(7)))
|
|
gettedKey, gettedValue, err := tree.Get(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(gettedKey, qt.DeepEquals, k)
|
|
c.Check(gettedValue, qt.DeepEquals, BigIntToBytes(bLen, big.NewInt(int64(7*2))))
|
|
}
|
|
|
|
func TestGenProofAndVerify(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
for i := 0; i < 10; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i*2)))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(7)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(14)))
|
|
kAux, proofV, siblings, existence, err := tree.GenProof(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(proofV, qt.DeepEquals, v)
|
|
c.Assert(k, qt.DeepEquals, kAux)
|
|
c.Assert(existence, qt.IsTrue)
|
|
|
|
root, err := tree.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
verif, err := CheckProof(tree.hashFunction, k, v, root, siblings)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(verif, qt.IsTrue)
|
|
}
|
|
|
|
func TestDumpAndImportDump(t *testing.T) {
|
|
c := qt.New(t)
|
|
database1, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree1, err := NewTree(database1, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree1.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
for i := 0; i < 16; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i*2)))
|
|
if err := tree1.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
e, err := tree1.Dump(nil)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
database2, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree2, err := NewTree(database2, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree2.db.Close() //nolint:errcheck
|
|
err = tree2.ImportDump(e)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
root1, err := tree1.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
root2, err := tree2.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(root2, qt.DeepEquals, root1)
|
|
c.Check(hex.EncodeToString(root2), qt.Equals,
|
|
"0d93aaa3362b2f999f15e15728f123087c2eee716f01c01f56e23aae07f09f08")
|
|
}
|
|
|
|
func TestRWMutex(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
var keys, values [][]byte
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(0))
|
|
keys = append(keys, k)
|
|
values = append(values, v)
|
|
}
|
|
go func() {
|
|
_, err = tree.AddBatch(keys, values)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(99999)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(99999)))
|
|
if err := tree.Add(k, v); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TODO UPDATE
|
|
// func TestSetGetNLeafs(t *testing.T) {
|
|
// c := qt.New(t)
|
|
// database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
// c.Assert(err, qt.IsNil)
|
|
// tree, err := NewTree(database, 100, HashFunctionPoseidon)
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// // 0
|
|
// tree.dbBatch = tree.db.NewBatch()
|
|
//
|
|
// err = tree.setNLeafs(0)
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// err = tree.dbBatch.Write()
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// n, err := tree.GetNLeafs()
|
|
// c.Assert(err, qt.IsNil)
|
|
// c.Assert(n, qt.Equals, 0)
|
|
//
|
|
// // 1024
|
|
// tree.dbBatch = tree.db.NewBatch()
|
|
//
|
|
// err = tree.setNLeafs(1024)
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// err = tree.dbBatch.Write()
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// n, err = tree.GetNLeafs()
|
|
// c.Assert(err, qt.IsNil)
|
|
// c.Assert(n, qt.Equals, 1024)
|
|
//
|
|
// // 2**64 -1
|
|
// tree.dbBatch = tree.db.NewBatch()
|
|
//
|
|
// maxUint := ^uint(0)
|
|
// maxInt := int(maxUint >> 1)
|
|
//
|
|
// err = tree.setNLeafs(maxInt)
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// err = tree.dbBatch.Write()
|
|
// c.Assert(err, qt.IsNil)
|
|
//
|
|
// n, err = tree.GetNLeafs()
|
|
// c.Assert(err, qt.IsNil)
|
|
// c.Assert(n, qt.Equals, maxInt)
|
|
// }
|
|
|
|
func TestAddBatchFullyUsed(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
database1, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree1, err := NewTree(database1, 4, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
database2, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree2, err := NewTree(database2, 4, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
var keys, values [][]byte
|
|
for i := 0; i < 16; i++ {
|
|
k := BigIntToBytes(1, big.NewInt(int64(i)))
|
|
v := k
|
|
|
|
keys = append(keys, k)
|
|
values = append(values, v)
|
|
|
|
// add one by one expecting no error
|
|
err := tree1.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
}
|
|
|
|
invalids, err := tree2.AddBatch(keys, values)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(0, qt.Equals, len(invalids))
|
|
|
|
root1, err := tree1.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
root2, err := tree2.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(root1, qt.DeepEquals, root2)
|
|
|
|
// get all key-values and check that are equal between both trees
|
|
for i := 0; i < 16; i++ {
|
|
auxK1, auxV1, err := tree1.Get(BigIntToBytes(1, big.NewInt(int64(i))))
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
auxK2, auxV2, err := tree2.Get(BigIntToBytes(1, big.NewInt(int64(i))))
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
c.Assert(auxK1, qt.DeepEquals, auxK2)
|
|
c.Assert(auxV1, qt.DeepEquals, auxV2)
|
|
}
|
|
|
|
// try adding one more key to both trees (through Add & AddBatch) and
|
|
// expect not being added due the tree is already full
|
|
k := BigIntToBytes(1, big.NewInt(int64(16)))
|
|
v := k
|
|
err = tree1.Add(k, v)
|
|
c.Assert(err, qt.Equals, ErrMaxVirtualLevel)
|
|
|
|
invalids, err = tree2.AddBatch([][]byte{k}, [][]byte{v})
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(1, qt.Equals, len(invalids))
|
|
}
|
|
|
|
func TestSetRoot(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
expectedRoot := "13742386369878513332697380582061714160370929283209286127733983161245560237407"
|
|
|
|
// fill the tree
|
|
bLen := 32
|
|
var keys, values [][]byte
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
keys = append(keys, k)
|
|
values = append(values, v)
|
|
}
|
|
indexes, err := tree.AddBatch(keys, values)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(len(indexes), qt.Equals, 0)
|
|
checkRootBIString(c, tree,
|
|
expectedRoot)
|
|
|
|
// add one more k-v
|
|
k := BigIntToBytes(bLen, big.NewInt(1000))
|
|
v := BigIntToBytes(bLen, big.NewInt(1000))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, tree,
|
|
"10747149055773881257049574592162159501044114324358186833013814735296193179713")
|
|
|
|
// do a SetRoot, and expect the same root than the original tree
|
|
pastRootBI, ok := new(big.Int).SetString(expectedRoot, 10)
|
|
c.Assert(ok, qt.IsTrue)
|
|
pastRoot := BigIntToBytes(32, pastRootBI)
|
|
|
|
err = tree.SetRoot(pastRoot)
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, tree, expectedRoot)
|
|
|
|
// check that the tree can be updated
|
|
err = tree.Add([]byte("test"), []byte("test"))
|
|
c.Assert(err, qt.IsNil)
|
|
err = tree.Update([]byte("test"), []byte("test"))
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// check that the k-v '1000' does not exist in the new tree
|
|
_, _, err = tree.Get(k)
|
|
c.Assert(err, qt.Equals, ErrKeyNotFound)
|
|
|
|
// check that can be set an empty root
|
|
err = tree.SetRoot(tree.emptyHash)
|
|
c.Assert(err, qt.IsNil)
|
|
}
|
|
|
|
func TestSnapshot(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// fill the tree
|
|
bLen := 32
|
|
var keys, values [][]byte
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
keys = append(keys, k)
|
|
values = append(values, v)
|
|
}
|
|
indexes, err := tree.AddBatch(keys, values)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Check(len(indexes), qt.Equals, 0)
|
|
checkRootBIString(c, tree,
|
|
"13742386369878513332697380582061714160370929283209286127733983161245560237407")
|
|
|
|
// do a snapshot, and expect the same root than the original tree
|
|
snapshotTree, err := tree.Snapshot(nil)
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, snapshotTree,
|
|
"13742386369878513332697380582061714160370929283209286127733983161245560237407")
|
|
|
|
// check that the snapshotTree can not be updated
|
|
_, err = snapshotTree.AddBatch(keys, values)
|
|
c.Assert(err, qt.Equals, ErrSnapshotNotEditable)
|
|
err = snapshotTree.Add([]byte("test"), []byte("test"))
|
|
c.Assert(err, qt.Equals, ErrSnapshotNotEditable)
|
|
err = snapshotTree.Update([]byte("test"), []byte("test"))
|
|
c.Assert(err, qt.Equals, ErrSnapshotNotEditable)
|
|
err = snapshotTree.ImportDump(nil)
|
|
c.Assert(err, qt.Equals, ErrSnapshotNotEditable)
|
|
|
|
// update the original tree by adding a new key-value, and check that
|
|
// snapshotTree still has the old root, but the original tree has a new
|
|
// root
|
|
err = tree.Add([]byte("test"), []byte("test"))
|
|
c.Assert(err, qt.IsNil)
|
|
checkRootBIString(c, snapshotTree,
|
|
"13742386369878513332697380582061714160370929283209286127733983161245560237407")
|
|
checkRootBIString(c, tree,
|
|
"1025190963769001718196479367844646783678188389989148142691917685159698888868")
|
|
}
|
|
|
|
func TestGetFromSnapshotExpectArboErrKeyNotFound(t *testing.T) {
|
|
c := qt.New(t)
|
|
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 256, HashFunctionPoseidon)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
bLen := 32
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(3)))
|
|
|
|
root, err := tree.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err = tree.Snapshot(root)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
_, _, err = tree.Get(k)
|
|
c.Assert(err, qt.Equals, ErrKeyNotFound) // and not equal to db.ErrKeyNotFound
|
|
}
|
|
|
|
func TestKeyLen(t *testing.T) {
|
|
c := qt.New(t)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
// maxLevels is 100, keyPath length = ceil(maxLevels/8) = 13
|
|
maxLevels := 100
|
|
tree, err := NewTree(database, maxLevels, HashFunctionBlake2b)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// expect no errors when adding a key of only 4 bytes (when the
|
|
// required length of keyPath for 100 levels would be 13 bytes)
|
|
bLen := 4
|
|
k := BigIntToBytes(bLen, big.NewInt(1))
|
|
v := BigIntToBytes(bLen, big.NewInt(1))
|
|
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
err = tree.Update(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
_, _, _, _, err = tree.GenProof(k)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
_, _, err = tree.Get(k)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
k = BigIntToBytes(bLen, big.NewInt(2))
|
|
v = BigIntToBytes(bLen, big.NewInt(2))
|
|
invalids, err := tree.AddBatch([][]byte{k}, [][]byte{v})
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(len(invalids), qt.Equals, 0)
|
|
|
|
// expect errors when adding a key bigger than maximum capacity of the
|
|
// tree: ceil(maxLevels/8)
|
|
maxLevels = 32
|
|
database, err = badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err = NewTree(database, maxLevels, HashFunctionBlake2b)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
maxKeyLen := int(math.Ceil(float64(maxLevels) / float64(8))) //nolint:gomnd
|
|
k = BigIntToBytes(maxKeyLen+1, big.NewInt(1))
|
|
v = BigIntToBytes(maxKeyLen+1, big.NewInt(1))
|
|
|
|
expectedErrMsg := "len(k) can not be bigger than ceil(maxLevels/8)," +
|
|
" where len(k): 5, maxLevels: 32, max key len=ceil(maxLevels/8): 4." +
|
|
" Might need a bigger tree depth (maxLevels>=40) in order to input" +
|
|
" keys of length 5"
|
|
|
|
err = tree.Add(k, v)
|
|
c.Assert(err.Error(), qt.Equals, expectedErrMsg)
|
|
|
|
err = tree.Update(k, v)
|
|
c.Assert(err.Error(), qt.Equals, expectedErrMsg)
|
|
|
|
_, _, _, _, err = tree.GenProof(k)
|
|
c.Assert(err.Error(), qt.Equals, expectedErrMsg)
|
|
|
|
_, _, err = tree.Get(k)
|
|
c.Assert(err.Error(), qt.Equals, expectedErrMsg)
|
|
|
|
// check AddBatch with few key-values
|
|
invalids, err = tree.AddBatch([][]byte{k}, [][]byte{v})
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(len(invalids), qt.Equals, 1)
|
|
|
|
// check AddBatch with many key-values
|
|
nCPU := flp2(runtime.NumCPU())
|
|
nKVs := nCPU + 1
|
|
var ks, vs [][]byte
|
|
for i := 0; i < nKVs; i++ {
|
|
ks = append(ks, BigIntToBytes(maxKeyLen+1, big.NewInt(1)))
|
|
vs = append(vs, BigIntToBytes(maxKeyLen+1, big.NewInt(1)))
|
|
}
|
|
invalids, err = tree.AddBatch(ks, vs)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(len(invalids), qt.Equals, nKVs)
|
|
|
|
// check that with maxKeyLen it can be added
|
|
k = BigIntToBytes(maxKeyLen, big.NewInt(1))
|
|
err = tree.Add(k, v)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// check CheckProof check with key longer
|
|
kAux, vAux, packedSiblings, existence, err := tree.GenProof(k)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(existence, qt.IsTrue)
|
|
|
|
root, err := tree.Root()
|
|
c.Assert(err, qt.IsNil)
|
|
verif, err := CheckProof(tree.HashFunction(), kAux, vAux, root, packedSiblings)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(verif, qt.IsTrue)
|
|
|
|
// use a similar key but with one zero, expect that CheckProof fails on
|
|
// the verification
|
|
kAux = append(kAux, 0)
|
|
verif, err = CheckProof(tree.HashFunction(), kAux, vAux, root, packedSiblings)
|
|
c.Assert(err, qt.IsNil)
|
|
c.Assert(verif, qt.IsFalse)
|
|
}
|
|
|
|
func TestKeyLenBiggerThan32(t *testing.T) {
|
|
c := qt.New(t)
|
|
maxLevels := 264
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, maxLevels, HashFunctionBlake2b)
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
bLen := 33
|
|
err = tree.Add(
|
|
randomBytes(bLen),
|
|
randomBytes(bLen))
|
|
c.Assert(err, qt.IsNil)
|
|
|
|
// 2nd key that we add, will find a node with len(key)==32 (due the
|
|
// hash output size, expect that next Add does not give any error, as
|
|
// internally it will use a keyPath of size corresponent to the
|
|
// maxLevels size of the tree
|
|
err = tree.Add(
|
|
randomBytes(bLen),
|
|
randomBytes(bLen))
|
|
c.Assert(err, qt.IsNil)
|
|
}
|
|
|
|
func BenchmarkAdd(b *testing.B) {
|
|
bLen := 32 // for both Poseidon & Sha256
|
|
// prepare inputs
|
|
var ks, vs [][]byte
|
|
for i := 0; i < 1000; i++ {
|
|
k := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
v := BigIntToBytes(bLen, big.NewInt(int64(i)))
|
|
ks = append(ks, k)
|
|
vs = append(vs, v)
|
|
}
|
|
|
|
b.Run("Poseidon", func(b *testing.B) {
|
|
benchmarkAdd(b, HashFunctionPoseidon, ks, vs)
|
|
})
|
|
b.Run("Sha256", func(b *testing.B) {
|
|
benchmarkAdd(b, HashFunctionSha256, ks, vs)
|
|
})
|
|
}
|
|
|
|
func benchmarkAdd(b *testing.B, hashFunc HashFunction, ks, vs [][]byte) {
|
|
c := qt.New(b)
|
|
database, err := badgerdb.New(db.Options{Path: c.TempDir()})
|
|
c.Assert(err, qt.IsNil)
|
|
tree, err := NewTree(database, 140, hashFunc)
|
|
c.Assert(err, qt.IsNil)
|
|
defer tree.db.Close() //nolint:errcheck
|
|
|
|
for i := 0; i < len(ks); i++ {
|
|
if err := tree.Add(ks[i], vs[i]); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|