Skip to content
This repository was archived by the owner on Jun 6, 2025. It is now read-only.

Commit a9905e4

Browse files
committed
core/state: use parallel trie insert to update storage trie
When there are more than parallelInsertThreshold (currently set to 1000) pending storages update to storage trie, we will use the new TryBatchInsert to parallel insert these storages if possible.
1 parent ea23006 commit a9905e4

File tree

2 files changed

+110
-1
lines changed

2 files changed

+110
-1
lines changed

core/blockchain_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package core
1818

1919
import (
20+
"encoding/binary"
2021
"errors"
2122
"fmt"
2223
"io/ioutil"
@@ -4312,3 +4313,89 @@ func TestSidecarsPruning(t *testing.T) {
43124313
}
43134314
}
43144315
}
4316+
4317+
func TestBlockChain_2000StorageUpdate(t *testing.T) {
4318+
var (
4319+
numTxs = 2000
4320+
signer = types.HomesteadSigner{}
4321+
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
4322+
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
4323+
bankFunds = big.NewInt(100000000000000000)
4324+
contractAddress = common.HexToAddress("0x1234")
4325+
gspec = Genesis{
4326+
Config: params.TestChainConfig,
4327+
Alloc: GenesisAlloc{
4328+
testBankAddress: {Balance: bankFunds},
4329+
contractAddress: {
4330+
Nonce: 1,
4331+
Balance: common.Big0,
4332+
// Store 1 into slot passed by calldata
4333+
Code: []byte{
4334+
byte(vm.PUSH0),
4335+
byte(vm.CALLDATALOAD),
4336+
byte(vm.PUSH1),
4337+
byte(0x1),
4338+
byte(vm.SWAP1),
4339+
byte(vm.SSTORE),
4340+
byte(vm.STOP),
4341+
},
4342+
Storage: make(map[common.Hash]common.Hash),
4343+
},
4344+
},
4345+
GasLimit: 100e6, // 100 M
4346+
}
4347+
)
4348+
4349+
for i := 0; i < 1000; i++ {
4350+
gspec.Alloc[contractAddress].Storage[common.BigToHash(big.NewInt(int64(i)))] = common.BigToHash(big.NewInt(0x100))
4351+
}
4352+
4353+
// Generate the original common chain segment and the two competing forks
4354+
engine := ethash.NewFaker()
4355+
db := rawdb.NewMemoryDatabase()
4356+
genesis := gspec.MustCommit(db)
4357+
4358+
blockGenerator := func(i int, block *BlockGen) {
4359+
block.SetCoinbase(common.Address{1})
4360+
for txi := 0; txi < numTxs; txi++ {
4361+
var calldata [32]byte
4362+
binary.BigEndian.PutUint64(calldata[:], uint64(txi))
4363+
tx, err := types.SignTx(
4364+
types.NewTransaction(uint64(txi), contractAddress, common.Big0, 100_000,
4365+
block.header.BaseFee, calldata[:]),
4366+
signer,
4367+
testBankKey)
4368+
if err != nil {
4369+
t.Error(err)
4370+
}
4371+
block.AddTx(tx)
4372+
}
4373+
}
4374+
4375+
shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, blockGenerator, true)
4376+
err := os.Mkdir("./pebble", 0775)
4377+
if err != nil {
4378+
t.Fatal(err)
4379+
}
4380+
defer os.RemoveAll("./pebble")
4381+
// Import the shared chain and the original canonical one
4382+
diskdb, err := rawdb.NewPebbleDBDatabase("./pebble", 1024, 500000, "", false, false)
4383+
if err != nil {
4384+
t.Fatal(err)
4385+
}
4386+
defer diskdb.Close()
4387+
gspec.MustCommit(diskdb)
4388+
4389+
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
4390+
if err != nil {
4391+
t.Fatalf("failed to create tester chain: %v", err)
4392+
}
4393+
if _, err := chain.InsertChain(shared, nil); err != nil {
4394+
t.Fatalf("failed to insert shared chain: %v", err)
4395+
}
4396+
4397+
blockHash := chain.CurrentBlock().Hash()
4398+
if blockHash != (common.HexToHash("0x684f656efba5a77f0e8b4c768a2b3479b28250fd7b81dbb9a888abf6180b01bd")) {
4399+
t.Fatalf("Block hash mismatches, exp %s got %s", common.Hash{}, blockHash)
4400+
}
4401+
}

core/state/state_object.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ import (
2828
"github.com/ethereum/go-ethereum/crypto"
2929
"github.com/ethereum/go-ethereum/metrics"
3030
"github.com/ethereum/go-ethereum/rlp"
31+
"github.com/ethereum/go-ethereum/trie"
3132
)
3233

3334
var emptyCodeHash = crypto.Keccak256(nil)
3435

36+
const ParallelInsertThreshold = 1000
37+
3538
type Code []byte
3639

3740
func (c Code) String() string {
@@ -338,6 +341,16 @@ func (s *stateObject) updateTrie(db Database) Trie {
338341
tr := s.getTrie(db)
339342
hasher := s.db.hasher
340343

344+
var (
345+
parallelInsert, ok bool
346+
secureTrie *trie.SecureTrie
347+
keys, values [][]byte
348+
)
349+
if len(s.pendingStorage) > ParallelInsertThreshold {
350+
if secureTrie, ok = tr.(*trie.SecureTrie); ok {
351+
parallelInsert = true
352+
}
353+
}
341354
usedStorage := make([][]byte, 0, len(s.pendingStorage))
342355
for key, value := range s.pendingStorage {
343356
// Skip noop changes, persist actual changes
@@ -353,8 +366,14 @@ func (s *stateObject) updateTrie(db Database) Trie {
353366
} else {
354367
// Encoding []byte cannot fail, ok to ignore the error.
355368
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
356-
s.setError(tr.TryUpdate(key[:], v))
357369
s.db.StorageUpdated += 1
370+
if parallelInsert {
371+
key := key
372+
keys = append(keys, key[:])
373+
values = append(values, v)
374+
} else {
375+
s.setError(tr.TryUpdate(key[:], v))
376+
}
358377
}
359378
// If state snapshotting is active, cache the data til commit
360379
if s.db.snap != nil {
@@ -369,6 +388,9 @@ func (s *stateObject) updateTrie(db Database) Trie {
369388
}
370389
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
371390
}
391+
if parallelInsert && len(keys) > 0 {
392+
s.setError(secureTrie.TryBatchInsert(keys, values))
393+
}
372394
if s.db.prefetcher != nil {
373395
s.db.prefetcher.used(s.data.Root, usedStorage)
374396
}

0 commit comments

Comments
 (0)