From 6f337b56b123a5f03f0a6037ddcd743b0396fb9a Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 20 Mar 2020 17:08:22 +0100 Subject: [PATCH] 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) --- core/state/snapshot/hextrie_generator.go | 15 +- core/state/snapshot/trie_generator_test.go | 61 +++- trie/stacktrie.go | 334 ++++++++++++++++++++- 3 files changed, 395 insertions(+), 15 deletions(-) diff --git a/core/state/snapshot/hextrie_generator.go b/core/state/snapshot/hextrie_generator.go index b7ab83e8b5..a16bf28644 100644 --- a/core/state/snapshot/hextrie_generator.go +++ b/core/state/snapshot/hextrie_generator.go @@ -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 diff --git a/core/state/snapshot/trie_generator_test.go b/core/state/snapshot/trie_generator_test.go index 4ddb0c81b3..16f7ad4772 100644 --- a/core/state/snapshot/trie_generator_test.go +++ b/core/state/snapshot/trie_generator_test.go @@ -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())) + } +} diff --git a/trie/stacktrie.go b/trie/stacktrie.go index 403ed7c2b6..c31b8db81f 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -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 +}