Skip to content

Commit

Permalink
trie nodes add oper impl
Browse files Browse the repository at this point in the history
  • Loading branch information
bbroder-uji committed Oct 2, 2023
1 parent 79fb8fe commit a9700c1
Show file tree
Hide file tree
Showing 9 changed files with 1,774 additions and 0 deletions.
360 changes: 360 additions & 0 deletions crypto/statetrie/README.md

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions crypto/statetrie/backing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package statetrie

import (
"sync"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/statetrie/nibbles"
)

// Backing nodes are placeholders for nodes that have been stored in the
// backing store. All we need is the full key of the node and its hash.
type backingNode struct {
key nibbles.Nibbles
hash crypto.Digest
}

var backingNodePool = sync.Pool{
New: func() interface{} {
return &backingNode{
key: make(nibbles.Nibbles, 0),
}
},
}

func makeBackingNode(hash crypto.Digest, key nibbles.Nibbles) *backingNode {
stats.makebanodes++
ba := backingNodePool.Get().(*backingNode)
ba.hash = hash
ba.key = append(ba.key[:0], key...)
return ba
}
func (ba *backingNode) setHash(hash crypto.Digest) {
ba.hash = hash
}
func (ba *backingNode) add(mt *Trie, pathKey nibbles.Nibbles, remainingKey nibbles.Nibbles, valueHash crypto.Digest) (node, error) {
// will be provided in the subsequent backing store PR
return nil, nil
}
func (ba *backingNode) hashing() error {
return nil
}
func (ba *backingNode) getKey() nibbles.Nibbles {
return ba.key
}
func (ba *backingNode) getHash() *crypto.Digest {
return &ba.hash
}
func (ba *backingNode) serialize() ([]byte, error) {
panic("backingNode cannot be serialized")
}
174 changes: 174 additions & 0 deletions crypto/statetrie/branch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package statetrie

import (
"bytes"

"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/statetrie/nibbles"
)

type branchNode struct {
children [16]node
valueHash crypto.Digest
key nibbles.Nibbles
hash crypto.Digest
}

// makeBranchNode creates a branch node with the provided children nodes, valueHash,
// and full key.
func makeBranchNode(children [16]node, valueHash crypto.Digest, key nibbles.Nibbles) *branchNode {
stats.makebranches++
bn := &branchNode{children: children, valueHash: valueHash, key: make(nibbles.Nibbles, len(key))}
copy(bn.key, key)
return bn
}
func (bn *branchNode) add(mt *Trie, pathKey nibbles.Nibbles, remainingKey nibbles.Nibbles, valueHash crypto.Digest) (node, error) {
//Three operational transitions:
//
//- BN.ADD.1: Store the new value in the branch node value slot. This overwrites
// the branch node slot value.
//
//- BN.ADD.2: Make a new leaf node with the new value, and point an available
// branch child slot at it. This stores a new leaf node in a child slot.
//
//- BN.ADD.3: This repoints the child node to a new/existing node resulting from
// performing the Add operation on the child node.
if len(remainingKey) == 0 {
// If we're here, then set the value hash in this node, overwriting the old one.
if bn.valueHash == valueHash {
// If it is the same value, do not zero the hash
return bn, nil
}

bn.valueHash = valueHash
// transition BN.ADD.1
bn.hash = crypto.Digest{}
return bn, nil
}

// Otherwise, shift out the first nibble and check the children for it.
shifted := nibbles.ShiftLeft(remainingKey, 1)
slot := remainingKey[0]
if bn.children[slot] == nil {
// nil children are available.
lnKey := pathKey[:]
lnKey = append(lnKey, slot)

// transition BN.ADD.2
bn.hash = crypto.Digest{}
bn.children[slot] = makeLeafNode(shifted, valueHash, lnKey)
} else {
// Not available. Descend down the branch.
replacement, err := bn.children[slot].add(mt, append(pathKey, remainingKey[0]), shifted, valueHash)
if err != nil {
return nil, err
}
// If the replacement hash is zero, zero the branch node hash
if replacement.getHash().IsZero() {
bn.hash = crypto.Digest{}
}
// transition BN.ADD.3
bn.children[slot] = replacement
}

return bn, nil
}

// hashing serializes the node and then hashes it, storing the hash in the node.
func (bn *branchNode) hashing() error {
if bn.hash.IsZero() {
for i := 0; i < 16; i++ {
if bn.children[i] != nil && bn.children[i].getHash().IsZero() {
err := bn.children[i].hashing()
if err != nil {
return err
}
}
}
bytes, err := bn.serialize()
if err != nil {
return err
}
stats.cryptohashes++
bn.hash = crypto.Hash(bytes)
}
return nil
}

// deserializeBranchNode turns a data array and its key in the trie into
// a branch node.
func deserializeBranchNode(data []byte, key nibbles.Nibbles) *branchNode {
if data[0] != 5 {
panic("invalid prefix for branch node")
}
if len(data) < (1 + 17*crypto.DigestSize) {
panic("data too short to be a branch node")
}

var children [16]node
for i := 0; i < 16; i++ {
var hash crypto.Digest

copy(hash[:], data[1+i*crypto.DigestSize:(1+crypto.DigestSize)+i*crypto.DigestSize])
if !hash.IsZero() {
chKey := key[:]
chKey = append(chKey, byte(i))
children[i] = makeBackingNode(hash, chKey)
}
}
var valueHash crypto.Digest
copy(valueHash[:], data[(1+16*crypto.DigestSize):(1+17*crypto.DigestSize)])
return makeBranchNode(children, valueHash, key)
}

// setHash sets the value of the hash for the node.
func (bn *branchNode) setHash(hash crypto.Digest) {
bn.hash = hash
}

var bnbuffer bytes.Buffer

func (bn *branchNode) serialize() ([]byte, error) {
bnbuffer.Reset()
var empty crypto.Digest
prefix := byte(5)

bnbuffer.WriteByte(prefix)
for i := 0; i < 16; i++ {
if bn.children[i] != nil {
bnbuffer.Write(bn.children[i].getHash().ToSlice())
} else {
bnbuffer.Write(empty[:])
}
}
bnbuffer.Write(bn.valueHash[:])
return bnbuffer.Bytes(), nil
}

// getKey gets the nibbles of the full key for this node.
func (bn *branchNode) getKey() nibbles.Nibbles {
return bn.key
}

// getHash gets the hash for this node. If the hash has not been set by a
// hashing operation like branchNode.hashing, getHash will not calculate it
// (instead it will return the empty hash, crypto.Digest{})
func (bn *branchNode) getHash() *crypto.Digest {
return &bn.hash
}
Loading

0 comments on commit a9700c1

Please sign in to comment.