ceremonyclient/node/store/hypergraph.go
2025-02-18 19:07:48 -06:00

1264 lines
29 KiB
Go

package store
import (
"bytes"
"encoding/gob"
"fmt"
"math/big"
"github.com/pkg/errors"
"go.uber.org/zap"
"source.quilibrium.com/quilibrium/monorepo/node/crypto"
"source.quilibrium.com/quilibrium/monorepo/node/hypergraph/application"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
type HypergraphStore interface {
NewTransaction(indexed bool) (Transaction, error)
LoadVertexTree(id []byte) (
*crypto.RawVectorCommitmentTree,
error,
)
LoadVertexData(id []byte) ([]application.Encrypted, error)
SaveVertexTree(
txn Transaction,
id []byte,
vertTree *crypto.RawVectorCommitmentTree,
) error
CommitAndSaveVertexData(
txn Transaction,
id []byte,
data []application.Encrypted,
) (*crypto.RawVectorCommitmentTree, []byte, error)
LoadHypergraph() (
*application.Hypergraph,
error,
)
SaveHypergraph(
txn Transaction,
hg *application.Hypergraph,
) error
GetBranchNode(id NodeID) (*StoredBranchNode, error)
GetLeafNode(id NodeID) (*StoredLeafNode, error)
BatchWrite(
txn Transaction,
branches map[NodeID]*StoredBranchNode,
leaves map[NodeID]*StoredLeafNode,
deletions map[NodeID]struct{},
) error
}
var _ HypergraphStore = (*PebbleHypergraphStore)(nil)
type PebbleHypergraphStore struct {
db KVDB
logger *zap.Logger
}
func NewPebbleHypergraphStore(
db KVDB,
logger *zap.Logger,
) *PebbleHypergraphStore {
return &PebbleHypergraphStore{
db,
logger,
}
}
const (
HYPERGRAPH_SHARD = 0x09
VERTEX_ADDS = 0x00
VERTEX_REMOVES = 0x10
VERTEX_DATA = 0xF0
HYPEREDGE_ADDS = 0x01
HYPEREDGE_REMOVES = 0x11
SET_TREE_ROOT = 0x00
SET_TREE_BRANCH = 0x01
SET_TREE_LEAF = 0x02
)
func hypergraphVertexAddsKey(shardKey application.ShardKey) []byte {
key := []byte{HYPERGRAPH_SHARD, VERTEX_ADDS}
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
return key
}
func hypergraphVertexDataKey(id []byte) []byte {
key := []byte{HYPERGRAPH_SHARD, VERTEX_DATA}
key = append(key, id...)
return key
}
func hypergraphVertexRemovesKey(shardKey application.ShardKey) []byte {
key := []byte{HYPERGRAPH_SHARD, VERTEX_REMOVES}
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
return key
}
func hypergraphHyperedgeAddsKey(shardKey application.ShardKey) []byte {
key := []byte{HYPERGRAPH_SHARD, HYPEREDGE_ADDS}
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
return key
}
func hypergraphHyperedgeRemovesKey(shardKey application.ShardKey) []byte {
key := []byte{HYPERGRAPH_SHARD, HYPEREDGE_REMOVES}
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
return key
}
func shardKeyFromKey(key []byte) application.ShardKey {
return application.ShardKey{
L1: [3]byte(key[3:6]),
L2: [32]byte(key[6:]),
}
}
func SetTreeBranchKey(
shardKey application.ShardKey,
phaseSet byte,
prefix []int,
) NodeID {
key := []byte{HYPERGRAPH_SHARD, phaseSet}
if len(prefix) == 0 {
key = append(key, SET_TREE_ROOT)
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
return NodeID(key)
}
key = append(key, SET_TREE_BRANCH)
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
key = append(key, packNibbles(prefix)...)
return NodeID(key)
}
func SetTreeLeafKey(
shardKey application.ShardKey,
phaseSet byte,
leafKey []byte,
) NodeID {
key := []byte{HYPERGRAPH_SHARD, phaseSet}
key = append(key, SET_TREE_LEAF)
key = append(key, shardKey.L1[:]...)
key = append(key, shardKey.L2[:]...)
key = append(key, leafKey...)
return NodeID(key)
}
func (p *PebbleHypergraphStore) NewTransaction(indexed bool) (
Transaction,
error,
) {
return p.db.NewBatch(indexed), nil
}
func (p *PebbleHypergraphStore) LoadVertexTree(id []byte) (
*crypto.RawVectorCommitmentTree,
error,
) {
tree := &crypto.RawVectorCommitmentTree{}
var b bytes.Buffer
vertexData, closer, err := p.db.Get(hypergraphVertexDataKey(id))
if err != nil {
return nil, errors.Wrap(err, "load vertex data")
}
defer closer.Close()
b.Write(vertexData)
dec := gob.NewDecoder(&b)
if err := dec.Decode(tree); err != nil {
return nil, errors.Wrap(err, "load vertex data")
}
return tree, nil
}
func (p *PebbleHypergraphStore) LoadVertexData(id []byte) (
[]application.Encrypted,
error,
) {
tree := &crypto.RawVectorCommitmentTree{}
var b bytes.Buffer
vertexData, closer, err := p.db.Get(hypergraphVertexDataKey(id))
if err != nil {
return nil, errors.Wrap(err, "load vertex data")
}
defer closer.Close()
b.Write(vertexData)
dec := gob.NewDecoder(&b)
if err := dec.Decode(tree); err != nil {
return nil, errors.Wrap(err, "load vertex data")
}
encData := []application.Encrypted{}
for _, d := range crypto.GetAllLeaves(tree) {
verencData := crypto.MPCitHVerEncFromBytes(d.Value)
encData = append(encData, verencData)
}
return encData, nil
}
func (p *PebbleHypergraphStore) SaveVertexTree(
txn Transaction,
id []byte,
vertTree *crypto.RawVectorCommitmentTree,
) error {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(vertTree); err != nil {
return errors.Wrap(err, "save vertex tree")
}
return errors.Wrap(
txn.Set(hypergraphVertexDataKey(id), buf.Bytes()),
"save vertex tree",
)
}
func (p *PebbleHypergraphStore) CommitAndSaveVertexData(
txn Transaction,
id []byte,
data []application.Encrypted,
) (*crypto.RawVectorCommitmentTree, []byte, error) {
dataTree := application.EncryptedToVertexTree(data)
commit := dataTree.Commit(false)
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(dataTree); err != nil {
return nil, nil, errors.Wrap(err, "commit and save vertex data")
}
return dataTree, commit, errors.Wrap(
txn.Set(hypergraphVertexDataKey(id), buf.Bytes()),
"commit and save vertex data",
)
}
func (p *PebbleHypergraphStore) LoadHypergraph() (
*application.Hypergraph,
error,
) {
hg := application.NewHypergraph(
func(
shardKey application.ShardKey,
phaseSet protobufs.HypergraphPhaseSet,
) crypto.VectorCommitmentTree {
return NewPersistentVectorTree(p, shardKey, phaseSet)
},
)
vertexAddsIter, err := p.db.NewIter(
[]byte{HYPERGRAPH_SHARD, VERTEX_ADDS, SET_TREE_ROOT},
[]byte{HYPERGRAPH_SHARD, VERTEX_ADDS, SET_TREE_BRANCH},
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
defer vertexAddsIter.Close()
for vertexAddsIter.First(); vertexAddsIter.Valid(); vertexAddsIter.Next() {
shardKey := make([]byte, len(vertexAddsIter.Key()))
copy(shardKey, vertexAddsIter.Key())
node := &StoredBranchNode{}
var b bytes.Buffer
b.Write(vertexAddsIter.Value())
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
tree := NewPersistentVectorTree(
p,
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_ADDS,
)
tree.tree.Root, err = tree.storedToBranch(node)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
err := hg.SetIdSet(
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_ADDS,
tree,
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
}
vertexRemovesIter, err := p.db.NewIter(
[]byte{HYPERGRAPH_SHARD, VERTEX_REMOVES, SET_TREE_ROOT},
[]byte{HYPERGRAPH_SHARD, VERTEX_REMOVES, SET_TREE_BRANCH},
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
defer vertexRemovesIter.Close()
for vertexRemovesIter.First(); vertexRemovesIter.Valid(); vertexRemovesIter.Next() {
shardKey := make([]byte, len(vertexRemovesIter.Key()))
copy(shardKey, vertexRemovesIter.Key())
node := &StoredBranchNode{}
var b bytes.Buffer
b.Write(vertexRemovesIter.Value())
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
tree := NewPersistentVectorTree(
p,
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_REMOVES,
)
tree.tree.Root, err = tree.storedToBranch(node)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
err := hg.SetIdSet(
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_REMOVES,
tree,
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
}
hyperedgeAddsIter, err := p.db.NewIter(
[]byte{HYPERGRAPH_SHARD, HYPEREDGE_ADDS, SET_TREE_ROOT},
[]byte{HYPERGRAPH_SHARD, HYPEREDGE_ADDS, SET_TREE_BRANCH},
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
defer hyperedgeAddsIter.Close()
for hyperedgeAddsIter.First(); hyperedgeAddsIter.Valid(); hyperedgeAddsIter.Next() {
shardKey := make([]byte, len(hyperedgeAddsIter.Key()))
copy(shardKey, hyperedgeAddsIter.Key())
node := &StoredBranchNode{}
var b bytes.Buffer
b.Write(hyperedgeAddsIter.Value())
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
tree := NewPersistentVectorTree(
p,
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_ADDS,
)
tree.tree.Root, err = tree.storedToBranch(node)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
err := hg.SetIdSet(
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_ADDS,
tree,
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
}
hyperedgeRemovesIter, err := p.db.NewIter(
[]byte{HYPERGRAPH_SHARD, HYPEREDGE_REMOVES, SET_TREE_ROOT},
[]byte{HYPERGRAPH_SHARD, HYPEREDGE_REMOVES, SET_TREE_BRANCH},
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
defer hyperedgeRemovesIter.Close()
for hyperedgeRemovesIter.First(); hyperedgeRemovesIter.Valid(); hyperedgeRemovesIter.Next() {
shardKey := make([]byte, len(hyperedgeRemovesIter.Key()))
copy(shardKey, hyperedgeRemovesIter.Key())
node := &StoredBranchNode{}
var b bytes.Buffer
b.Write(hyperedgeRemovesIter.Value())
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
tree := NewPersistentVectorTree(
p,
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_REMOVES,
)
tree.tree.Root, err = tree.storedToBranch(node)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
err := hg.SetIdSet(
shardKeyFromKey(shardKey),
protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_REMOVES,
tree,
)
if err != nil {
return nil, errors.Wrap(err, "load hypergraph")
}
}
return hg, nil
}
func (p *PebbleHypergraphStore) SaveHypergraph(
txn Transaction,
hg *application.Hypergraph,
) error {
hg.Commit()
for _, vertexAdds := range hg.GetVertexAdds() {
if vertexAdds.IsDirty() {
err := vertexAdds.GetTree().(*PersistentVectorTree).WriteBatch(txn)
if err != nil {
return errors.Wrap(err, "save hypergraph")
}
}
}
for _, vertexRemoves := range hg.GetVertexRemoves() {
if vertexRemoves.IsDirty() {
err := vertexRemoves.GetTree().(*PersistentVectorTree).WriteBatch(txn)
if err != nil {
return errors.Wrap(err, "save hypergraph")
}
}
}
for _, hyperedgeAdds := range hg.GetHyperedgeAdds() {
if hyperedgeAdds.IsDirty() {
err := hyperedgeAdds.GetTree().(*PersistentVectorTree).WriteBatch(txn)
if err != nil {
return errors.Wrap(err, "save hypergraph")
}
}
}
for _, hyperedgeRemoves := range hg.GetHyperedgeRemoves() {
if hyperedgeRemoves.IsDirty() {
err := hyperedgeRemoves.GetTree().(*PersistentVectorTree).WriteBatch(txn)
if err != nil {
return errors.Wrap(err, "save hypergraph")
}
}
}
return nil
}
func (p *PebbleHypergraphStore) GetBranchNode(id NodeID) (
*StoredBranchNode,
error,
) {
data, closer, err := p.db.Get([]byte(id))
if err != nil {
return nil, errors.Wrap(err, "get branch node")
}
defer closer.Close()
node := &StoredBranchNode{}
var b bytes.Buffer
b.Write(data)
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "get branch node")
}
return node, nil
}
func (p *PebbleHypergraphStore) GetLeafNode(id NodeID) (
*StoredLeafNode,
error,
) {
data, closer, err := p.db.Get([]byte(id))
if err != nil {
return nil, errors.Wrap(err, "get branch node")
}
defer closer.Close()
node := &StoredLeafNode{}
var b bytes.Buffer
b.Write(data)
dec := gob.NewDecoder(&b)
if err := dec.Decode(node); err != nil {
return nil, errors.Wrap(err, "get branch node")
}
return node, nil
}
func (p *PebbleHypergraphStore) BatchWrite(
txn Transaction,
branches map[NodeID]*StoredBranchNode,
leaves map[NodeID]*StoredLeafNode,
deletions map[NodeID]struct{},
) error {
for id := range deletions {
if err := txn.Delete([]byte(id)); err != nil {
return errors.Wrap(err, "batch write")
}
}
for id, node := range branches {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(node); err != nil {
return errors.Wrap(err, "batch write")
}
if err := txn.Set([]byte(id), buf.Bytes()); err != nil {
return errors.Wrap(err, "batch write")
}
}
for id, node := range leaves {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(node); err != nil {
return errors.Wrap(err, "batch write")
}
if err := txn.Set([]byte(id), buf.Bytes()); err != nil {
return errors.Wrap(err, "batch write")
}
}
return nil
}
// StoredBranchNode represents the serializable form of a branch node.
// Storage of trees necessarily includes the metadata because trees can be
// lazily loaded, and this metadata is sometimes the only information we need.
// Commitments should also be stored due to time cost of calculation.
type StoredBranchNode struct {
Prefix []int
Commitment []byte
ChildrenIDs [crypto.NodesPerBranch]NodeID
Size []byte
LeafCount int
LongestBranch int
}
// StoredLeafNode represents the serializable form of a leaf node.
// Storage of leaves necessarily includes the value of the data as we may not
// be dynamically resolving the underlying indirect reference the value
// represents with lazy loading, but the distinction between the value and hash
// target (if present) may be important.
type StoredLeafNode struct {
Commitment []byte
Key []byte
Value []byte
HashTarget []byte
Size []byte
}
type NodeID string
type PersistentVectorTree struct {
store HypergraphStore
tree *crypto.RawVectorCommitmentTree
shardKey application.ShardKey
phaseSet byte
addedBranches map[NodeID]*StoredBranchNode
addedLeaves map[NodeID]*StoredLeafNode
deletions map[NodeID]struct{}
}
var _ crypto.VectorCommitmentTree = (*PersistentVectorTree)(nil)
func NewPersistentVectorTree(
store HypergraphStore,
shardKey application.ShardKey,
phaseSet protobufs.HypergraphPhaseSet,
) *PersistentVectorTree {
phaseByte := byte(0x00)
switch phaseSet {
case protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_ADDS:
phaseByte = VERTEX_ADDS
case protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_VERTEX_REMOVES:
phaseByte = VERTEX_REMOVES
case protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_ADDS:
phaseByte = HYPEREDGE_ADDS
case protobufs.HypergraphPhaseSet_HYPERGRAPH_PHASE_SET_HYPEREDGE_REMOVES:
phaseByte = HYPEREDGE_REMOVES
}
return &PersistentVectorTree{
store: store,
tree: &crypto.RawVectorCommitmentTree{},
shardKey: shardKey,
phaseSet: phaseByte,
addedBranches: make(map[NodeID]*StoredBranchNode),
addedLeaves: make(map[NodeID]*StoredLeafNode),
deletions: make(map[NodeID]struct{}),
}
}
func serializeBigInt(n *big.Int) []byte {
if n == nil {
return nil
}
return n.FillBytes(make([]byte, 32))
}
func deserializeBigInt(data []byte) *big.Int {
if len(data) == 0 {
return big.NewInt(0)
}
return new(big.Int).SetBytes(data)
}
func branchToStored(
shardKey application.ShardKey,
phaseSet byte,
prefix []int,
node *crypto.VectorCommitmentBranchNode,
) *StoredBranchNode {
stored := &StoredBranchNode{
Prefix: make([]int, len(node.Prefix)),
Commitment: node.Commitment,
ChildrenIDs: [crypto.NodesPerBranch]NodeID{},
Size: serializeBigInt(node.Size),
LeafCount: node.LeafCount,
LongestBranch: node.LongestBranch,
}
copy(stored.Prefix, node.Prefix)
for i, child := range node.Children {
if child == nil {
continue
}
switch c := child.(type) {
case *crypto.VectorCommitmentBranchNode:
stored.ChildrenIDs[i] = SetTreeBranchKey(
shardKey,
phaseSet,
append(append(append([]int{}, prefix...), node.Prefix...), i),
)
case *crypto.VectorCommitmentLeafNode:
stored.ChildrenIDs[i] = SetTreeLeafKey(
shardKey,
phaseSet,
c.Key,
)
}
}
return stored
}
func leafToStored(node *crypto.VectorCommitmentLeafNode) *StoredLeafNode {
return &StoredLeafNode{
Key: node.Key,
Commitment: node.Commitment,
Value: node.Value,
HashTarget: node.HashTarget,
Size: serializeBigInt(node.Size),
}
}
func (t *PersistentVectorTree) storedToBranch(
stored *StoredBranchNode,
) (*crypto.VectorCommitmentBranchNode, error) {
node := &crypto.VectorCommitmentBranchNode{
Prefix: stored.Prefix,
Commitment: stored.Commitment,
Children: [crypto.NodesPerBranch]crypto.VectorCommitmentNode{},
Size: deserializeBigInt(stored.Size),
LeafCount: stored.LeafCount,
LongestBranch: stored.LongestBranch,
}
for i, childID := range stored.ChildrenIDs {
if childID == "" {
continue
}
var child crypto.VectorCommitmentNode
var err error
if childID[2] == SET_TREE_BRANCH {
child, err = t.loadBranchNode(childID)
} else {
child, err = t.loadLeafNode(childID)
}
if err != nil {
return nil, err
}
node.Children[i] = child
}
return node, nil
}
func storedToLeaf(stored *StoredLeafNode) *crypto.VectorCommitmentLeafNode {
return &crypto.VectorCommitmentLeafNode{
Key: stored.Key,
Commitment: stored.Commitment,
Value: stored.Value,
HashTarget: stored.HashTarget,
Size: deserializeBigInt(stored.Size),
}
}
func (t *PersistentVectorTree) loadBranchNode(
id NodeID,
) (*crypto.VectorCommitmentBranchNode, error) {
stored, err := t.store.GetBranchNode(id)
if err != nil {
return nil, fmt.Errorf("failed to load branch node %s: %w", id, err)
}
return t.storedToBranch(stored)
}
func (t *PersistentVectorTree) loadLeafNode(id NodeID) (
*crypto.VectorCommitmentLeafNode,
error,
) {
stored, err := t.store.GetLeafNode(id)
if err != nil {
return nil, errors.Wrap(
errors.Wrap(
err,
fmt.Sprintf("failed to load leaf node %s", id),
),
"load leaf node",
)
}
return storedToLeaf(stored), nil
}
func (t *PersistentVectorTree) Load() error {
stored, err := t.store.GetBranchNode(SetTreeBranchKey(
t.shardKey,
t.phaseSet,
[]int{},
))
if err != nil {
return err
}
root, err := t.storedToBranch(stored)
if err != nil {
return err
}
t.tree.Root = root
return nil
}
func (t *PersistentVectorTree) trackNodeChanges(
oldPrefix, newPrefix []int,
oldNode, newNode crypto.VectorCommitmentNode,
) {
if oldNode == nil && newNode == nil {
return
}
// deletions first
if oldNode != nil {
var oldID NodeID
switch n := oldNode.(type) {
case *crypto.VectorCommitmentBranchNode:
oldID = SetTreeBranchKey(
t.shardKey,
t.phaseSet,
oldPrefix,
)
case *crypto.VectorCommitmentLeafNode:
oldID = SetTreeLeafKey(
t.shardKey,
t.phaseSet,
n.Key,
)
}
if _, ok := t.addedBranches[oldID]; ok {
delete(t.addedBranches, oldID)
} else if _, ok := t.addedLeaves[oldID]; ok {
delete(t.addedLeaves, oldID)
} else {
t.deletions[oldID] = struct{}{}
}
}
// then additions
if newNode != nil {
switch n := newNode.(type) {
case *crypto.VectorCommitmentBranchNode:
id := SetTreeBranchKey(
t.shardKey,
t.phaseSet,
newPrefix,
)
t.addedBranches[id] = branchToStored(
t.shardKey,
t.phaseSet,
newPrefix,
n,
)
case *crypto.VectorCommitmentLeafNode:
id := SetTreeLeafKey(
t.shardKey,
t.phaseSet,
n.Key,
)
t.addedLeaves[id] = leafToStored(n)
}
}
}
func (t *PersistentVectorTree) Get(key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, errors.New("empty key not allowed")
}
var get func(
prefix []int,
node crypto.VectorCommitmentNode,
depth int,
) ([]byte, error)
get = func(
prefix []int,
node crypto.VectorCommitmentNode,
depth int,
) ([]byte, error) {
if node == nil {
return nil, nil
}
switch n := node.(type) {
case *crypto.VectorCommitmentLeafNode:
if bytes.Equal(n.Key, key) {
return n.Value, nil
}
return nil, nil
case *crypto.VectorCommitmentBranchNode:
// Check prefix match
for i, expectedNibble := range n.Prefix {
if crypto.GetNextNibble(
key,
depth+i*crypto.BranchBits,
) != expectedNibble {
return nil, nil
}
}
// Get final nibble after prefix
finalNibble := crypto.GetNextNibble(
key,
depth+len(n.Prefix)*crypto.BranchBits,
)
isLoaded := false
for _, c := range n.Children {
if c != nil {
isLoaded = true
break
}
}
if !isLoaded {
var self *crypto.VectorCommitmentBranchNode
var err error
if len(prefix) == 0 {
self, err = t.loadBranchNode(
SetTreeBranchKey(
t.shardKey,
t.phaseSet,
append([]int{}, prefix...),
),
)
} else {
self, err = t.loadBranchNode(
SetTreeBranchKey(
t.shardKey,
t.phaseSet,
append(
append([]int{}, prefix...),
n.Prefix...,
),
),
)
}
if err != nil {
return nil, errors.Wrap(err, "get")
}
n.Children = self.Children
}
return get(
append(
append(
append([]int{}, prefix...),
n.Prefix...,
),
finalNibble,
),
n.Children[finalNibble],
depth+len(n.Prefix)*crypto.BranchBits+crypto.BranchBits,
)
}
return nil, nil
}
value, err := get([]int{}, t.tree.Root, 0)
if err != nil {
return nil, err
}
if value == nil {
return nil, errors.New("key not found")
}
return value, nil
}
func (t *PersistentVectorTree) Insert(
key, value, hashTarget []byte,
size *big.Int,
) error {
if len(key) == 0 {
return errors.New("empty key not allowed")
}
var insert func(
prefix []int,
node crypto.VectorCommitmentNode,
depth int,
) (crypto.VectorCommitmentNode, error)
insert = func(
prefix []int,
node crypto.VectorCommitmentNode,
depth int,
) (crypto.VectorCommitmentNode, error) {
if node == nil {
t.trackNodeChanges(
nil,
prefix,
nil,
&crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
},
)
return &crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
}, nil
}
switch n := node.(type) {
case *crypto.VectorCommitmentLeafNode:
if bytes.Equal(n.Key, key) {
n.Value = value
n.HashTarget = hashTarget
n.Commitment = nil
n.Size = size
t.trackNodeChanges(nil, prefix, nil, n)
return n, nil
}
// Get common prefix nibbles and divergence point
sharedNibbles, divergeDepth := crypto.GetNibblesUntilDiverge(
n.Key,
key,
depth,
)
// Create single branch node with shared prefix
branch := &crypto.VectorCommitmentBranchNode{
Prefix: sharedNibbles,
LeafCount: 2,
LongestBranch: 1,
Size: new(big.Int).Add(n.Size, size),
}
// Add both leaves at their final positions
finalOldNibble := crypto.GetNextNibble(n.Key, divergeDepth)
finalNewNibble := crypto.GetNextNibble(key, divergeDepth)
branch.Children[finalOldNibble] = n
branch.Children[finalNewNibble] = &crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
}
t.trackNodeChanges(
nil,
append(append(append([]int{}, prefix...), sharedNibbles...), finalOldNibble),
nil,
n,
)
t.trackNodeChanges(
nil,
append(append(append([]int{}, prefix...), sharedNibbles...), finalNewNibble),
nil,
&crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
},
)
t.trackNodeChanges(
nil,
prefix,
nil,
branch,
)
return branch, nil
case *crypto.VectorCommitmentBranchNode:
isLoaded := false
for _, c := range n.Children {
if c != nil {
isLoaded = true
break
}
}
if !isLoaded {
self, err := t.loadBranchNode(
SetTreeBranchKey(
t.shardKey,
t.phaseSet,
append(
append([]int{}, prefix...),
n.Prefix...,
),
),
)
if err != nil {
return nil, errors.Wrap(err, "insert")
}
n.Children = self.Children
}
if len(n.Prefix) > 0 {
// Check if the new key matches the prefix
for i, expectedNibble := range n.Prefix {
actualNibble := crypto.GetNextNibble(key, depth+i*crypto.BranchBits)
if actualNibble != expectedNibble {
// Create new branch with shared prefix subset
newBranch := &crypto.VectorCommitmentBranchNode{
Prefix: n.Prefix[:i],
LeafCount: n.LeafCount + 1,
LongestBranch: n.LongestBranch + 1,
Size: new(big.Int).Add(n.Size, size),
}
// Position old branch and new leaf
newBranch.Children[expectedNibble] = n
n.Prefix = n.Prefix[i+1:] // remove shared prefix from old branch
newBranch.Children[actualNibble] = &crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
}
t.trackNodeChanges(
prefix,
append(append(append([]int{}, prefix...), newBranch.Prefix...), expectedNibble),
n,
n,
)
t.trackNodeChanges(
nil,
append(append(append([]int{}, prefix...), newBranch.Prefix...), actualNibble),
nil,
&crypto.VectorCommitmentLeafNode{
Key: key,
Value: value,
HashTarget: hashTarget,
Size: size,
},
)
t.trackNodeChanges(
nil,
prefix,
nil,
newBranch,
)
return newBranch, nil
}
}
// Key matches prefix, continue with final nibble
finalNibble := crypto.GetNextNibble(
key,
depth+len(n.Prefix)*crypto.BranchBits,
)
inserted, err := insert(
append(
append(
append([]int{}, prefix...),
n.Prefix...,
),
finalNibble,
),
n.Children[finalNibble],
depth+len(n.Prefix)*crypto.BranchBits+crypto.BranchBits,
)
if err != nil {
return nil, err
}
n.Children[finalNibble] = inserted
n.Commitment = nil
n.LeafCount += 1
switch i := inserted.(type) {
case *crypto.VectorCommitmentBranchNode:
if n.LongestBranch <= i.LongestBranch {
n.LongestBranch = i.LongestBranch + 1
}
case *crypto.VectorCommitmentLeafNode:
n.LongestBranch = 1
}
n.Size = n.Size.Add(n.Size, size)
t.trackNodeChanges(
nil,
prefix,
nil,
n,
)
return n, nil
} else {
// Simple branch without prefix
nibble := crypto.GetNextNibble(key, depth)
inserted, err := insert(
append(
append(
append([]int{}, prefix...),
n.Prefix...,
),
nibble,
),
n.Children[nibble],
depth+crypto.BranchBits,
)
if err != nil {
return nil, err
}
n.Children[nibble] = inserted
n.Commitment = nil
n.LeafCount += 1
switch i := inserted.(type) {
case *crypto.VectorCommitmentBranchNode:
if n.LongestBranch <= i.LongestBranch {
n.LongestBranch = i.LongestBranch + 1
}
case *crypto.VectorCommitmentLeafNode:
n.LongestBranch = 1
}
n.Size = n.Size.Add(n.Size, size)
t.trackNodeChanges(
nil,
prefix,
nil,
n,
)
return n, nil
}
}
return nil, nil
}
newRoot, err := insert([]int{}, t.tree.Root, 0)
if err != nil {
return err
}
t.tree.Root = newRoot
return nil
}
func (t *PersistentVectorTree) Delete(key []byte) error {
return errors.New("deletion not supported")
}
func (t *PersistentVectorTree) Commit(recalculate bool) []byte {
return t.tree.Commit(recalculate)
}
func (t *PersistentVectorTree) WriteBatch(txn Transaction) error {
err := t.store.BatchWrite(txn, t.addedBranches, t.addedLeaves, t.deletions)
if err != nil {
return errors.Wrap(err, "write batch")
}
// Reset change tracking
t.addedBranches = make(map[NodeID]*StoredBranchNode)
t.addedLeaves = make(map[NodeID]*StoredLeafNode)
t.deletions = make(map[NodeID]struct{})
return nil
}
func (t *PersistentVectorTree) GetMetadata() (
leafCount int,
longestBranch int,
) {
return t.tree.GetMetadata()
}
func (t *PersistentVectorTree) GetSize() *big.Int {
return t.tree.GetSize()
}
func (t *PersistentVectorTree) Prove(key []byte) [][]byte {
return t.tree.Prove(key)
}
func (t *PersistentVectorTree) Verify(key []byte, proofs [][]byte) bool {
return t.tree.Verify(key, proofs)
}
func (
t *PersistentVectorTree,
) GetInternalTree() *crypto.RawVectorCommitmentTree {
return t.tree
}
// this method is truncating the ints to bytes because the total size of the
// branch bits is 6. if this ever increases, this will break a lot of things.
func packNibbles(values []int) []byte {
out := []byte{}
for _, v := range values {
out = append(out, byte(v))
}
return out
}