Skip to content

Commit

Permalink
use nil in InternalNode to turn it into a stateless node
Browse files Browse the repository at this point in the history
  • Loading branch information
gballet committed Apr 19, 2023
1 parent 64f0e0b commit a27fc03
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 22 deletions.
1 change: 1 addition & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
errNotSupportedInStateless = errors.New("not implemented in stateless")
errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen")
errStatelessAndStatefulMix = errors.New("a stateless node should not be found in a stateful tree")
errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view")
)

const (
Expand Down
26 changes: 13 additions & 13 deletions proof_ipa.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,23 +335,23 @@ func TreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) {
}
}

root := NewStatelessWithCommitment(rootC)
root := NewStatelessInternal().(*InternalNode)
root.commitment = rootC
comms := proof.Cs
for _, p := range paths {
comms, err = root.insertStem(p, info[string(p)], comms)
if err != nil {
return nil, err
}
}
values := make([][]byte, NodeWidth)
for i, k := range proof.Keys {
if len(proof.Values[i]) == 0 {
// Skip the nil keys, they are here to prove
// an absence.
continue
}

for i, k := range proof.Keys {
if len(proof.Values[i]) == 0 {
// Skip the nil keys, they are here to prove
// an absence.
continue
if bytes.Equal(k, info[string(p)].stem) {
values[k[31]] = proof.Values[i]
}
}

err = root.insertValue(k, proof.Values[i])
comms, err = root.CreatePath(p, info[string(p)], comms, values)
if err != nil {
return nil, err
}
Expand Down
16 changes: 9 additions & 7 deletions stateless_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ func TestStatelessToDot(t *testing.T) {
}
}

// TODO move this to proof_test
func TestStatelessDeserialize(t *testing.T) {
root := New()
for _, k := range [][]byte{zeroKeyTest, oneKeyTest, fourtyKeyTest, ffx32KeyTest} {
Expand Down Expand Up @@ -278,11 +279,11 @@ func TestStatelessDeserialize(t *testing.T) {
t.Fatalf("differing root commitments %x != %x", droot.Commitment().Bytes(), root.Commitment().Bytes())
}

if !Equal(droot.(*StatelessNode).children[0].(*StatelessNode).commitment, root.(*InternalNode).children[0].Commit()) {
if !Equal(droot.(*InternalNode).children[0].(*LeafNode).commitment, root.(*InternalNode).children[0].Commit()) {
t.Fatal("differing commitment for child #0")
}

if !Equal(droot.(*StatelessNode).children[64].Commit(), root.(*InternalNode).children[64].Commit()) {
if !Equal(droot.(*InternalNode).children[64].Commit(), root.(*InternalNode).children[64].Commit()) {
t.Fatal("differing commitment for child #64")
}
}
Expand Down Expand Up @@ -313,13 +314,14 @@ func TestStatelessDeserializeMissginChildNode(t *testing.T) {
if !Equal(droot.Commit(), root.Commit()) {
t.Fatal("differing root commitments")
}

if !Equal(droot.(*StatelessNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) {
t.Log(ToDot(root))
t.Log(ToDot(droot))
if !Equal(droot.(*InternalNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) {
t.Fatal("differing commitment for child #0")
}

if droot.(*StatelessNode).children[64] != nil {
t.Fatal("non-nil child #64")
if droot.(*InternalNode).children[64] != nil {
t.Fatalf("non-nil child #64: %v", droot.(*InternalNode).children[64])
}
}

Expand Down Expand Up @@ -351,7 +353,7 @@ func TestStatelessDeserializeDepth2(t *testing.T) {
t.Fatal("differing root commitments")
}

if !Equal(droot.(*StatelessNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) {
if !Equal(droot.(*InternalNode).children[0].Commit(), root.(*InternalNode).children[0].Commit()) {
t.Fatal("differing commitment for child #0")
}
}
Expand Down
99 changes: 97 additions & 2 deletions tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ func New() VerkleNode {
return newInternalNode(0)
}

func NewStatelessInternal() VerkleNode {
node := new(InternalNode)
node.children = make([]VerkleNode, NodeWidth)
return node
}

// New creates a new leaf node
func NewLeafNode(stem []byte, values [][]byte) *LeafNode {
cfg := GetConfig()
Expand Down Expand Up @@ -296,6 +302,11 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes
n.cowChild(nChild)

switch child := n.children[nChild].(type) {
case nil:
// Finding nil in a tree means that this is a stateless tree, so
// some information is missing. Typically, this means that the proof
// is incorrect, or that there is a bug in tree reconstruction.
return errMissingNodeInStateless
case Empty:
n.children[nChild] = NewLeafNode(stem, values)
n.children[nChild].setDepth(n.depth + 1)
Expand Down Expand Up @@ -351,9 +362,86 @@ func (n *InternalNode) InsertStem(stem []byte, values [][]byte, resolver NodeRes
return nil
}

// CreatePath inserts a given stem in the tree, placing it as
// described by stemInfo. Its third parameters is the list of
// commitments that have not been assigned a node. It returns
// the same list, save the commitments that were consumed
// during this call.
func (n *InternalNode) CreatePath(path []byte, stemInfo stemInfo, comms []*Point, values [][]byte) ([]*Point, error) {
if len(path) == 0 {
return comms, errors.New("invalid path")
}

// path is 1 byte long, the leaf node must be created
if len(path) == 1 {
switch stemInfo.stemType & 3 {
case extStatusAbsentEmpty:
// nothing to do
case extStatusAbsentOther:
// insert poa stem
case extStatusPresent:
// insert stem
newchild := &LeafNode{
commitment: comms[0],
stem: stemInfo.stem,
values: values,
depth: n.depth + 1,
}
n.children[path[0]] = newchild
comms = comms[1:]
if stemInfo.has_c1 {
newchild.c1 = comms[0]
comms = comms[1:]
} else {
newchild.c1 = new(Point)
}
if stemInfo.has_c2 {
newchild.c2 = comms[0]
comms = comms[1:]
} else {
newchild.c2 = new(Point)
}
for b, value := range stemInfo.values {
newchild.values[b] = value
}
}
return comms, nil
}

switch child := n.children[path[0]].(type) {
case nil:
// create the child node if missing
n.children[path[0]] = &InternalNode{
children: make([]VerkleNode, NodeWidth),
depth: n.depth + 1,
commitment: comms[0],
}
comms = comms[1:]
case *InternalNode:
// nothing else to do
case *LeafNode:
return comms, fmt.Errorf("error rebuilding the tree from a proof: stem %x leads to an already-existing leaf node at depth %x", stemInfo.stem, n.depth)
default:
return comms, fmt.Errorf("error rebuilding the tree from a proof: stem %x leads to an unsupported node type %v", stemInfo.stem, child)
}

// This should only be used in the context of
// stateless nodes, so panic if another node
// type is found.
child := n.children[path[0]].(*InternalNode)

// recurse
return child.CreatePath(path[1:], stemInfo, comms, values)
}

func (n *InternalNode) GetStem(stem []byte, resolver NodeResolverFn) ([][]byte, error) {
nchild := offset2key(stem, n.depth) // index of the child pointed by the next byte in the key
switch child := n.children[nchild].(type) {
case nil:
// Finding nil in a tree means that this is a stateless tree, so
// some information is missing. Typically, this means that the proof
// is incorrect, or that there is a bug in tree reconstruction.
return nil, errMissingNodeInStateless
case Empty:
return nil, nil
case *HashedNode:
Expand Down Expand Up @@ -641,7 +729,11 @@ func (n *InternalNode) GetProofItems(keys keylist) (*ProofElements, []byte, [][]
var points [NodeWidth]*Point
for i, child := range n.children {
fiPtrs[i] = &fi[i]
points[i] = child.Commitment()
if child != nil {
points[i] = child.Commitment()
} else {
points[i] = new(Point)
}
}
toFrMultiple(fiPtrs[:], points[:])

Expand All @@ -665,7 +757,7 @@ func (n *InternalNode) GetProofItems(keys keylist) (*ProofElements, []byte, [][]

// Special case of a proof of absence: no children
// commitment, as the value is 0.
if _, ok := n.children[childIdx].(Empty); ok {
if _, ok := n.children[childIdx].(Empty); ok || n.children[childIdx] == nil {
// A question arises here: what if this proof of absence
// corresponds to several stems? Should the ext status be
// repeated as many times? It would be wasteful, so the
Expand Down Expand Up @@ -768,6 +860,9 @@ func (n *InternalNode) toDot(parent, path string) string {
}

for i, child := range n.children {
if child == nil {
continue
}
ret = fmt.Sprintf("%s%s", ret, child.toDot(me, fmt.Sprintf("%s%02x", path, i)))
}

Expand Down

0 comments on commit a27fc03

Please sign in to comment.