mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
trie, core: rough idea to improve derivesha
This commit is contained in:
parent
aacb8e6f06
commit
0455e7a9c6
4 changed files with 115 additions and 22 deletions
|
|
@ -14,26 +14,26 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package trie
|
package types
|
||||||
|
|
||||||
// bytesPool is a pool for byteslices. It is safe for concurrent use.
|
// BytesPool is a pool for byteslices. It is safe for concurrent use.
|
||||||
type bytesPool struct {
|
type BytesPool struct {
|
||||||
c chan []byte
|
c chan []byte
|
||||||
w int
|
w int
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBytesPool creates a new bytesPool. The sliceCap sets the capacity of
|
// NewBytesPool creates a new BytesPool. The sliceCap sets the capacity of
|
||||||
// newly allocated slices, and the nitems determines how many items the pool
|
// newly allocated slices, and the nitems determines how many items the pool
|
||||||
// will hold, at maximum.
|
// will hold, at maximum.
|
||||||
func newBytesPool(sliceCap, nitems int) *bytesPool {
|
func NewBytesPool(sliceCap, nitems int) *BytesPool {
|
||||||
return &bytesPool{
|
return &BytesPool{
|
||||||
c: make(chan []byte, nitems),
|
c: make(chan []byte, nitems),
|
||||||
w: sliceCap,
|
w: sliceCap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a slice. Safe for concurrent use.
|
// Get returns a slice. Safe for concurrent use.
|
||||||
func (bp *bytesPool) Get() []byte {
|
func (bp *BytesPool) Get() []byte {
|
||||||
select {
|
select {
|
||||||
case b := <-bp.c:
|
case b := <-bp.c:
|
||||||
return b
|
return b
|
||||||
|
|
@ -44,7 +44,7 @@ func (bp *bytesPool) Get() []byte {
|
||||||
|
|
||||||
// Put returns a slice to the pool. Safe for concurrent use. This method
|
// Put returns a slice to the pool. Safe for concurrent use. This method
|
||||||
// will ignore slices that are too small or too large (>3x the cap)
|
// will ignore slices that are too small or too large (>3x the cap)
|
||||||
func (bp *bytesPool) Put(b []byte) {
|
func (bp *BytesPool) Put(b []byte) {
|
||||||
if c := cap(b); c < bp.w || c > 3*bp.w {
|
if c := cap(b); c < bp.w || c > 3*bp.w {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +84,11 @@ type TrieHasher interface {
|
||||||
Hash() common.Hash
|
Hash() common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RecyclingTrieHasher interface {
|
||||||
|
TrieHasher
|
||||||
|
Reclaim(*BytesPool)
|
||||||
|
}
|
||||||
|
|
||||||
// DerivableList is the input to DeriveSha.
|
// DerivableList is the input to DeriveSha.
|
||||||
// It is implemented by the 'Transactions' and 'Receipts' types.
|
// It is implemented by the 'Transactions' and 'Receipts' types.
|
||||||
// This is internal, do not use these methods.
|
// This is internal, do not use these methods.
|
||||||
|
|
@ -95,10 +100,7 @@ type DerivableList interface {
|
||||||
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
|
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
list.EncodeIndex(i, buf)
|
list.EncodeIndex(i, buf)
|
||||||
// It's really unfortunate that we need to perform this copy.
|
return buf.Bytes()
|
||||||
// StackTrie holds onto the values until Hash is called, so the values
|
|
||||||
// written to it must not alias.
|
|
||||||
return common.CopyBytes(buf.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header.
|
// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header.
|
||||||
|
|
@ -118,17 +120,84 @@ func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash {
|
||||||
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
||||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||||
value := encodeForDerive(list, i, valueBuf)
|
value := encodeForDerive(list, i, valueBuf)
|
||||||
|
// It's really unfortunate that we need to perform this copy.
|
||||||
|
// StackTrie holds onto the values until Hash is called, so the values
|
||||||
|
// written to it must not alias.
|
||||||
|
value = common.CopyBytes(value)
|
||||||
hasher.Update(indexBuf, value)
|
hasher.Update(indexBuf, value)
|
||||||
}
|
}
|
||||||
if list.Len() > 0 {
|
if list.Len() > 0 {
|
||||||
indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
|
indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
|
||||||
value := encodeForDerive(list, 0, valueBuf)
|
value := encodeForDerive(list, 0, valueBuf)
|
||||||
|
value = common.CopyBytes(value)
|
||||||
|
|
||||||
hasher.Update(indexBuf, value)
|
hasher.Update(indexBuf, value)
|
||||||
}
|
}
|
||||||
for i := 0x80; i < list.Len(); i++ {
|
for i := 0x80; i < list.Len(); i++ {
|
||||||
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||||
value := encodeForDerive(list, i, valueBuf)
|
value := encodeForDerive(list, i, valueBuf)
|
||||||
|
value = common.CopyBytes(value)
|
||||||
|
|
||||||
hasher.Update(indexBuf, value)
|
hasher.Update(indexBuf, value)
|
||||||
}
|
}
|
||||||
return hasher.Hash()
|
return hasher.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeriveShaNG(list DerivableList, hasher RecyclingTrieHasher) common.Hash {
|
||||||
|
hasher.Reset()
|
||||||
|
|
||||||
|
valueBuf := encodeBufferPool.Get().(*bytes.Buffer)
|
||||||
|
defer encodeBufferPool.Put(valueBuf)
|
||||||
|
|
||||||
|
cap := 300
|
||||||
|
vPool := NewBytesPool(cap, 40)
|
||||||
|
hasher.Reclaim(vPool)
|
||||||
|
|
||||||
|
// StackTrie requires values to be inserted in increasing hash order, which is not the
|
||||||
|
// order that `list` provides hashes in. This insertion sequence ensures that the
|
||||||
|
// order is correct.
|
||||||
|
//
|
||||||
|
// The error returned by hasher is omitted because hasher will produce an incorrect
|
||||||
|
// hash in case any error occurs.
|
||||||
|
var indexBuf []byte
|
||||||
|
for i := 1; i < list.Len() && i <= 0x7f; i++ {
|
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||||
|
value := encodeForDerive(list, i, valueBuf)
|
||||||
|
// It's really unfortunate that we need to perform this copy.
|
||||||
|
// StackTrie holds onto the values until Hash is called, so the values
|
||||||
|
// written to it must not alias
|
||||||
|
vBuf := vPool.Get()
|
||||||
|
if cap < len(value) {
|
||||||
|
vBuf = common.CopyBytes(value)
|
||||||
|
} else {
|
||||||
|
vBuf = vBuf[:len(value)]
|
||||||
|
copy(vBuf, value)
|
||||||
|
}
|
||||||
|
hasher.Update(indexBuf, vBuf)
|
||||||
|
}
|
||||||
|
if list.Len() > 0 {
|
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
|
||||||
|
value := encodeForDerive(list, 0, valueBuf)
|
||||||
|
vBuf := vPool.Get()
|
||||||
|
if cap < len(value) {
|
||||||
|
vBuf = common.CopyBytes(value)
|
||||||
|
} else {
|
||||||
|
vBuf = vBuf[:len(value)]
|
||||||
|
copy(vBuf, value)
|
||||||
|
}
|
||||||
|
hasher.Update(indexBuf, vBuf)
|
||||||
|
}
|
||||||
|
for i := 0x80; i < list.Len(); i++ {
|
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
|
||||||
|
value := encodeForDerive(list, i, valueBuf)
|
||||||
|
vBuf := vPool.Get()
|
||||||
|
if cap < len(value) {
|
||||||
|
vBuf = common.CopyBytes(value)
|
||||||
|
} else {
|
||||||
|
vBuf = vBuf[:len(value)]
|
||||||
|
copy(vBuf, value)
|
||||||
|
}
|
||||||
|
hasher.Update(indexBuf, vBuf)
|
||||||
|
}
|
||||||
|
return hasher.Hash()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,18 @@ func BenchmarkDeriveSha200(b *testing.B) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
var exp common.Hash
|
var exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||||
var got common.Hash
|
|
||||||
|
var have common.Hash
|
||||||
|
|
||||||
b.Run("std_trie", func(b *testing.B) {
|
b.Run("std_trie", func(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
have = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
|
||||||
|
}
|
||||||
|
if have != exp {
|
||||||
|
b.Errorf("got %x exp %x", have, exp)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -95,12 +100,22 @@ func BenchmarkDeriveSha200(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
got = types.DeriveSha(txs, trie.NewStackTrie(nil))
|
have = types.DeriveShaNG(txs, trie.NewStackTrie(nil))
|
||||||
|
}
|
||||||
|
if have != exp {
|
||||||
|
b.Errorf("got %x exp %x", have, exp)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if got != exp {
|
//b.Run("stack_trie_reuse", func(b *testing.B) {
|
||||||
b.Errorf("got %x exp %x", got, exp)
|
// b.ResetTimer()
|
||||||
}
|
// b.ReportAllocs()
|
||||||
|
// for i := 0; i < b.N; i++ {
|
||||||
|
// have = types.DeriveShaNG(txs, trie.NewStackTrie(nil))
|
||||||
|
// }
|
||||||
|
// if have != exp {
|
||||||
|
// b.Errorf("got %x exp %x", have, exp)
|
||||||
|
// }
|
||||||
|
//})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFuzzDeriveSha(t *testing.T) {
|
func TestFuzzDeriveSha(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stPool = sync.Pool{New: func() any { return new(stNode) }}
|
stPool = sync.Pool{New: func() any { return new(stNode) }}
|
||||||
bPool = newBytesPool(32, 100)
|
bPool = types.NewBytesPool(32, 100)
|
||||||
_ = types.TrieHasher((*StackTrie)(nil))
|
_ = types.TrieHasher((*StackTrie)(nil))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -50,6 +50,7 @@ type StackTrie struct {
|
||||||
onTrieNode OnTrieNode
|
onTrieNode OnTrieNode
|
||||||
kBuf []byte // buf space used for hex-key during insertions
|
kBuf []byte // buf space used for hex-key during insertions
|
||||||
pBuf []byte // buf space used for path during insertions
|
pBuf []byte // buf space used for path during insertions
|
||||||
|
vPool *types.BytesPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStackTrie allocates and initializes an empty trie. The committed nodes
|
// NewStackTrie allocates and initializes an empty trie. The committed nodes
|
||||||
|
|
@ -64,6 +65,10 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *StackTrie) Reclaim(vPool *types.BytesPool) {
|
||||||
|
st.vPool = vPool
|
||||||
|
}
|
||||||
|
|
||||||
// Update inserts a (key, value) pair into the stack trie.
|
// Update inserts a (key, value) pair into the stack trie.
|
||||||
func (t *StackTrie) Update(key, value []byte) error {
|
func (t *StackTrie) Update(key, value []byte) error {
|
||||||
if len(value) == 0 {
|
if len(value) == 0 {
|
||||||
|
|
@ -392,7 +397,11 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
|
||||||
st.typ = hashedNode
|
st.typ = hashedNode
|
||||||
st.key = st.key[:0]
|
st.key = st.key[:0]
|
||||||
|
|
||||||
st.val = nil // Release reference to potentially externally held slice.
|
// Release reference to (potentially externally held) value-slice.
|
||||||
|
if len(st.val) > 0 && t.vPool != nil {
|
||||||
|
t.vPool.Put(st.val)
|
||||||
|
}
|
||||||
|
st.val = nil
|
||||||
|
|
||||||
// Skip committing the non-root node if the size is smaller than 32 bytes
|
// Skip committing the non-root node if the size is smaller than 32 bytes
|
||||||
// as tiny nodes are always embedded in their parent except root node.
|
// as tiny nodes are always embedded in their parent except root node.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue