Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

statetrie: trie struct, node structs, and add operation #5767

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-2024 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 (
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/statetrie/nibbles"
"sync"
)

// 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

Check warning on line 48 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L47-L48

Added lines #L47 - L48 were not covered by tests
}
func (ba *backingNode) add(mt *Trie, pathKey nibbles.Nibbles, remainingKey nibbles.Nibbles, valueHash crypto.Digest) (node, error) {

Check warning on line 50 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L50

Added line #L50 was not covered by tests
// will be provided in the subsequent backing store PR
return nil, nil

Check warning on line 52 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L52

Added line #L52 was not covered by tests
}
func (ba *backingNode) hashing() error {
return nil

Check warning on line 55 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L54-L55

Added lines #L54 - L55 were not covered by tests
}
func (ba *backingNode) getKey() nibbles.Nibbles {
return ba.key

Check warning on line 58 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L57-L58

Added lines #L57 - L58 were not covered by tests
}
func (ba *backingNode) getHash() *crypto.Digest {
return &ba.hash
}
func (ba *backingNode) serialize() ([]byte, error) {
panic("backingNode cannot be serialized")

Check warning on line 64 in crypto/statetrie/backing.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/backing.go#L63-L64

Added lines #L63 - L64 were not covered by tests
}
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-2024 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

Check warning on line 56 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L56

Added line #L56 was not covered by tests
}

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

Check warning on line 80 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L80

Added line #L80 was not covered by tests
}
// 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

Check warning on line 100 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L100

Added line #L100 was not covered by tests
}
}
}
bytes, err := bn.serialize()
if err != nil {
return err

Check warning on line 106 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L106

Added line #L106 was not covered by tests
}
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 {

Check failure on line 116 in crypto/statetrie/branch.go

View workflow job for this annotation

GitHub Actions / reviewdog-warnings

[Lint Warnings] reported by reviewdog 🐶 func `deserializeBranchNode` is unused (unused) Raw Output: crypto/statetrie/branch.go:116:6: func `deserializeBranchNode` is unused (unused) func deserializeBranchNode(data []byte, key nibbles.Nibbles) *branchNode { ^
if data[0] != 5 {
panic("invalid prefix for branch node")

Check warning on line 118 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L116-L118

Added lines #L116 - L118 were not covered by tests
}
if len(data) < (1 + 17*crypto.DigestSize) {
panic("data too short to be a branch node")

Check warning on line 121 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L120-L121

Added lines #L120 - L121 were not covered by tests
}

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

Check warning on line 126 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L124-L126

Added lines #L124 - L126 were not covered by tests

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)

Check warning on line 132 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L128-L132

Added lines #L128 - L132 were not covered by tests
}
}
var valueHash crypto.Digest
copy(valueHash[:], data[(1+16*crypto.DigestSize):(1+17*crypto.DigestSize)])
return makeBranchNode(children, valueHash, key)

Check warning on line 137 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L135-L137

Added lines #L135 - L137 were not covered by tests
}

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

Check warning on line 142 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L141-L142

Added lines #L141 - L142 were not covered by tests
}

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

Check warning on line 166 in crypto/statetrie/branch.go

View check run for this annotation

Codecov / codecov/patch

crypto/statetrie/branch.go#L165-L166

Added lines #L165 - L166 were not covered by tests
}

// 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
Loading