mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
trie: stack trie rewrite (#10)
* Base algorithm * Pass the initial tests * Works with all available tests * Fix missing headers * Various fixes after rebase * Fix issue coming out of goerli sync * Code cleanup and documentation * More comments * Remove outdated tests * Compare StdGenerate with ReStackTrie * Fix the benchmark tests (#11)
This commit is contained in:
parent
99a495501c
commit
6f337b56b1
3 changed files with 395 additions and 15 deletions
|
|
@ -39,8 +39,8 @@ func GenerateTrieRoot(it AccountIterator) common.Hash {
|
|||
return generateTrieRoot(it, StdGenerate)
|
||||
}
|
||||
|
||||
func CrosscheckTriehasher(it AccountIterator, begin,end int) bool {
|
||||
return verifyHasher(it, StackGenerate, begin, end)
|
||||
func CrosscheckTriehasher(it AccountIterator, begin, end int) bool {
|
||||
return verifyHasher(it, ReStackGenerate, begin, end)
|
||||
}
|
||||
|
||||
func generateTrieRoot(it AccountIterator, generatorFn trieGeneratorFn) common.Hash {
|
||||
|
|
@ -77,6 +77,17 @@ func generateTrieRoot(it AccountIterator, generatorFn trieGeneratorFn) common.Ha
|
|||
return result
|
||||
}
|
||||
|
||||
// ReStackGenerate is a hexary trie builder which is built from the
|
||||
// bottom-up as keys are added. It attempts to save memory by doing
|
||||
// the RLP encoding on the fly during hashing.
|
||||
func ReStackGenerate(in chan (leaf), out chan (common.Hash)) {
|
||||
t := trie.NewReStackTrie()
|
||||
for leaf := range in {
|
||||
t.TryUpdate(leaf.key[:], leaf.value)
|
||||
}
|
||||
out <- t.Hash()
|
||||
}
|
||||
|
||||
func verifyHasher(it AccountIterator, generatorFn trieGeneratorFn, begin, end int) bool {
|
||||
var (
|
||||
referenceFn = StdGenerate
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
|
|
@ -116,7 +117,7 @@ func TestMultipleStackTrieInsertion(t *testing.T) {
|
|||
|
||||
var got2 common.Hash
|
||||
it = head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
got2 = generateTrieRoot(it, StackGenerate)
|
||||
got2 = generateTrieRoot(it, ReStackGenerate)
|
||||
if got2 != got1 {
|
||||
t.Fatalf("Error: got %x exp %x", got2, got1)
|
||||
}
|
||||
|
|
@ -156,7 +157,12 @@ func BenchmarkTrieGeneration(b *testing.B) {
|
|||
snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(4000), nil)
|
||||
head := snaps.Snapshot(common.HexToHash("0x02"))
|
||||
// Call it once to make it create the lists before test starts
|
||||
head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
// Run the standard version once without the timer, to get the
|
||||
// correct value. This will warm the cache and make StdGenerate
|
||||
// appear a bit faster than it really is.
|
||||
exp := generateTrieRoot(it, StdGenerate)
|
||||
|
||||
b.Run("standard", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
|
@ -166,7 +172,7 @@ func BenchmarkTrieGeneration(b *testing.B) {
|
|||
got = generateTrieRoot(it, StdGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if exp := common.HexToHash("fecc4e1fce05c888c8acc8baa2d7677a531714668b7a09b5ede6e3e110be266b"); got != exp {
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
})
|
||||
|
|
@ -179,7 +185,7 @@ func BenchmarkTrieGeneration(b *testing.B) {
|
|||
got = generateTrieRoot(it, PruneGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if exp := common.HexToHash("fecc4e1fce05c888c8acc8baa2d7677a531714668b7a09b5ede6e3e110be266b"); got != exp {
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
|
||||
|
|
@ -190,10 +196,10 @@ func BenchmarkTrieGeneration(b *testing.B) {
|
|||
var got common.Hash
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
got = generateTrieRoot(it, StackGenerate)
|
||||
got = generateTrieRoot(it, ReStackGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if exp := common.HexToHash("fecc4e1fce05c888c8acc8baa2d7677a531714668b7a09b5ede6e3e110be266b"); got != exp {
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
|
||||
|
|
@ -204,29 +210,48 @@ func BenchmarkTrieGeneration(b *testing.B) {
|
|||
snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(10000), nil)
|
||||
head := snaps.Snapshot(common.HexToHash("0x02"))
|
||||
// Call it once to make it create the lists before test starts
|
||||
head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
// Run the standard version once without the timer, to get the
|
||||
// correct value. This will warm the cache and make StdGenerate
|
||||
// appear a bit faster than it really is.
|
||||
exp := generateTrieRoot(it, StdGenerate)
|
||||
b.Run("standard", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
var got common.Hash
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
generateTrieRoot(it, StdGenerate)
|
||||
got = generateTrieRoot(it, StdGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
})
|
||||
b.Run("pruning", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
var got common.Hash
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
generateTrieRoot(it, PruneGenerate)
|
||||
got = generateTrieRoot(it, PruneGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
})
|
||||
b.Run("stack", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
var got common.Hash
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := head.(*diffLayer).AccountIterator(common.HexToHash("0x00"))
|
||||
generateTrieRoot(it, StackGenerate)
|
||||
got = generateTrieRoot(it, ReStackGenerate)
|
||||
}
|
||||
b.StopTimer()
|
||||
if got != exp {
|
||||
b.Fatalf("Error: got %x exp %x", got, exp)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -242,7 +267,7 @@ func TestStackVsStandard(t *testing.T) {
|
|||
{key: "04f0862f9177d381deeed0e6af3b0751f3cce6887746ba13cf41aa1c4dbf6591", value: "f8440180a014baf10561054a68fe522434b4d4c25e1b377e745bf1d676afa71bc891cacf9ba0debc58a981ca4f637e282ab5985d169a0237d03ea9336bc3434d9dce79e62ab3"},
|
||||
{key: "04f0a6c0cb97e624bcb799f7d88717fe7fe4894877a8987a27d4792c36a2833e", value: "f8440180a0880595df1b6b3923e8036106cb641aae6b1249faa02d3217da8c556c0fff172ba06569f607421e3779a571977d84910e1177059946e0a064e487b1502e6a282623"},
|
||||
}
|
||||
stackT := trie.NewStackTrie()
|
||||
stackT := trie.NewReStackTrie()
|
||||
stdT, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New()))
|
||||
for _, kv := range vals {
|
||||
stackT.TryUpdate(common.FromHex(kv.key), common.FromHex(kv.value))
|
||||
|
|
@ -252,3 +277,17 @@ func TestStackVsStandard(t *testing.T) {
|
|||
t.Errorf("Hash mismatch, got %x, exp %x", got, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReStackTrieLeafInsert(t *testing.T) {
|
||||
root := trie.NewReStackTrie()
|
||||
root.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, []byte{248, 84, 136, 201, 58, 199, 94, 92, 47, 25, 82, 136, 85, 30, 0, 108, 3, 217, 199, 45, 160, 38, 197, 164, 24, 42, 129, 122, 66, 245, 69, 203, 198, 177, 205, 148, 164, 9, 87, 135, 151, 110, 131, 242, 141, 63, 75, 13, 236, 208, 24, 251, 99, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
root.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34}, []byte{248, 84, 136, 151, 135, 82, 39, 143, 175, 28, 19, 136, 27, 243, 181, 127, 169, 210, 240, 84, 160, 52, 216, 185, 7, 102, 228, 7, 49, 45, 109, 52, 74, 37, 153, 183, 176, 196, 229, 64, 42, 194, 181, 0, 219, 64, 95, 83, 159, 218, 232, 244, 135, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
root.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51}, []byte{248, 84, 136, 42, 104, 141, 120, 24, 60, 93, 100, 136, 113, 58, 195, 183, 81, 96, 180, 5, 160, 35, 125, 39, 98, 242, 32, 146, 145, 57, 131, 244, 142, 175, 147, 131, 149, 247, 74, 118, 76, 192, 96, 220, 249, 119, 73, 229, 183, 205, 104, 162, 122, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
ref, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New()))
|
||||
ref.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17}, []byte{248, 84, 136, 201, 58, 199, 94, 92, 47, 25, 82, 136, 85, 30, 0, 108, 3, 217, 199, 45, 160, 38, 197, 164, 24, 42, 129, 122, 66, 245, 69, 203, 198, 177, 205, 148, 164, 9, 87, 135, 151, 110, 131, 242, 141, 63, 75, 13, 236, 208, 24, 251, 99, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
ref.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34}, []byte{248, 84, 136, 151, 135, 82, 39, 143, 175, 28, 19, 136, 27, 243, 181, 127, 169, 210, 240, 84, 160, 52, 216, 185, 7, 102, 228, 7, 49, 45, 109, 52, 74, 37, 153, 183, 176, 196, 229, 64, 42, 194, 181, 0, 219, 64, 95, 83, 159, 218, 232, 244, 135, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
ref.TryUpdate([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51}, []byte{248, 84, 136, 42, 104, 141, 120, 24, 60, 93, 100, 136, 113, 58, 195, 183, 81, 96, 180, 5, 160, 35, 125, 39, 98, 242, 32, 146, 145, 57, 131, 244, 142, 175, 147, 131, 149, 247, 74, 118, 76, 192, 96, 220, 249, 119, 73, 229, 183, 205, 104, 162, 122, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112})
|
||||
if !bytes.Equal(ref.Hash().Bytes(), root.Hash().Bytes()) {
|
||||
t.Fatalf("Invalid hash, expected %s got %s", common.ToHex(ref.Hash().Bytes()), common.ToHex(root.Hash().Bytes()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ package trie
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
//"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// StackTrieItem represents an (extension, fullnode) tuple to be stored
|
||||
|
|
@ -60,7 +61,6 @@ func (st *StackTrie) TryUpdate(key, value []byte) error {
|
|||
panic("deletion not supported")
|
||||
}
|
||||
st.insert(&st.stack[0].ext, nil, k, valueNode(value))
|
||||
//fmt.Println("trie=", &st.stack[0].ext)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -261,3 +261,333 @@ func (st *StackTrie) Hash() common.Hash {
|
|||
h, _ := st.hasher.hash(&st.stack[0].ext, false)
|
||||
return common.BytesToHash(h.(hashNode))
|
||||
}
|
||||
|
||||
// ReStackTrie is a reimplementation of the Stacktrie, that fixes
|
||||
// bugs in the previous implementation, and which also implements
|
||||
// its own hashing mechanism which is more specific and hopefully
|
||||
// more efficient that the default hasher.
|
||||
type ReStackTrie struct {
|
||||
nodeType uint8 // node type (as in branch, ext, leaf)
|
||||
val []byte // value contained by this node if it's a leaf
|
||||
key []byte // key chunk covered by this (full|ext) node
|
||||
keyOffset int // offset of the key chunk inside a full key
|
||||
children [16]*ReStackTrie // list of children (for fullnodes and exts)
|
||||
}
|
||||
|
||||
// NewReStackTrie allocates and initializes an empty trie.
|
||||
func NewReStackTrie() *ReStackTrie {
|
||||
return &ReStackTrie{
|
||||
nodeType: 3,
|
||||
}
|
||||
}
|
||||
|
||||
// List all values that ReStackTrie#nodeType can hold
|
||||
const (
|
||||
branchNode = iota
|
||||
extNode
|
||||
leafNode
|
||||
emptyNode
|
||||
)
|
||||
|
||||
func (st *ReStackTrie) TryUpdate(key, value []byte) error {
|
||||
k := keybytesToHex(key)
|
||||
if len(value) == 0 {
|
||||
panic("deletion not supported")
|
||||
}
|
||||
st.insert(k[:len(k)-1], value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function that, given a full key, determines the index
|
||||
// at which the chunk pointed by st.keyOffset is different from
|
||||
// the same chunk in the full key.
|
||||
func (st *ReStackTrie) getDiffIndex(key []byte) int {
|
||||
diffindex := 0
|
||||
for ; diffindex < len(st.key) && st.key[diffindex] == key[st.keyOffset+diffindex]; diffindex++ {
|
||||
}
|
||||
return diffindex
|
||||
}
|
||||
|
||||
// Helper function to that inserts a (key, value) pair into
|
||||
// the trie.
|
||||
func (st *ReStackTrie) insert(key, value []byte) {
|
||||
switch st.nodeType {
|
||||
case branchNode: /* Branch */
|
||||
idx := key[st.keyOffset]
|
||||
if st.children[idx] == nil {
|
||||
st.children[idx] = NewReStackTrie()
|
||||
st.children[idx].keyOffset = st.keyOffset + 1
|
||||
}
|
||||
st.children[idx].insert(key, value)
|
||||
case extNode: /* Ext */
|
||||
// Compare both key chunks and see where they differ
|
||||
diffidx := st.getDiffIndex(key)
|
||||
|
||||
// Check if chunks are identical. If so, recurse into
|
||||
// the child node. Otherwise, the key has to be split
|
||||
// into 1) an optional common prefix, 2) the fullnode
|
||||
// representing the two differing path, and 3) a leaf
|
||||
// for each of the differentiated subtrees.
|
||||
if diffidx == len(st.key) {
|
||||
// Ext key and key segment are identical, recurse into
|
||||
// the child node.
|
||||
st.children[0].insert(key, value)
|
||||
} else {
|
||||
// Save the original part. Depending if the break is
|
||||
// at the extension's last byte or not, create an
|
||||
// intermediate extension or use the extension's child
|
||||
// node directly.
|
||||
var n *ReStackTrie
|
||||
if diffidx < len(st.key)-1 {
|
||||
n = NewReStackTrie()
|
||||
n.key = st.key[diffidx+1:]
|
||||
n.children[0] = st.children[0]
|
||||
n.nodeType = 1
|
||||
} else {
|
||||
// Break on the last byte, no need to insert
|
||||
// an extension node: reuse the current node
|
||||
n = st.children[0]
|
||||
}
|
||||
n.keyOffset = st.keyOffset + diffidx + 1
|
||||
|
||||
// Create a leaf for the inserted part
|
||||
o := NewReStackTrie()
|
||||
o.keyOffset = st.keyOffset + diffidx + 1
|
||||
o.key = key[o.keyOffset:]
|
||||
o.val = value
|
||||
o.nodeType = leafNode
|
||||
|
||||
// Insert both child leaves where they belong:
|
||||
if diffidx == 0 {
|
||||
// the break is on the first byte, so
|
||||
// the current node is converted into
|
||||
// a branch node.
|
||||
st.children[0] = nil
|
||||
st.children[st.key[diffidx]] = n
|
||||
st.children[key[st.keyOffset+diffidx]] = o
|
||||
st.nodeType = branchNode
|
||||
st.key = nil
|
||||
} else {
|
||||
// the common prefix is at least one byte
|
||||
// long, insert a new intermediate branch
|
||||
// node.
|
||||
st.children[0] = NewReStackTrie()
|
||||
st.children[0].nodeType = branchNode
|
||||
st.children[0].children[st.key[diffidx]] = n
|
||||
st.children[0].children[key[st.keyOffset+diffidx]] = o
|
||||
st.children[0].keyOffset = st.keyOffset + diffidx
|
||||
st.key = st.key[:diffidx]
|
||||
}
|
||||
}
|
||||
|
||||
case leafNode: /* Leaf */
|
||||
// Compare both key chunks and see where they differ
|
||||
diffidx := st.getDiffIndex(key)
|
||||
|
||||
// Overwriting a key isn't supported, which means that
|
||||
// the current leaf is expected to be split into 1) an
|
||||
// optional extension for the common prefix of these 2
|
||||
// keys, 2) a fullnode selecting the path on which the
|
||||
// keys differ, and 3) one leaf for the differentiated
|
||||
// component of each key.
|
||||
if diffidx >= len(st.key) {
|
||||
panic("Trying to insert into existing key")
|
||||
}
|
||||
|
||||
// Check if the split occurs at the first nibble of the
|
||||
// chunk. In that case, no prefix extnode is necessary.
|
||||
// Otherwise, create that
|
||||
var p *ReStackTrie
|
||||
if diffidx == 0 {
|
||||
// Convert current leaf into a branch
|
||||
st.nodeType = branchNode
|
||||
p = st
|
||||
st.children[0] = nil
|
||||
} else {
|
||||
// Convert current node into an ext,
|
||||
// and insert a child branch node.
|
||||
st.nodeType = extNode
|
||||
st.children[0] = NewReStackTrie()
|
||||
st.children[0].nodeType = branchNode
|
||||
st.children[0].keyOffset = st.keyOffset + diffidx
|
||||
p = st.children[0]
|
||||
}
|
||||
|
||||
// Create the two child leaves: the one containing the
|
||||
// original value and the one containing the new value
|
||||
origIdx := st.key[diffidx]
|
||||
p.children[origIdx] = NewReStackTrie()
|
||||
p.children[origIdx].nodeType = leafNode
|
||||
p.children[origIdx].key = st.key[diffidx+1:]
|
||||
p.children[origIdx].val = st.val
|
||||
p.children[origIdx].keyOffset = p.keyOffset + 1
|
||||
|
||||
newIdx := key[diffidx+st.keyOffset]
|
||||
p.children[newIdx] = NewReStackTrie()
|
||||
p.children[newIdx].nodeType = leafNode
|
||||
p.children[newIdx].key = key[p.keyOffset+1:]
|
||||
p.children[newIdx].val = value
|
||||
p.children[newIdx].keyOffset = p.keyOffset + 1
|
||||
|
||||
st.key = st.key[:diffidx]
|
||||
case emptyNode: /* Empty */
|
||||
st.nodeType = leafNode
|
||||
st.key = key[st.keyOffset:]
|
||||
st.val = value
|
||||
default:
|
||||
panic("invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
// writeEvenHP writes a key with its hex prefix into a writer (presumably, the
|
||||
// input of a hasher) and then writes the value. The value can be a maximum of
|
||||
// 256 bytes, as it is only concerned with writing account leaves and optimize
|
||||
// for this use case.
|
||||
func writeHPRLP(writer io.Writer, key, val []byte, leaf bool) {
|
||||
// DEBUG don't remove yet
|
||||
//var writer bytes.Buffer
|
||||
|
||||
// Determine the _t_ part of the hex prefix
|
||||
hp := byte(0)
|
||||
if leaf {
|
||||
hp = 32
|
||||
}
|
||||
|
||||
const maxHeaderSize = 1 /* key byte list header */ +
|
||||
1 /* list header for key + value */ +
|
||||
1 /* potential size byte if total size > 56 */ +
|
||||
1 /* hex prefix if key is even-length*/
|
||||
header := [maxHeaderSize]byte{}
|
||||
keyOffset := 0
|
||||
headerPos := maxHeaderSize - 1
|
||||
|
||||
// Add the hex prefix to its own byte if the key length is even, and
|
||||
// as the most significant nibble of the key if it's odd.
|
||||
// In the latter case, the first nibble of the key will be part of
|
||||
// the header and it will be skipped later when it's added to the
|
||||
// hasher sponge.
|
||||
if len(key)%2 == 0 {
|
||||
header[headerPos] = hp
|
||||
} else {
|
||||
header[headerPos] = hp | key[0] | 16
|
||||
keyOffset = 1
|
||||
}
|
||||
headerPos--
|
||||
|
||||
// Add the key byte header, the key is 32 bytes max so it's always
|
||||
// under 56 bytes - no extra byte needed.
|
||||
keyByteSize := byte(len(key) / 2)
|
||||
if len(key) > 1 || header[len(header)-1] > 128 {
|
||||
header[headerPos] = 0x80 + keyByteSize + 1 /* HP */
|
||||
headerPos--
|
||||
}
|
||||
|
||||
// If this is a leaf being inserted, the header length for the
|
||||
// value part will be two bytes as the leaf is more than 56 bytes
|
||||
// long.
|
||||
valHeaderLen := 1
|
||||
if len(val) > 56 {
|
||||
valHeaderLen = 2
|
||||
}
|
||||
|
||||
// Add the global header, with optional length, and specify at
|
||||
// which byte the header is starting.
|
||||
payloadSize := int(keyByteSize) + (len(header) - headerPos - 1) +
|
||||
valHeaderLen + len(val) /* value + rlp header */
|
||||
var start int
|
||||
if payloadSize > 56 {
|
||||
header[headerPos] = byte(payloadSize)
|
||||
headerPos--
|
||||
header[headerPos] = 0xf8
|
||||
start = headerPos
|
||||
} else {
|
||||
header[headerPos] = 0xc0 + byte(payloadSize)
|
||||
start = headerPos
|
||||
}
|
||||
|
||||
// Write the header into the sponge
|
||||
writer.Write(header[start:])
|
||||
|
||||
// Write the key into the sponge
|
||||
var m byte
|
||||
for i, nibble := range key {
|
||||
// Skip the first byte if the key has an odd-length, since
|
||||
// it has already been written with the header.
|
||||
if i >= keyOffset {
|
||||
if (i-keyOffset)%2 == 0 {
|
||||
m = nibble
|
||||
} else {
|
||||
writer.Write([]byte{m*16 + nibble})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if leaf {
|
||||
writer.Write([]byte{0xb8, byte(len(val))})
|
||||
} else {
|
||||
writer.Write([]byte{0x80 + byte(len(val))})
|
||||
}
|
||||
writer.Write(val)
|
||||
|
||||
// DEBUG don't remove yet
|
||||
//if leaf {
|
||||
//fmt.Println("leaf rlp ", writer)
|
||||
//} else {
|
||||
//fmt.Println("ext rlp ", writer)
|
||||
//}
|
||||
//io.Copy(w, &writer)
|
||||
}
|
||||
|
||||
func (st *ReStackTrie) Hash() (h common.Hash) {
|
||||
d := sha3.NewLegacyKeccak256()
|
||||
switch st.nodeType {
|
||||
case 0:
|
||||
payload := [544]byte{}
|
||||
pos := 3 // maximum header length given what we know
|
||||
for _, v := range st.children {
|
||||
if v != nil {
|
||||
// Write a 32 byte list to the sponge
|
||||
payload[pos] = 0xa0
|
||||
pos++
|
||||
copy(payload[pos:pos+32], v.Hash().Bytes())
|
||||
pos += 32
|
||||
} else {
|
||||
// Write an empty list to the sponge
|
||||
payload[pos] = 0x80
|
||||
pos++
|
||||
}
|
||||
}
|
||||
// Add empty 17th value
|
||||
payload[pos] = 0x80
|
||||
pos++
|
||||
|
||||
// Compute the header, length size is either 0, 1 or 2 bytes since
|
||||
// there are at least 17 empty list headers, and at most 16 hashes
|
||||
// plus an empty header for the value.
|
||||
var start int
|
||||
if pos-3 < 56 {
|
||||
payload[2] = 0xc0 + byte(pos-3)
|
||||
start = 2
|
||||
} else if pos-3 < 256 {
|
||||
payload[2] = byte(pos - 3)
|
||||
payload[1] = 0xf8
|
||||
start = 1
|
||||
} else {
|
||||
payload[2] = byte(pos - 3)
|
||||
payload[1] = byte((pos - 3) >> 8)
|
||||
payload[0] = 0xf9
|
||||
start = 0
|
||||
}
|
||||
d.Write(payload[start:pos])
|
||||
case 1:
|
||||
ch := st.children[0].Hash().Bytes()
|
||||
writeHPRLP(d, st.key, ch, false)
|
||||
case 2:
|
||||
writeHPRLP(d, st.key, st.val, true)
|
||||
case 3:
|
||||
default:
|
||||
panic("Invalid node type")
|
||||
}
|
||||
d.Sum(h[:0])
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue