mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 13:46:43 +00:00
trie: introduce UpdateBatch
This commit is contained in:
parent
2dbb580f51
commit
cd191ed55a
6 changed files with 232 additions and 6 deletions
|
|
@ -91,12 +91,18 @@ type Trie interface {
|
||||||
// in the trie with provided address.
|
// in the trie with provided address.
|
||||||
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
|
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
|
||||||
|
|
||||||
|
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||||
|
UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error
|
||||||
|
|
||||||
// UpdateStorage associates key with value in the trie. If value has length zero,
|
// UpdateStorage associates key with value in the trie. If value has length zero,
|
||||||
// any existing value is deleted from the trie. The value bytes must not be modified
|
// any existing value is deleted from the trie. The value bytes must not be modified
|
||||||
// by the caller while they are stored in the trie. If a node was not found in the
|
// by the caller while they are stored in the trie. If a node was not found in the
|
||||||
// database, a trie.MissingNodeError is returned.
|
// database, a trie.MissingNodeError is returned.
|
||||||
UpdateStorage(addr common.Address, key, value []byte) error
|
UpdateStorage(addr common.Address, key, value []byte) error
|
||||||
|
|
||||||
|
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||||
|
UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error
|
||||||
|
|
||||||
// DeleteAccount abstracts an account deletion from the trie.
|
// DeleteAccount abstracts an account deletion from the trie.
|
||||||
DeleteAccount(address common.Address) error
|
DeleteAccount(address common.Address) error
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||||
|
func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error {
|
||||||
|
var (
|
||||||
|
hkeys = make([][]byte, 0, len(keys))
|
||||||
|
evals = make([][]byte, 0, len(values))
|
||||||
|
)
|
||||||
|
for _, key := range keys {
|
||||||
|
hk := crypto.Keccak256(key)
|
||||||
|
if t.preimages != nil {
|
||||||
|
t.secKeyCache[common.Hash(hk)] = key
|
||||||
|
}
|
||||||
|
hkeys = append(hkeys, hk)
|
||||||
|
}
|
||||||
|
for _, val := range values {
|
||||||
|
data, err := rlp.EncodeToBytes(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
evals = append(evals, data)
|
||||||
|
}
|
||||||
|
return t.trie.UpdateBatch(hkeys, evals)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateAccount will abstract the write of an account to the secure trie.
|
// UpdateAccount will abstract the write of an account to the secure trie.
|
||||||
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
|
func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error {
|
||||||
hk := crypto.Keccak256(address.Bytes())
|
hk := crypto.Keccak256(address.Bytes())
|
||||||
|
|
@ -206,6 +229,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||||
|
func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error {
|
||||||
|
var (
|
||||||
|
hkeys = make([][]byte, 0, len(addresses))
|
||||||
|
values = make([][]byte, 0, len(accounts))
|
||||||
|
)
|
||||||
|
for _, addr := range addresses {
|
||||||
|
hk := crypto.Keccak256(addr.Bytes())
|
||||||
|
if t.preimages != nil {
|
||||||
|
t.secKeyCache[common.Hash(hk)] = addr.Bytes()
|
||||||
|
}
|
||||||
|
hkeys = append(hkeys, hk)
|
||||||
|
}
|
||||||
|
for _, acc := range accounts {
|
||||||
|
data, err := rlp.EncodeToBytes(acc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
values = append(values, data)
|
||||||
|
}
|
||||||
|
return t.trie.UpdateBatch(hkeys, values)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
|
func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package trie
|
||||||
import (
|
import (
|
||||||
"maps"
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// opTracer tracks the changes of trie nodes. During the trie operations,
|
// opTracer tracks the changes of trie nodes. During the trie operations,
|
||||||
|
|
@ -33,12 +34,10 @@ import (
|
||||||
// while the latter is inserted/deleted in order to follow the rule of trie.
|
// while the latter is inserted/deleted in order to follow the rule of trie.
|
||||||
// This tool can track all of them no matter the node is embedded in its
|
// This tool can track all of them no matter the node is embedded in its
|
||||||
// parent or not, but valueNode is never tracked.
|
// parent or not, but valueNode is never tracked.
|
||||||
//
|
|
||||||
// Note opTracer is not thread-safe, callers should be responsible for handling
|
|
||||||
// the concurrency issues by themselves.
|
|
||||||
type opTracer struct {
|
type opTracer struct {
|
||||||
inserts map[string]struct{}
|
inserts map[string]struct{}
|
||||||
deletes map[string]struct{}
|
deletes map[string]struct{}
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newOpTracer initializes the tracer for capturing trie changes.
|
// newOpTracer initializes the tracer for capturing trie changes.
|
||||||
|
|
@ -53,6 +52,9 @@ func newOpTracer() *opTracer {
|
||||||
// in the deletion set (resurrected node), then just wipe it from
|
// in the deletion set (resurrected node), then just wipe it from
|
||||||
// the deletion set as it's "untouched".
|
// the deletion set as it's "untouched".
|
||||||
func (t *opTracer) onInsert(path []byte) {
|
func (t *opTracer) onInsert(path []byte) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
if _, present := t.deletes[string(path)]; present {
|
if _, present := t.deletes[string(path)]; present {
|
||||||
delete(t.deletes, string(path))
|
delete(t.deletes, string(path))
|
||||||
return
|
return
|
||||||
|
|
@ -64,6 +66,9 @@ func (t *opTracer) onInsert(path []byte) {
|
||||||
// in the addition set, then just wipe it from the addition set
|
// in the addition set, then just wipe it from the addition set
|
||||||
// as it's untouched.
|
// as it's untouched.
|
||||||
func (t *opTracer) onDelete(path []byte) {
|
func (t *opTracer) onDelete(path []byte) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
if _, present := t.inserts[string(path)]; present {
|
if _, present := t.inserts[string(path)]; present {
|
||||||
delete(t.inserts, string(path))
|
delete(t.inserts, string(path))
|
||||||
return
|
return
|
||||||
|
|
@ -73,12 +78,18 @@ func (t *opTracer) onDelete(path []byte) {
|
||||||
|
|
||||||
// reset clears the content tracked by tracer.
|
// reset clears the content tracked by tracer.
|
||||||
func (t *opTracer) reset() {
|
func (t *opTracer) reset() {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
clear(t.inserts)
|
clear(t.inserts)
|
||||||
clear(t.deletes)
|
clear(t.deletes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy returns a deep copied tracer instance.
|
// copy returns a deep copied tracer instance.
|
||||||
func (t *opTracer) copy() *opTracer {
|
func (t *opTracer) copy() *opTracer {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
return &opTracer{
|
return &opTracer{
|
||||||
inserts: maps.Clone(t.inserts),
|
inserts: maps.Clone(t.inserts),
|
||||||
deletes: maps.Clone(t.deletes),
|
deletes: maps.Clone(t.deletes),
|
||||||
|
|
@ -87,6 +98,9 @@ func (t *opTracer) copy() *opTracer {
|
||||||
|
|
||||||
// deletedList returns a list of node paths which are deleted from the trie.
|
// deletedList returns a list of node paths which are deleted from the trie.
|
||||||
func (t *opTracer) deletedList() [][]byte {
|
func (t *opTracer) deletedList() [][]byte {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
paths := make([][]byte, 0, len(t.deletes))
|
paths := make([][]byte, 0, len(t.deletes))
|
||||||
for path := range t.deletes {
|
for path := range t.deletes {
|
||||||
paths = append(paths, []byte(path))
|
paths = append(paths, []byte(path))
|
||||||
|
|
@ -97,11 +111,9 @@ func (t *opTracer) deletedList() [][]byte {
|
||||||
// prevalueTracer tracks the original values of resolved trie nodes. Cached trie
|
// prevalueTracer tracks the original values of resolved trie nodes. Cached trie
|
||||||
// node values are expected to be immutable. A zero-size node value is treated as
|
// node values are expected to be immutable. A zero-size node value is treated as
|
||||||
// non-existent and should not occur in practice.
|
// non-existent and should not occur in practice.
|
||||||
//
|
|
||||||
// Note prevalueTracer is not thread-safe, callers should be responsible for
|
|
||||||
// handling the concurrency issues by themselves.
|
|
||||||
type prevalueTracer struct {
|
type prevalueTracer struct {
|
||||||
data map[string][]byte
|
data map[string][]byte
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
|
// newPrevalueTracer initializes the tracer for capturing resolved trie nodes.
|
||||||
|
|
@ -115,18 +127,27 @@ func newPrevalueTracer() *prevalueTracer {
|
||||||
// blob internally. Do not modify the value outside this function,
|
// blob internally. Do not modify the value outside this function,
|
||||||
// as it is not deep-copied.
|
// as it is not deep-copied.
|
||||||
func (t *prevalueTracer) put(path []byte, val []byte) {
|
func (t *prevalueTracer) put(path []byte, val []byte) {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
t.data[string(path)] = val
|
t.data[string(path)] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns the cached trie node value. If the node is not found, nil will
|
// get returns the cached trie node value. If the node is not found, nil will
|
||||||
// be returned.
|
// be returned.
|
||||||
func (t *prevalueTracer) get(path []byte) []byte {
|
func (t *prevalueTracer) get(path []byte) []byte {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
return t.data[string(path)]
|
return t.data[string(path)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasList returns a list of flags indicating whether the corresponding trie nodes
|
// hasList returns a list of flags indicating whether the corresponding trie nodes
|
||||||
// specified by the path exist in the trie.
|
// specified by the path exist in the trie.
|
||||||
func (t *prevalueTracer) hasList(list [][]byte) []bool {
|
func (t *prevalueTracer) hasList(list [][]byte) []bool {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
exists := make([]bool, 0, len(list))
|
exists := make([]bool, 0, len(list))
|
||||||
for _, path := range list {
|
for _, path := range list {
|
||||||
_, ok := t.data[string(path)]
|
_, ok := t.data[string(path)]
|
||||||
|
|
@ -137,16 +158,25 @@ func (t *prevalueTracer) hasList(list [][]byte) []bool {
|
||||||
|
|
||||||
// values returns a list of values of the cached trie nodes.
|
// values returns a list of values of the cached trie nodes.
|
||||||
func (t *prevalueTracer) values() [][]byte {
|
func (t *prevalueTracer) values() [][]byte {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
return slices.Collect(maps.Values(t.data))
|
return slices.Collect(maps.Values(t.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset resets the cached content in the prevalueTracer.
|
// reset resets the cached content in the prevalueTracer.
|
||||||
func (t *prevalueTracer) reset() {
|
func (t *prevalueTracer) reset() {
|
||||||
|
t.lock.Lock()
|
||||||
|
defer t.lock.Unlock()
|
||||||
|
|
||||||
clear(t.data)
|
clear(t.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy returns a copied prevalueTracer instance.
|
// copy returns a copied prevalueTracer instance.
|
||||||
func (t *prevalueTracer) copy() *prevalueTracer {
|
func (t *prevalueTracer) copy() *prevalueTracer {
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
|
||||||
// Shadow clone is used, as the cached trie node values are immutable
|
// Shadow clone is used, as the cached trie node values are immutable
|
||||||
return &prevalueTracer{
|
return &prevalueTracer{
|
||||||
data: maps.Clone(t.data),
|
data: maps.Clone(t.data),
|
||||||
|
|
|
||||||
61
trie/trie.go
61
trie/trie.go
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Trie represents a Merkle Patricia Trie. Use New to create a trie that operates
|
// Trie represents a Merkle Patricia Trie. Use New to create a trie that operates
|
||||||
|
|
@ -406,6 +407,66 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateBatch updates a batch of entries concurrently.
|
||||||
|
func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error {
|
||||||
|
// Short circuit if the trie is already committed and unusable.
|
||||||
|
if t.committed {
|
||||||
|
return ErrCommitted
|
||||||
|
}
|
||||||
|
if len(keys) != len(values) {
|
||||||
|
return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
|
||||||
|
}
|
||||||
|
// Insert the entries sequentially if there are not too many
|
||||||
|
// trie nodes in the trie.
|
||||||
|
fn, ok := t.root.(*fullNode)
|
||||||
|
if !ok || len(keys) < 4 { // TODO(rjl493456442) the parallelism threshold should be twisted
|
||||||
|
for i, key := range keys {
|
||||||
|
err := t.Update(key, values[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
ikeys = make(map[byte][][]byte)
|
||||||
|
ivals = make(map[byte][][]byte)
|
||||||
|
eg errgroup.Group
|
||||||
|
)
|
||||||
|
for i, key := range keys {
|
||||||
|
hkey := keybytesToHex(key)
|
||||||
|
ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey)
|
||||||
|
ivals[hkey[0]] = append(ivals[hkey[0]], values[i])
|
||||||
|
}
|
||||||
|
for pos, ks := range ikeys {
|
||||||
|
eg.Go(func() error {
|
||||||
|
vs := ivals[pos]
|
||||||
|
for i, k := range ks {
|
||||||
|
if len(vs[i]) != 0 {
|
||||||
|
_, n, err := t.insert(fn.Children[pos], []byte{pos}, k[1:], valueNode(vs[i]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fn.Children[pos] = n
|
||||||
|
} else {
|
||||||
|
_, n, err := t.delete(fn.Children[pos], []byte{pos}, k[1:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fn.Children[pos] = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.unhashed += len(keys)
|
||||||
|
t.uncommitted += len(keys)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MustDelete is a wrapper of Delete and will omit any encountered error but
|
// MustDelete is a wrapper of Delete and will omit any encountered error but
|
||||||
// just print out an error message.
|
// just print out an error message.
|
||||||
func (t *Trie) MustDelete(key []byte) {
|
func (t *Trie) MustDelete(key []byte) {
|
||||||
|
|
|
||||||
|
|
@ -1499,3 +1499,57 @@ func testTrieCopyNewTrie(t *testing.T, entries []kv) {
|
||||||
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
|
t.Errorf("Hash mismatch: old %v, new %v", hash, tr.Hash())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateBatch(t *testing.T) {
|
||||||
|
testUpdateBatch(t, []kv{
|
||||||
|
{k: []byte("do"), v: []byte("verb")},
|
||||||
|
{k: []byte("ether"), v: []byte("wookiedoo")},
|
||||||
|
{k: []byte("horse"), v: []byte("stallion")},
|
||||||
|
{k: []byte("shaman"), v: []byte("horse")},
|
||||||
|
{k: []byte("doge"), v: []byte("coin")},
|
||||||
|
{k: []byte("dog"), v: []byte("puppy")},
|
||||||
|
})
|
||||||
|
|
||||||
|
var entries []kv
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)})
|
||||||
|
}
|
||||||
|
testUpdateBatch(t, entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateBatch(t *testing.T, entries []kv) {
|
||||||
|
var (
|
||||||
|
base = NewEmpty(nil)
|
||||||
|
keys [][]byte
|
||||||
|
vals [][]byte
|
||||||
|
)
|
||||||
|
for _, entry := range entries {
|
||||||
|
base.Update(entry.k, entry.v)
|
||||||
|
keys = append(keys, entry.k)
|
||||||
|
vals = append(vals, entry.v)
|
||||||
|
}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
k, v := testrand.Bytes(32), testrand.Bytes(32)
|
||||||
|
base.Update(k, v)
|
||||||
|
keys = append(keys, k)
|
||||||
|
vals = append(vals, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmp := NewEmpty(nil)
|
||||||
|
if err := cmp.UpdateBatch(keys, vals); err != nil {
|
||||||
|
t.Fatalf("Failed to update batch, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the original tree, the changes made on the copy one shouldn't
|
||||||
|
// affect the old one
|
||||||
|
for _, key := range keys {
|
||||||
|
v1, _ := base.Get(key)
|
||||||
|
v2, _ := cmp.Get(key)
|
||||||
|
if !bytes.Equal(v1, v2) {
|
||||||
|
t.Errorf("Unexpected data, key: %v, want: %v, got: %v", key, v1, v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if base.Hash() != cmp.Hash() {
|
||||||
|
t.Errorf("Hash mismatch: want %x, got %x", base.Hash(), cmp.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,22 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateAccountBatch attempts to update a list accounts in the batch manner.
|
||||||
|
func (t *VerkleTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error {
|
||||||
|
if len(addresses) != len(accounts) {
|
||||||
|
return fmt.Errorf("address and accounts length mismatch: %d != %d", len(addresses), len(accounts))
|
||||||
|
}
|
||||||
|
if len(addresses) != len(codeLens) {
|
||||||
|
return fmt.Errorf("address and code length mismatch: %d != %d", len(addresses), len(codeLens))
|
||||||
|
}
|
||||||
|
for i, addr := range addresses {
|
||||||
|
if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateStorage implements state.Trie, writing the provided storage slot into
|
// UpdateStorage implements state.Trie, writing the provided storage slot into
|
||||||
// the tree. If the tree is corrupted, an error will be returned.
|
// the tree. If the tree is corrupted, an error will be returned.
|
||||||
func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
||||||
|
|
@ -169,6 +185,19 @@ func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) er
|
||||||
return t.root.Insert(k, v[:], t.nodeResolver)
|
return t.root.Insert(k, v[:], t.nodeResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateStorageBatch attempts to update a list storages in the batch manner.
|
||||||
|
func (t *VerkleTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error {
|
||||||
|
if len(keys) != len(values) {
|
||||||
|
return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values))
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if err := t.UpdateStorage(address, key, values[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteAccount leaves the account untouched, as no account deletion can happen
|
// DeleteAccount leaves the account untouched, as no account deletion can happen
|
||||||
// in verkle.
|
// in verkle.
|
||||||
// There is a special corner case, in which an account that is prefunded, CREATE2-d
|
// There is a special corner case, in which an account that is prefunded, CREATE2-d
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue