erigon-pulse/cmd/verkle/verkle_tree_writer.go
Giulio rebuffo bf768a4278
Adapted verkle tree generation in preperation of Beverly Hills (#5459)
* fixed increment

* removed useless code

* Adapted verkle tree generation in preperation of Beverly Hills

Co-authored-by: giuliorebuffo <giuliorebuffo@system76-pc.localdomain>
2022-09-22 00:33:48 +02:00

273 lines
7.7 KiB
Go

package main
import (
"context"
"encoding/binary"
"time"
"github.com/anacrolix/sync"
"github.com/gballet/go-verkle"
"github.com/ledgerwatch/erigon-lib/etl"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/core/types/accounts"
"github.com/ledgerwatch/erigon/turbo/trie/vtree"
"github.com/ledgerwatch/log/v3"
)
func flushVerkleNode(db kv.RwTx, node verkle.VerkleNode, logInterval *time.Ticker, key []byte) error {
var err error
totalInserted := 0
node.(*verkle.InternalNode).Flush(func(node verkle.VerkleNode) {
if err != nil {
return
}
var encodedNode []byte
rootHash := node.ComputeCommitment().Bytes()
encodedNode, err = node.Serialize()
if err != nil {
return
}
err = db.Put(VerkleTrie, rootHash[:], encodedNode)
totalInserted++
select {
case <-logInterval.C:
log.Info("Flushing Verkle nodes", "inserted", totalInserted, "key", common.Bytes2Hex(key))
default:
}
})
return err
}
func collectVerkleNode(collector *etl.Collector, node verkle.VerkleNode, logInterval *time.Ticker, key []byte) error {
var err error
totalInserted := 0
node.(*verkle.InternalNode).Flush(func(node verkle.VerkleNode) {
if err != nil {
return
}
var encodedNode []byte
rootHash := node.ComputeCommitment().Bytes()
encodedNode, err = node.Serialize()
if err != nil {
return
}
err = collector.Collect(rootHash[:], encodedNode)
totalInserted++
select {
case <-logInterval.C:
log.Info("Flushing Verkle nodes", "inserted", totalInserted, "key", common.Bytes2Hex(key))
default:
}
})
return err
}
type VerkleTreeWriter struct {
db kv.RwTx
collector *etl.Collector
mu sync.Mutex
tmpdir string
}
func NewVerkleTreeWriter(db kv.RwTx, tmpdir string) *VerkleTreeWriter {
return &VerkleTreeWriter{
db: db,
collector: etl.NewCollector("verkleTreeWriterLogPrefix", tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize*8)),
tmpdir: tmpdir,
}
}
func (v *VerkleTreeWriter) UpdateAccount(versionKey []byte, codeSize uint64, isContract bool, acc accounts.Account) error {
v.mu.Lock()
defer v.mu.Unlock()
var codeHashKey, nonceKey, balanceKey, codeSizeKey, nonce, balance, cs [32]byte
copy(codeHashKey[:], versionKey[:31])
copy(nonceKey[:], versionKey[:31])
copy(balanceKey[:], versionKey[:31])
copy(codeSizeKey[:], versionKey[:31])
codeHashKey[31] = vtree.CodeKeccakLeafKey
nonceKey[31] = vtree.NonceLeafKey
balanceKey[31] = vtree.BalanceLeafKey
codeSizeKey[31] = vtree.CodeSizeLeafKey
// Process values
int256ToVerkleFormat(&acc.Balance, balance[:])
binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
// Insert in the tree
if err := v.collector.Collect(versionKey, []byte{0}); err != nil {
return err
}
if err := v.collector.Collect(nonceKey[:], nonce[:]); err != nil {
return err
}
if err := v.collector.Collect(balanceKey[:], balance[:]); err != nil {
return err
}
if isContract {
binary.LittleEndian.PutUint64(cs[:], codeSize)
if err := v.collector.Collect(codeHashKey[:], acc.CodeHash[:]); err != nil {
return err
}
if err := v.collector.Collect(codeSizeKey[:], cs[:]); err != nil {
return err
}
}
return nil
}
func (v *VerkleTreeWriter) DeleteAccount(versionKey []byte, isContract bool) error {
v.mu.Lock()
defer v.mu.Unlock()
var codeHashKey, nonceKey, balanceKey, codeSizeKey [32]byte
copy(codeHashKey[:], versionKey[:31])
copy(nonceKey[:], versionKey[:31])
copy(balanceKey[:], versionKey[:31])
copy(codeSizeKey[:], versionKey[:31])
codeHashKey[31] = vtree.CodeKeccakLeafKey
nonceKey[31] = vtree.NonceLeafKey
balanceKey[31] = vtree.BalanceLeafKey
codeSizeKey[31] = vtree.CodeSizeLeafKey
// Insert in the tree
if err := v.collector.Collect(versionKey, []byte{0}); err != nil {
return err
}
if err := v.collector.Collect(nonceKey[:], []byte{0}); err != nil {
return err
}
if err := v.collector.Collect(balanceKey[:], []byte{0}); err != nil {
return err
}
if isContract {
if err := v.collector.Collect(codeHashKey[:], []byte{0}); err != nil {
return err
}
if err := v.collector.Collect(codeSizeKey[:], []byte{0}); err != nil {
return err
}
}
return nil
}
func (v *VerkleTreeWriter) Insert(key, value []byte) error {
v.mu.Lock()
defer v.mu.Unlock()
return v.collector.Collect(key, value)
}
func (v *VerkleTreeWriter) WriteContractCodeChunks(codeKeys [][]byte, chunks [][]byte) error {
v.mu.Lock()
defer v.mu.Unlock()
for i, codeKey := range codeKeys {
if err := v.collector.Collect(codeKey, chunks[i]); err != nil {
return err
}
}
return nil
}
func (v *VerkleTreeWriter) CommitVerkleTreeFromScratch() (common.Hash, error) {
if err := v.db.ClearBucket(VerkleTrie); err != nil {
return common.Hash{}, err
}
verkleCollector := etl.NewCollector(VerkleTrie, v.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize))
defer verkleCollector.Close()
root := verkle.New()
logInterval := time.NewTicker(30 * time.Second)
if err := v.collector.Load(v.db, VerkleTrie, func(k []byte, v []byte, _ etl.CurrentTableReader, next etl.LoadNextFunc) error {
if len(v) == 0 {
return next(k, nil, nil)
}
if err := root.InsertOrdered(common.CopyBytes(k), common.CopyBytes(v), func(node verkle.VerkleNode) {
rootHash := node.ComputeCommitment().Bytes()
encodedNode, err := node.Serialize()
if err != nil {
panic(err)
}
if err := verkleCollector.Collect(rootHash[:], encodedNode); err != nil {
panic(err)
}
select {
case <-logInterval.C:
log.Info("[Verkle] Assembling Verkle Tree", "key", common.Bytes2Hex(k))
default:
}
}); err != nil {
return err
}
return next(k, nil, nil)
}, etl.TransformArgs{Quit: context.Background().Done()}); err != nil {
return common.Hash{}, err
}
// Flush the rest all at once
if err := collectVerkleNode(v.collector, root, logInterval, nil); err != nil {
return common.Hash{}, err
}
log.Info("Started Verkle Tree Flushing")
return root.ComputeCommitment().Bytes(), verkleCollector.Load(v.db, VerkleTrie, etl.IdentityLoadFunc, etl.TransformArgs{Quit: context.Background().Done(),
LogDetailsLoad: func(k, v []byte) (additionalLogArguments []interface{}) {
return []interface{}{"key", common.Bytes2Hex(k)}
}})
}
func (v *VerkleTreeWriter) CommitVerkleTree(root common.Hash) (common.Hash, error) {
resolverFunc := func(root []byte) ([]byte, error) {
return v.db.GetOne(VerkleTrie, root)
}
var rootNode verkle.VerkleNode
if root != (common.Hash{}) {
nodeEncoded, err := v.db.GetOne(VerkleTrie, root[:])
if err != nil {
return common.Hash{}, err
}
rootNode, err = verkle.ParseNode(nodeEncoded, 0, root[:])
if err != nil {
return common.Hash{}, err
}
} else {
return v.CommitVerkleTreeFromScratch()
}
verkleCollector := etl.NewCollector(VerkleTrie, v.tmpdir, etl.NewSortableBuffer(etl.BufferOptimalSize))
defer verkleCollector.Close()
insertionBeforeFlushing := 2_000_000 // 2M node to flush at a time
insertions := 0
logInterval := time.NewTicker(30 * time.Second)
if err := v.collector.Load(v.db, VerkleTrie, func(key []byte, value []byte, _ etl.CurrentTableReader, next etl.LoadNextFunc) error {
if len(value) > 0 {
if err := rootNode.Insert(common.CopyBytes(key), common.CopyBytes(value), resolverFunc); err != nil {
return err
}
insertions++
}
if insertions > insertionBeforeFlushing {
if err := flushVerkleNode(v.db, rootNode, logInterval, key); err != nil {
return err
}
insertions = 0
}
return next(key, nil, nil)
}, etl.TransformArgs{Quit: context.Background().Done()}); err != nil {
return common.Hash{}, err
}
commitment := rootNode.ComputeCommitment().Bytes()
return common.BytesToHash(commitment[:]), flushVerkleNode(v.db, rootNode, logInterval, nil)
}
func (v *VerkleTreeWriter) Close() {
v.collector.Close()
}