mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
core/state: persist state size metrics
This commit is contained in:
parent
2e5cd21edf
commit
288588b852
5 changed files with 245 additions and 30 deletions
|
|
@ -360,3 +360,22 @@ func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keyS
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasStateSizeStats checks if the state size stats for the specified state root is present in the database.
|
||||||
|
func HasStateSizeStats(db ethdb.KeyValueReader, root common.Hash) bool {
|
||||||
|
ok, _ := db.Has(stateSizeStatsKey(root))
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStateSizeStats retrieves the state size stats for the specified state root.
|
||||||
|
func ReadStateSizeStats(db ethdb.KeyValueReader, root common.Hash) []byte {
|
||||||
|
data, _ := db.Get(stateSizeStatsKey(root))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStateSizeStats stores the state size stats for the specified state root.
|
||||||
|
func WriteStateSizeStats(db ethdb.KeyValueWriter, root common.Hash, stats []byte) {
|
||||||
|
if err := db.Put(stateSizeStatsKey(root), stats); err != nil {
|
||||||
|
log.Crit("Failed to store state size stats", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -427,6 +427,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
filterMapRows stat
|
filterMapRows stat
|
||||||
filterMapLastBlock stat
|
filterMapLastBlock stat
|
||||||
filterMapBlockLV stat
|
filterMapBlockLV stat
|
||||||
|
stateSizeStats stat
|
||||||
|
|
||||||
// Path-mode archive data
|
// Path-mode archive data
|
||||||
stateIndex stat
|
stateIndex stat
|
||||||
|
|
@ -527,6 +528,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
|
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
|
||||||
stateIndex.add(size)
|
stateIndex.add(size)
|
||||||
|
|
||||||
|
// State size stats
|
||||||
|
case bytes.HasPrefix(key, stateSizeStatsPrefix):
|
||||||
|
stateSizeStats.add(size)
|
||||||
|
|
||||||
// Verkle trie data is detected, determine the sub-category
|
// Verkle trie data is detected, determine the sub-category
|
||||||
case bytes.HasPrefix(key, VerklePrefix):
|
case bytes.HasPrefix(key, VerklePrefix):
|
||||||
remain := key[len(VerklePrefix):]
|
remain := key[len(VerklePrefix):]
|
||||||
|
|
@ -631,6 +636,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||||
{"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
|
{"Key-Value store", "Beacon sync headers", beaconHeaders.sizeString(), beaconHeaders.countString()},
|
||||||
{"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
|
{"Key-Value store", "Clique snapshots", cliqueSnaps.sizeString(), cliqueSnaps.countString()},
|
||||||
{"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
|
{"Key-Value store", "Singleton metadata", metadata.sizeString(), metadata.countString()},
|
||||||
|
{"Key-Value store", "State size stats", stateSizeStats.sizeString(), stateSizeStats.countString()},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect all registered append-only file store then.
|
// Inspect all registered append-only file store then.
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,9 @@ var (
|
||||||
|
|
||||||
// Verkle transition information
|
// Verkle transition information
|
||||||
VerkleTransitionStatePrefix = []byte("verkle-transition-state-")
|
VerkleTransitionStatePrefix = []byte("verkle-transition-state-")
|
||||||
|
|
||||||
|
// State size metrics
|
||||||
|
stateSizeStatsPrefix = []byte("st") // stateSizeStatsPrefix + root -> stat (rlp)
|
||||||
)
|
)
|
||||||
|
|
||||||
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
||||||
|
|
@ -459,3 +462,10 @@ func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID
|
||||||
func transitionStateKey(hash common.Hash) []byte {
|
func transitionStateKey(hash common.Hash) []byte {
|
||||||
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
|
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stateSizeStatsKey(root common.Hash) []byte {
|
||||||
|
result := make([]byte, len(stateSizeStatsPrefix)+common.HashLength)
|
||||||
|
copy(result, stateSizeStatsPrefix)
|
||||||
|
copy(result[len(stateSizeStatsPrefix):], root.Bytes())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
@ -263,7 +264,7 @@ type stateSizeQuery struct {
|
||||||
|
|
||||||
// SizeTracker handles the state size initialization and tracks of state size metrics.
|
// SizeTracker handles the state size initialization and tracks of state size metrics.
|
||||||
type SizeTracker struct {
|
type SizeTracker struct {
|
||||||
db ethdb.KeyValueStore
|
db ethdb.Database
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
abort chan struct{}
|
abort chan struct{}
|
||||||
aborted chan struct{}
|
aborted chan struct{}
|
||||||
|
|
@ -272,7 +273,7 @@ type SizeTracker struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSizeTracker creates a new state size tracker and starts it automatically
|
// NewSizeTracker creates a new state size tracker and starts it automatically
|
||||||
func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTracker, error) {
|
func NewSizeTracker(db ethdb.Database, triedb *triedb.Database) (*SizeTracker, error) {
|
||||||
if triedb.Scheme() != rawdb.PathScheme {
|
if triedb.Scheme() != rawdb.PathScheme {
|
||||||
return nil, errors.New("state size tracker is not compatible with hash mode")
|
return nil, errors.New("state size tracker is not compatible with hash mode")
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +285,26 @@ func NewSizeTracker(db ethdb.KeyValueStore, triedb *triedb.Database) (*SizeTrack
|
||||||
updateCh: make(chan *stateUpdate),
|
updateCh: make(chan *stateUpdate),
|
||||||
queryCh: make(chan *stateSizeQuery),
|
queryCh: make(chan *stateSizeQuery),
|
||||||
}
|
}
|
||||||
go t.run()
|
|
||||||
|
// Load the existing stats if they exist
|
||||||
|
stats := make(map[common.Hash]SizeStats, statEvictThreshold)
|
||||||
|
header := rawdb.ReadHeadHeader(t.db)
|
||||||
|
headNumber := header.Number.Uint64()
|
||||||
|
for i := 0; header != nil && i < statEvictThreshold; i++ {
|
||||||
|
if enc := rawdb.ReadStateSizeStats(t.db, header.Root); len(enc) > 0 {
|
||||||
|
stats[header.Root] = decodeSizeStats(enc)
|
||||||
|
}
|
||||||
|
header = rawdb.ReadHeader(t.db, header.ParentHash, header.Number.Uint64()-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var h sizeStatsHeap
|
||||||
|
if len(stats) > 0 {
|
||||||
|
h = sizeStatsHeap(slices.Collect(maps.Values(stats)))
|
||||||
|
heap.Init(&h)
|
||||||
|
log.Info("Loaded state size stats from db", "headNumber", headNumber, "count", len(stats))
|
||||||
|
}
|
||||||
|
|
||||||
|
go t.run(stats, h)
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -314,17 +334,22 @@ func (h *sizeStatsHeap) Pop() any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// run performs the state size initialization and handles updates
|
// run performs the state size initialization and handles updates
|
||||||
func (t *SizeTracker) run() {
|
func (t *SizeTracker) run(stats map[common.Hash]SizeStats, h sizeStatsHeap) {
|
||||||
defer close(t.aborted)
|
defer close(t.aborted)
|
||||||
|
|
||||||
var last common.Hash
|
var err error
|
||||||
stats, err := t.init() // launch background thread for state size init
|
if len(stats) == 0 { // if no stats from db, launch background thread for state size init
|
||||||
|
stats, err = t.init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to init state size tracker", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h := sizeStatsHeap(slices.Collect(maps.Values(stats)))
|
|
||||||
heap.Init(&h)
|
|
||||||
|
|
||||||
|
h = sizeStatsHeap(slices.Collect(maps.Values(stats)))
|
||||||
|
heap.Init(&h)
|
||||||
|
}
|
||||||
|
|
||||||
|
var last common.Hash
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case u := <-t.updateCh:
|
case u := <-t.updateCh:
|
||||||
|
|
@ -341,6 +366,8 @@ func (t *SizeTracker) run() {
|
||||||
stats[u.root] = stat
|
stats[u.root] = stat
|
||||||
last = u.root
|
last = u.root
|
||||||
|
|
||||||
|
rawdb.WriteStateSizeStats(t.db, u.root, encodeSizeStats(stat))
|
||||||
|
|
||||||
// Publish statistics to metric system
|
// Publish statistics to metric system
|
||||||
stat.publish()
|
stat.publish()
|
||||||
|
|
||||||
|
|
@ -359,12 +386,23 @@ func (t *SizeTracker) run() {
|
||||||
} else {
|
} else {
|
||||||
root = last
|
root = last
|
||||||
}
|
}
|
||||||
if s, ok := stats[root]; ok {
|
|
||||||
|
// Check in-memory stats first
|
||||||
|
s, ok := stats[root]
|
||||||
|
if ok {
|
||||||
r.result <- &s
|
r.result <- &s
|
||||||
} else {
|
continue
|
||||||
r.result <- nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load from db if exist
|
||||||
|
if enc := rawdb.ReadStateSizeStats(t.db, root); len(enc) > 0 {
|
||||||
|
s := decodeSizeStats(enc)
|
||||||
|
r.result <- &s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not exist, return nil result
|
||||||
|
r.result <- nil
|
||||||
case <-t.abort:
|
case <-t.abort:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -381,9 +419,10 @@ type buildResult struct {
|
||||||
|
|
||||||
func (t *SizeTracker) init() (map[common.Hash]SizeStats, error) {
|
func (t *SizeTracker) init() (map[common.Hash]SizeStats, error) {
|
||||||
// Wait for snapshot completion and then init
|
// Wait for snapshot completion and then init
|
||||||
ticker := time.NewTicker(10 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
if !t.triedb.SnapshotCompleted() {
|
||||||
wait:
|
wait:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -400,6 +439,7 @@ wait:
|
||||||
return nil, errors.New("size tracker closed")
|
return nil, errors.New("size tracker closed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
updates = make(map[common.Hash]*stateUpdate)
|
updates = make(map[common.Hash]*stateUpdate)
|
||||||
|
|
@ -413,7 +453,6 @@ wait:
|
||||||
updates[u.root] = u
|
updates[u.root] = u
|
||||||
children[u.originRoot] = append(children[u.originRoot], u.root)
|
children[u.originRoot] = append(children[u.originRoot], u.root)
|
||||||
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
|
log.Debug("Received state update", "root", u.root, "blockNumber", u.blockNumber)
|
||||||
|
|
||||||
case r := <-t.queryCh:
|
case r := <-t.queryCh:
|
||||||
r.err = errors.New("state size is not initialized yet")
|
r.err = errors.New("state size is not initialized yet")
|
||||||
r.result <- nil
|
r.result <- nil
|
||||||
|
|
@ -453,7 +492,11 @@ wait:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
stats[child] = base.add(diff)
|
stat := base.add(diff)
|
||||||
|
stats[child] = stat
|
||||||
|
|
||||||
|
rawdb.WriteStateSizeStats(t.db, child, encodeSizeStats(stat))
|
||||||
|
|
||||||
if err := apply(child, stats[child]); err != nil {
|
if err := apply(child, stats[child]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -466,6 +509,7 @@ wait:
|
||||||
|
|
||||||
// Set initial latest stats
|
// Set initial latest stats
|
||||||
stats[result.root] = result.stat
|
stats[result.root] = result.stat
|
||||||
|
rawdb.WriteStateSizeStats(t.db, result.root, encodeSizeStats(result.stat))
|
||||||
log.Info("Measured persistent state size", "root", result.root, "number", result.blockNumber, "stat", result.stat, "elapsed", common.PrettyDuration(result.elapsed))
|
log.Info("Measured persistent state size", "root", result.root, "number", result.blockNumber, "stat", result.stat, "elapsed", common.PrettyDuration(result.elapsed))
|
||||||
return stats, nil
|
return stats, nil
|
||||||
|
|
||||||
|
|
@ -672,3 +716,64 @@ func (t *SizeTracker) Query(root *common.Hash) (*SizeStats, error) {
|
||||||
return <-r.result, r.err
|
return <-r.result, r.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sizeStatsRLP is the RLP-encodable version of SizeStats.
|
||||||
|
// RLP only supports unsigned integers, so we use uint64 for persistence.
|
||||||
|
// Cumulative stats are always non-negative, so this is safe for storage.
|
||||||
|
type sizeStatsRLP struct {
|
||||||
|
StateRoot common.Hash
|
||||||
|
BlockNumber uint64
|
||||||
|
Accounts uint64
|
||||||
|
AccountBytes uint64
|
||||||
|
Storages uint64
|
||||||
|
StorageBytes uint64
|
||||||
|
AccountTrienodes uint64
|
||||||
|
AccountTrienodeBytes uint64
|
||||||
|
StorageTrienodes uint64
|
||||||
|
StorageTrienodeBytes uint64
|
||||||
|
ContractCodes uint64
|
||||||
|
ContractCodeBytes uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeSizeStats(stat SizeStats) []byte {
|
||||||
|
rlpStat := sizeStatsRLP{
|
||||||
|
StateRoot: stat.StateRoot,
|
||||||
|
BlockNumber: stat.BlockNumber,
|
||||||
|
Accounts: uint64(stat.Accounts),
|
||||||
|
AccountBytes: uint64(stat.AccountBytes),
|
||||||
|
Storages: uint64(stat.Storages),
|
||||||
|
StorageBytes: uint64(stat.StorageBytes),
|
||||||
|
AccountTrienodes: uint64(stat.AccountTrienodes),
|
||||||
|
AccountTrienodeBytes: uint64(stat.AccountTrienodeBytes),
|
||||||
|
StorageTrienodes: uint64(stat.StorageTrienodes),
|
||||||
|
StorageTrienodeBytes: uint64(stat.StorageTrienodeBytes),
|
||||||
|
ContractCodes: uint64(stat.ContractCodes),
|
||||||
|
ContractCodeBytes: uint64(stat.ContractCodeBytes),
|
||||||
|
}
|
||||||
|
data, err := rlp.EncodeToBytes(&rlpStat)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to encode state size stats", "err", err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeSizeStats(data []byte) SizeStats {
|
||||||
|
var rlpStat sizeStatsRLP
|
||||||
|
if err := rlp.DecodeBytes(data, &rlpStat); err != nil {
|
||||||
|
log.Crit("Failed to decode state size stats", "err", err)
|
||||||
|
}
|
||||||
|
return SizeStats{
|
||||||
|
StateRoot: rlpStat.StateRoot,
|
||||||
|
BlockNumber: rlpStat.BlockNumber,
|
||||||
|
Accounts: int64(rlpStat.Accounts),
|
||||||
|
AccountBytes: int64(rlpStat.AccountBytes),
|
||||||
|
Storages: int64(rlpStat.Storages),
|
||||||
|
StorageBytes: int64(rlpStat.StorageBytes),
|
||||||
|
AccountTrienodes: int64(rlpStat.AccountTrienodes),
|
||||||
|
AccountTrienodeBytes: int64(rlpStat.AccountTrienodeBytes),
|
||||||
|
StorageTrienodes: int64(rlpStat.StorageTrienodes),
|
||||||
|
StorageTrienodeBytes: int64(rlpStat.StorageTrienodeBytes),
|
||||||
|
ContractCodes: int64(rlpStat.ContractCodes),
|
||||||
|
ContractCodeBytes: int64(rlpStat.ContractCodeBytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,3 +237,78 @@ func TestSizeTracker(t *testing.T) {
|
||||||
t.Errorf("Storage trie node bytes mismatch: expected %d, got %d", expectedStats.StorageTrienodeBytes, actualStats.StorageTrienodeBytes)
|
t.Errorf("Storage trie node bytes mismatch: expected %d, got %d", expectedStats.StorageTrienodeBytes, actualStats.StorageTrienodeBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSizeStatsPersistence(t *testing.T) {
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
root := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef")
|
||||||
|
|
||||||
|
originalStats := SizeStats{
|
||||||
|
StateRoot: root,
|
||||||
|
BlockNumber: 12345,
|
||||||
|
Accounts: 1000,
|
||||||
|
AccountBytes: 50000,
|
||||||
|
Storages: 5000,
|
||||||
|
StorageBytes: 200000,
|
||||||
|
AccountTrienodes: 300,
|
||||||
|
AccountTrienodeBytes: 15000,
|
||||||
|
StorageTrienodes: 1500,
|
||||||
|
StorageTrienodeBytes: 75000,
|
||||||
|
ContractCodes: 100,
|
||||||
|
ContractCodeBytes: 500000,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify no stats exist initially
|
||||||
|
if rawdb.HasStateSizeStats(db, root) {
|
||||||
|
t.Fatal("Stats should not exist initially")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawdb.WriteStateSizeStats(db, root, encodeSizeStats(originalStats))
|
||||||
|
|
||||||
|
// Verify stats exist
|
||||||
|
if !rawdb.HasStateSizeStats(db, root) {
|
||||||
|
t.Fatal("Stats should exist after write")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := rawdb.ReadStateSizeStats(db, root)
|
||||||
|
loadedStats := decodeSizeStats(data)
|
||||||
|
|
||||||
|
// Verify all fields match
|
||||||
|
if loadedStats.StateRoot != originalStats.StateRoot {
|
||||||
|
t.Errorf("StateRoot mismatch: have %x, want %x", loadedStats.StateRoot, originalStats.StateRoot)
|
||||||
|
}
|
||||||
|
if loadedStats.BlockNumber != originalStats.BlockNumber {
|
||||||
|
t.Errorf("BlockNumber mismatch: have %d, want %d", loadedStats.BlockNumber, originalStats.BlockNumber)
|
||||||
|
}
|
||||||
|
if loadedStats.Accounts != originalStats.Accounts {
|
||||||
|
t.Errorf("Accounts mismatch: have %d, want %d", loadedStats.Accounts, originalStats.Accounts)
|
||||||
|
}
|
||||||
|
if loadedStats.AccountBytes != originalStats.AccountBytes {
|
||||||
|
t.Errorf("AccountBytes mismatch: have %d, want %d", loadedStats.AccountBytes, originalStats.AccountBytes)
|
||||||
|
}
|
||||||
|
if loadedStats.Storages != originalStats.Storages {
|
||||||
|
t.Errorf("Storages mismatch: have %d, want %d", loadedStats.Storages, originalStats.Storages)
|
||||||
|
}
|
||||||
|
if loadedStats.StorageBytes != originalStats.StorageBytes {
|
||||||
|
t.Errorf("StorageBytes mismatch: have %d, want %d", loadedStats.StorageBytes, originalStats.StorageBytes)
|
||||||
|
}
|
||||||
|
if loadedStats.AccountTrienodes != originalStats.AccountTrienodes {
|
||||||
|
t.Errorf("AccountTrienodes mismatch: have %d, want %d", loadedStats.AccountTrienodes, originalStats.AccountTrienodes)
|
||||||
|
}
|
||||||
|
if loadedStats.AccountTrienodeBytes != originalStats.AccountTrienodeBytes {
|
||||||
|
t.Errorf("AccountTrienodeBytes mismatch: have %d, want %d", loadedStats.AccountTrienodeBytes, originalStats.AccountTrienodeBytes)
|
||||||
|
}
|
||||||
|
if loadedStats.StorageTrienodes != originalStats.StorageTrienodes {
|
||||||
|
t.Errorf("StorageTrienodes mismatch: have %d, want %d", loadedStats.StorageTrienodes, originalStats.StorageTrienodes)
|
||||||
|
}
|
||||||
|
if loadedStats.StorageTrienodeBytes != originalStats.StorageTrienodeBytes {
|
||||||
|
t.Errorf("StorageTrienodeBytes mismatch: have %d, want %d", loadedStats.StorageTrienodeBytes, originalStats.StorageTrienodeBytes)
|
||||||
|
}
|
||||||
|
if loadedStats.ContractCodes != originalStats.ContractCodes {
|
||||||
|
t.Errorf("ContractCodes mismatch: have %d, want %d", loadedStats.ContractCodes, originalStats.ContractCodes)
|
||||||
|
}
|
||||||
|
if loadedStats.ContractCodeBytes != originalStats.ContractCodeBytes {
|
||||||
|
t.Errorf("ContractCodeBytes mismatch: have %d, want %d", loadedStats.ContractCodeBytes, originalStats.ContractCodeBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue