github.com/klaytn/klaytn@v1.10.2/blockchain/state_migration.go (about)

     1  // Copyright 2020 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package blockchain
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"runtime"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/VictoriaMetrics/fastcache"
    28  	"github.com/klaytn/klaytn/blockchain/types"
    29  
    30  	"github.com/alecthomas/units"
    31  	lru "github.com/hashicorp/golang-lru"
    32  	"github.com/klaytn/klaytn/blockchain/state"
    33  	"github.com/klaytn/klaytn/common"
    34  	"github.com/klaytn/klaytn/common/mclock"
    35  	"github.com/klaytn/klaytn/log"
    36  	"github.com/klaytn/klaytn/storage/database"
    37  	"github.com/klaytn/klaytn/storage/statedb"
    38  )
    39  
    40  var (
    41  	stopWarmUpErr           = errors.New("warm-up terminate by StopWarmUp")
    42  	blockChainStopWarmUpErr = errors.New("warm-up terminate as blockchain stopped")
    43  )
    44  
    45  type stateTrieMigrationDB struct {
    46  	database.DBManager
    47  }
    48  
    49  func (td *stateTrieMigrationDB) ReadCachedTrieNode(hash common.Hash) ([]byte, error) {
    50  	return td.ReadCachedTrieNodeFromNew(hash)
    51  }
    52  
    53  func (td *stateTrieMigrationDB) ReadCachedTrieNodePreimage(secureKey []byte) ([]byte, error) {
    54  	return td.ReadCachedTrieNodePreimageFromNew(secureKey)
    55  }
    56  
    57  func (td *stateTrieMigrationDB) ReadStateTrieNode(key []byte) ([]byte, error) {
    58  	return td.ReadStateTrieNodeFromNew(key)
    59  }
    60  
    61  func (td *stateTrieMigrationDB) HasStateTrieNode(key []byte) (bool, error) {
    62  	return td.HasStateTrieNodeFromNew(key)
    63  }
    64  
    65  func (td *stateTrieMigrationDB) HasCodeWithPrefix(hash common.Hash) bool {
    66  	return td.HasCodeWithPrefixFromNew(hash)
    67  }
    68  
    69  func (td *stateTrieMigrationDB) ReadPreimage(hash common.Hash) []byte {
    70  	return td.ReadPreimageFromNew(hash)
    71  }
    72  
    73  func (bc *BlockChain) stateMigrationCommit(s *statedb.TrieSync, batch database.Batch) (int, error) {
    74  	written, err := s.Commit(batch)
    75  	if written == 0 || err != nil {
    76  		return written, err
    77  	}
    78  
    79  	if batch.ValueSize() > database.IdealBatchSize {
    80  		if err := batch.Write(); err != nil {
    81  			return 0, fmt.Errorf("DB write error: %v", err)
    82  		}
    83  		batch.Reset()
    84  	}
    85  
    86  	return written, nil
    87  }
    88  
    89  func (bc *BlockChain) concurrentRead(db state.Database, quitCh chan struct{}, hashCh chan common.Hash, resultCh chan statedb.SyncResult) {
    90  	for {
    91  		select {
    92  		case <-quitCh:
    93  			return
    94  		case hash := <-hashCh:
    95  			data, err := db.TrieDB().NodeFromOld(hash)
    96  			if err != nil {
    97  				data, err = db.ContractCode(hash)
    98  			}
    99  			if err != nil {
   100  				resultCh <- statedb.SyncResult{Hash: hash, Err: err}
   101  				continue
   102  			}
   103  			resultCh <- statedb.SyncResult{Hash: hash, Data: data}
   104  		}
   105  	}
   106  }
   107  
   108  // migrateState is the core implementation of state trie migration.
   109  // This migrates a trie from StateTrieDB to StateTrieMigrationDB.
   110  // Reading StateTrieDB happens in parallel and writing StateTrieMigrationDB happens in batch write.
   111  //
   112  // Before this function is called, StateTrieMigrationDB should be set.
   113  // After the migration finish, the original StateTrieDB is removed and StateTrieMigrationDB becomes a new StateTrieDB.
   114  func (bc *BlockChain) migrateState(rootHash common.Hash) (returnErr error) {
   115  	bc.migrationErr = nil
   116  	defer func() {
   117  		bc.migrationErr = returnErr
   118  		// If migration stops by quit signal, it doesn't finish migration and it it will restart again.
   119  		if returnErr != ErrQuitBySignal {
   120  			// lock to prevent from a conflict of state DB close and state DB write
   121  			bc.mu.Lock()
   122  			bc.db.FinishStateMigration(returnErr == nil)
   123  			bc.mu.Unlock()
   124  		}
   125  	}()
   126  
   127  	start := time.Now()
   128  
   129  	srcState := bc.StateCache()
   130  	dstState := state.NewDatabase(&stateTrieMigrationDB{bc.db})
   131  
   132  	// NOTE: lruCache is mandatory when state migration and block processing are executed simultaneously
   133  	lruCache, _ := lru.New(int(2 * units.Giga / common.HashLength)) // 2GB for 62,500,000 common.Hash key values
   134  	trieSync := state.NewStateSync(rootHash, dstState.TrieDB().DiskDB(), nil, lruCache, nil)
   135  	var queue []common.Hash
   136  
   137  	quitCh := make(chan struct{})
   138  	defer close(quitCh)
   139  
   140  	// Prepare concurrent read goroutines
   141  	threads := runtime.NumCPU()
   142  	hashCh := make(chan common.Hash, threads)
   143  	resultCh := make(chan statedb.SyncResult, threads)
   144  
   145  	for th := 0; th < threads; th++ {
   146  		go bc.concurrentRead(srcState, quitCh, hashCh, resultCh)
   147  	}
   148  
   149  	stateTrieBatch := dstState.TrieDB().DiskDB().NewBatch(database.StateTrieDB)
   150  	stats := migrationStats{initialStartTime: start, startTime: mclock.Now()}
   151  
   152  	if bc.testMigrationHook != nil {
   153  		bc.testMigrationHook()
   154  	}
   155  
   156  	// Migration main loop
   157  	for trieSync.Pending() > 0 {
   158  		nodes, _, codes := trieSync.Missing(1024)
   159  		queue = append(queue[:0], append(nodes, codes...)...)
   160  		results := make([]statedb.SyncResult, len(queue))
   161  
   162  		// Read the trie nodes
   163  		startRead := time.Now()
   164  		go func() {
   165  			for _, hash := range queue {
   166  				hashCh <- hash
   167  			}
   168  		}()
   169  
   170  		for i := 0; i < len(queue); i++ {
   171  			result := <-resultCh
   172  			if result.Err != nil {
   173  				logger.Error("State migration is failed by resultCh",
   174  					"result.hash", result.Hash.String(), "result.Err", result.Err)
   175  				return fmt.Errorf("failed to retrieve node data for %x: %v", result.Hash, result.Err)
   176  			}
   177  			results[i] = result
   178  		}
   179  		stats.read += len(queue)
   180  		stats.readElapsed += time.Since(startRead)
   181  
   182  		// Process trie nodes
   183  		startProcess := time.Now()
   184  		for index, result := range results {
   185  			if err := trieSync.Process(result); err != nil {
   186  				logger.Error("State migration is failed by process error", "err", err)
   187  				return fmt.Errorf("failed to process result #%d: %v", index, err)
   188  			}
   189  		}
   190  		stats.processElapsed += time.Since(startProcess)
   191  
   192  		// Commit trie nodes
   193  		startWrite := time.Now()
   194  		written, err := bc.stateMigrationCommit(trieSync, stateTrieBatch)
   195  		if err != nil {
   196  			logger.Error("State migration is failed by commit error", "err", err)
   197  			return fmt.Errorf("failed to commit data #%d: %v", written, err)
   198  		}
   199  		stats.committed += written
   200  		stats.writeElapsed += time.Since(startWrite)
   201  
   202  		// Report progress
   203  		stats.stateMigrationReport(false, trieSync.Pending(), trieSync.CalcProgressPercentage())
   204  
   205  		select {
   206  		case <-bc.stopStateMigration:
   207  			logger.Info("State migration terminated by request")
   208  			return errors.New("stop state migration")
   209  		case <-bc.quit:
   210  			logger.Info("State migration stopped by quit signal; should continue on node restart")
   211  			return ErrQuitBySignal
   212  		default:
   213  		}
   214  
   215  		bc.readCnt, bc.committedCnt, bc.pendingCnt, bc.progress = stats.totalRead, stats.totalCommitted, trieSync.Pending(), stats.progress
   216  	}
   217  
   218  	// Flush trie nodes which is not written yet.
   219  	if err := stateTrieBatch.Write(); err != nil {
   220  		logger.Error("State migration is failed by commit error", "err", err)
   221  		return fmt.Errorf("DB write error: %v", err)
   222  	}
   223  
   224  	stats.stateMigrationReport(true, trieSync.Pending(), trieSync.CalcProgressPercentage())
   225  	bc.readCnt, bc.committedCnt, bc.pendingCnt, bc.progress = stats.totalRead, stats.totalCommitted, trieSync.Pending(), stats.progress
   226  
   227  	// Clear memory of trieSync
   228  	trieSync = nil
   229  
   230  	elapsed := time.Since(start)
   231  	speed := float64(stats.totalCommitted) / elapsed.Seconds()
   232  	logger.Info("State migration : Copy is done",
   233  		"totalRead", stats.totalRead, "totalCommitted", stats.totalCommitted,
   234  		"totalElapsed", elapsed, "committed per second", speed)
   235  
   236  	startCheck := time.Now()
   237  	if err := state.CheckStateConsistencyParallel(srcState, dstState, rootHash, bc.quit); err != nil {
   238  		logger.Error("State migration : copied stateDB is invalid", "err", err)
   239  		return err
   240  	}
   241  	checkElapsed := time.Since(startCheck)
   242  	logger.Info("State migration is completed", "copyElapsed", elapsed, "checkElapsed", checkElapsed)
   243  	return nil
   244  }
   245  
   246  // migrationStats tracks and reports on state migration.
   247  type migrationStats struct {
   248  	read, committed, totalRead, totalCommitted, pending int
   249  	progress                                            float64
   250  	initialStartTime                                    time.Time
   251  	startTime                                           mclock.AbsTime
   252  	readElapsed                                         time.Duration
   253  	processElapsed                                      time.Duration
   254  	writeElapsed                                        time.Duration
   255  }
   256  
   257  func (st *migrationStats) stateMigrationReport(force bool, pending int, progress float64) {
   258  	var (
   259  		now     = mclock.Now()
   260  		elapsed = time.Duration(now) - time.Duration(st.startTime)
   261  	)
   262  
   263  	if force || elapsed >= log.StatsReportLimit {
   264  		st.totalRead += st.read
   265  		st.totalCommitted += st.committed
   266  		st.pending, st.progress = pending, progress
   267  
   268  		progressStr := strconv.FormatFloat(st.progress, 'f', 4, 64)
   269  		progressStr = strings.TrimRight(progressStr, "0")
   270  		progressStr = strings.TrimRight(progressStr, ".") + "%"
   271  
   272  		logger.Info("State migration progress",
   273  			"progress", progressStr,
   274  			"totalRead", st.totalRead, "totalCommitted", st.totalCommitted, "pending", st.pending,
   275  			"read", st.read, "readElapsed", st.readElapsed, "processElapsed", st.processElapsed,
   276  			"written", st.committed, "writeElapsed", st.writeElapsed,
   277  			"elapsed", common.PrettyDuration(elapsed),
   278  			"totalElapsed", time.Since(st.initialStartTime))
   279  
   280  		st.read, st.committed = 0, 0
   281  		st.startTime = now
   282  	}
   283  }
   284  
   285  func (bc *BlockChain) checkTrieContents(oldDB, newDB *statedb.Database, root common.Hash) ([]common.Address, error) {
   286  	oldTrie, err := statedb.NewSecureTrie(root, oldDB)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	newTrie, err := statedb.NewSecureTrie(root, newDB)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	diff, _ := statedb.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
   297  	iter := statedb.NewIterator(diff)
   298  
   299  	var dirty []common.Address
   300  
   301  	for iter.Next() {
   302  		key := newTrie.GetKey(iter.Key)
   303  		if key == nil {
   304  			return nil, fmt.Errorf("no preimage found for hash %x", iter.Key)
   305  		}
   306  
   307  		dirty = append(dirty, common.BytesToAddress(key))
   308  	}
   309  
   310  	return dirty, nil
   311  }
   312  
   313  // restartStateMigration is called when a server is restarted while migration. The migration continues.
   314  func (bc *BlockChain) restartStateMigration() {
   315  	if bc.db.InMigration() {
   316  		number := bc.db.MigrationBlockNumber()
   317  
   318  		block := bc.GetBlockByNumber(number)
   319  		if block == nil {
   320  			logger.Error("failed to get migration block number", "blockNumber", number)
   321  			return
   322  		}
   323  
   324  		root := block.Root()
   325  		logger.Warn("State migration : Restarted", "blockNumber", number, "root", root.String())
   326  
   327  		bc.wg.Add(1)
   328  		go func() {
   329  			bc.migrateState(root)
   330  			bc.wg.Done()
   331  		}()
   332  	}
   333  }
   334  
   335  // PrepareStateMigration sets prepareStateMigration to be called in checkStartStateMigration.
   336  func (bc *BlockChain) PrepareStateMigration() error {
   337  	if bc.db.InMigration() || bc.prepareStateMigration {
   338  		return errors.New("migration already started")
   339  	}
   340  
   341  	bc.prepareStateMigration = true
   342  	logger.Info("State migration is prepared", "expectedMigrationStartingBlockNumber", bc.CurrentBlock().NumberU64()+1)
   343  
   344  	return nil
   345  }
   346  
   347  func (bc *BlockChain) checkStartStateMigration(number uint64, root common.Hash) bool {
   348  	if bc.prepareStateMigration {
   349  		logger.Info("State migration is started", "block", number, "root", root)
   350  
   351  		if err := bc.StartStateMigration(number, root); err != nil {
   352  			logger.Error("Failed to start state migration", "err", err)
   353  		}
   354  
   355  		bc.prepareStateMigration = false
   356  
   357  		return true
   358  	}
   359  
   360  	return false
   361  }
   362  
   363  // migrationPrerequisites is a collection of functions that needs to be run
   364  // before state trie migration. If one of the functions fails to run,
   365  // the migration will not start.
   366  var migrationPrerequisites []func(uint64) error
   367  
   368  func RegisterMigrationPrerequisites(f func(uint64) error) {
   369  	migrationPrerequisites = append(migrationPrerequisites, f)
   370  }
   371  
   372  // StartStateMigration checks prerequisites, configures DB and starts migration.
   373  func (bc *BlockChain) StartStateMigration(number uint64, root common.Hash) error {
   374  	if bc.db.InMigration() {
   375  		return errors.New("migration already started")
   376  	}
   377  
   378  	for _, f := range migrationPrerequisites {
   379  		if err := f(number); err != nil {
   380  			return err
   381  		}
   382  	}
   383  
   384  	if err := bc.db.CreateMigrationDBAndSetStatus(number); err != nil {
   385  		return err
   386  	}
   387  
   388  	bc.wg.Add(1)
   389  	go func() {
   390  		bc.migrateState(root)
   391  		bc.wg.Done()
   392  	}()
   393  
   394  	return nil
   395  }
   396  
   397  func (bc *BlockChain) StopStateMigration() error {
   398  	if !bc.db.InMigration() {
   399  		return errors.New("not in migration")
   400  	}
   401  
   402  	bc.stopStateMigration <- struct{}{}
   403  
   404  	return nil
   405  }
   406  
   407  // StateMigrationStatus returns if it is in migration, the block number of in migration,
   408  // number of committed blocks and number of pending blocks
   409  func (bc *BlockChain) StateMigrationStatus() (bool, uint64, int, int, int, float64, error) {
   410  	return bc.db.InMigration(), bc.db.MigrationBlockNumber(), bc.readCnt, bc.committedCnt, bc.pendingCnt, bc.progress, bc.migrationErr
   411  }
   412  
   413  // iterateStateTrie runs state.Iterator, generated from the given state trie node hash,
   414  // until it reaches end. If it reaches end, it will send a nil error to errCh to indicate that
   415  // it has been finished.
   416  func (bc *BlockChain) iterateStateTrie(root common.Hash, db state.Database, resultCh chan struct{}, errCh chan error) (resultErr error) {
   417  	defer func() { errCh <- resultErr }()
   418  
   419  	stateDB, err := state.New(root, db, nil)
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	it := state.NewNodeIterator(stateDB)
   425  	for it.Next() {
   426  		resultCh <- struct{}{}
   427  		select {
   428  		case <-bc.quitWarmUp:
   429  			return stopWarmUpErr
   430  		case <-bc.quit:
   431  			return blockChainStopWarmUpErr
   432  		default:
   433  		}
   434  	}
   435  	return nil
   436  }
   437  
   438  // warmUpChecker receives errors from each warm-up goroutine.
   439  // If it receives a nil error, it means a child goroutine is successfully terminated.
   440  // It also periodically checks and logs warm-up progress.
   441  func (bc *BlockChain) warmUpChecker(mainTrieDB *statedb.Database, numChildren int,
   442  	resultCh chan struct{}, errCh chan error,
   443  ) {
   444  	defer func() { bc.quitWarmUp = nil }()
   445  
   446  	cache := mainTrieDB.TrieNodeCache()
   447  	mainTrieCacheLimit := mainTrieDB.GetTrieNodeLocalCacheByteLimit()
   448  	logged := time.Now()
   449  	var context []interface{}
   450  	var percent uint64
   451  	var cnt int
   452  
   453  	updateContext := func() {
   454  		switch c := cache.(type) {
   455  		case *statedb.FastCache:
   456  			stats := c.UpdateStats().(fastcache.Stats)
   457  			percent = stats.BytesSize * 100 / mainTrieCacheLimit
   458  			context = []interface{}{
   459  				"warmUpCnt", cnt,
   460  				"cacheLimit", units.Base2Bytes(mainTrieCacheLimit).String(),
   461  				"cachedSize", units.Base2Bytes(stats.BytesSize).String(),
   462  				"percent", percent,
   463  			}
   464  		default:
   465  			context = []interface{}{
   466  				"warmUpCnt", cnt,
   467  				"cacheLimit", units.Base2Bytes(mainTrieCacheLimit).String(),
   468  			}
   469  		}
   470  	}
   471  
   472  	var resultErr error
   473  	for childCnt := 0; childCnt < numChildren; {
   474  		select {
   475  		case <-resultCh:
   476  			cnt++
   477  			if time.Since(logged) < log.StatsReportLimit {
   478  				continue
   479  			}
   480  
   481  			logged = time.Now()
   482  
   483  			updateContext()
   484  			if percent > 90 { // more than 90%
   485  				close(bc.quitWarmUp)
   486  				logger.Info("Warm up is completed", context...)
   487  				return
   488  			}
   489  
   490  			logger.Info("Warm up progress", context...)
   491  		case err := <-errCh:
   492  			// if errCh returns nil, it means success.
   493  			if err != nil {
   494  				resultErr = err
   495  				logger.Warn("Warm up got an error", "err", err)
   496  			}
   497  
   498  			childCnt++
   499  			logger.Debug("Warm up a child trie is finished", "childCnt", childCnt, "err", err)
   500  		}
   501  	}
   502  
   503  	updateContext()
   504  	context = append(context, "resultErr", resultErr)
   505  	logger.Info("Warm up is completed", context...)
   506  }
   507  
   508  // StartWarmUp retrieves all state/storage tries of the latest state root and caches the tries.
   509  func (bc *BlockChain) StartWarmUp() error {
   510  	block, db, mainTrieDB, err := bc.prepareWarmUp()
   511  	if err != nil {
   512  		return err
   513  	}
   514  	// retrieve children nodes of state trie root node
   515  	children, err := db.TrieDB().NodeChildren(block.Root())
   516  	if err != nil {
   517  		return err
   518  	}
   519  	// run goroutine for each child node
   520  	resultCh := make(chan struct{}, 10000)
   521  	errCh := make(chan error)
   522  	bc.quitWarmUp = make(chan struct{})
   523  	for _, child := range children {
   524  		go bc.iterateStateTrie(child, db, resultCh, errCh)
   525  	}
   526  	// run a warm-up checker routine
   527  	go bc.warmUpChecker(mainTrieDB, len(children), resultCh, errCh)
   528  	logger.Info("State trie warm-up is started", "blockNum", block.NumberU64(),
   529  		"root", block.Root().String(), "len(children)", len(children))
   530  	return nil
   531  }
   532  
   533  // StopWarmUp stops the warming up process.
   534  func (bc *BlockChain) StopWarmUp() error {
   535  	if bc.quitWarmUp == nil {
   536  		return ErrNotInWarmUp
   537  	}
   538  
   539  	close(bc.quitWarmUp)
   540  
   541  	return nil
   542  }
   543  
   544  // StartCollectingTrieStats collects state or storage trie statistics.
   545  func (bc *BlockChain) StartCollectingTrieStats(contractAddr common.Address) error {
   546  	block := bc.GetBlockByNumber(bc.lastCommittedBlock)
   547  	if block == nil {
   548  		return fmt.Errorf("Block #%d not found", bc.lastCommittedBlock)
   549  	}
   550  
   551  	mainTrieDB := bc.StateCache().TrieDB()
   552  	cache := mainTrieDB.TrieNodeCache()
   553  	if cache == nil {
   554  		return fmt.Errorf("target cache is nil")
   555  	}
   556  	db := state.NewDatabaseWithExistingCache(bc.db, cache)
   557  
   558  	startNode := block.Root()
   559  	// If the contractAddr is given, start collecting stats from the root of storage trie
   560  	if !common.EmptyAddress(contractAddr) {
   561  		var err error
   562  		startNode, err = bc.GetContractStorageRoot(block, db, contractAddr)
   563  		if err != nil {
   564  			logger.Error("Failed to get the contract storage root",
   565  				"contractAddr", contractAddr.String(), "rootHash", block.Root().String(),
   566  				"err", err)
   567  			return err
   568  		}
   569  	}
   570  
   571  	children, err := db.TrieDB().NodeChildren(startNode)
   572  	if err != nil {
   573  		logger.Error("Failed to retrieve the children of start node", "err", err)
   574  		return err
   575  	}
   576  
   577  	logger.Info("Started collecting trie statistics",
   578  		"blockNum", block.NumberU64(), "root", block.Root().String(), "len(children)", len(children))
   579  	go collectTrieStats(db, startNode)
   580  
   581  	return nil
   582  }
   583  
   584  // collectChildrenStats wraps CollectChildrenStats, in order to send finish signal to resultCh.
   585  func collectChildrenStats(db state.Database, child common.Hash, resultCh chan<- statedb.NodeInfo) {
   586  	db.TrieDB().CollectChildrenStats(child, 2, resultCh)
   587  	resultCh <- statedb.NodeInfo{Finished: true}
   588  }
   589  
   590  // collectTrieStats is the main function of collecting trie statistics.
   591  // It spawns goroutines for the upper-most children and iterates each sub-trie.
   592  func collectTrieStats(db state.Database, startNode common.Hash) {
   593  	children, err := db.TrieDB().NodeChildren(startNode)
   594  	if err != nil {
   595  		logger.Error("Failed to retrieve the children of start node", "err", err)
   596  	}
   597  
   598  	// collecting statistics by running individual goroutines for each child
   599  	resultCh := make(chan statedb.NodeInfo, 10000)
   600  	for _, child := range children {
   601  		go collectChildrenStats(db, child, resultCh)
   602  	}
   603  
   604  	numGoRoutines := len(children)
   605  	ticker := time.NewTicker(1 * time.Minute)
   606  
   607  	numNodes, numLeafNodes, maxDepth := 0, 0, 0
   608  	depthCounter := make(map[int]int)
   609  	begin := time.Now()
   610  	for {
   611  		select {
   612  		case result := <-resultCh:
   613  			if result.Finished {
   614  				numGoRoutines--
   615  				if numGoRoutines == 0 {
   616  					logger.Info("Finished collecting trie statistics", "elapsed", time.Since(begin),
   617  						"numNodes", numNodes, "numLeafNodes", numLeafNodes, "maxDepth", maxDepth)
   618  					printDepthStats(depthCounter)
   619  					return
   620  				}
   621  				continue
   622  			}
   623  			numNodes++
   624  			// if a leaf node, collect the depth data
   625  			if result.Depth != 0 {
   626  				numLeafNodes++
   627  				depthCounter[result.Depth]++
   628  				if result.Depth > maxDepth {
   629  					maxDepth = result.Depth
   630  				}
   631  			}
   632  		case <-ticker.C:
   633  			// leave a periodic log
   634  			logger.Info("Collecting trie statistics is in progress...", "elapsed", time.Since(begin),
   635  				"numGoRoutines", numGoRoutines, "numNodes", numNodes, "numLeafNodes", numLeafNodes, "maxDepth", maxDepth)
   636  			printDepthStats(depthCounter)
   637  		}
   638  	}
   639  }
   640  
   641  // printDepthStats leaves logs containing the depth and the number of nodes in the depth.
   642  func printDepthStats(depthCounter map[int]int) {
   643  	// max depth 20 is set by heuristically
   644  	for depth := 2; depth < 20; depth++ {
   645  		if depthCounter[depth] == 0 {
   646  			continue
   647  		}
   648  		logger.Info("number of leaf nodes in a depth",
   649  			"depth", depth, "numNodes", depthCounter[depth])
   650  	}
   651  }
   652  
   653  // GetContractStorageRoot returns the storage root of a contract based on the given block.
   654  func (bc *BlockChain) GetContractStorageRoot(block *types.Block, db state.Database, contractAddr common.Address) (common.Hash, error) {
   655  	stateDB, err := state.New(block.Root(), db, nil)
   656  	if err != nil {
   657  		return common.Hash{}, fmt.Errorf("failed to get StateDB - %w", err)
   658  	}
   659  	return stateDB.GetContractStorageRoot(contractAddr)
   660  }
   661  
   662  // prepareWarmUp creates and returns resources needed for state warm-up.
   663  func (bc *BlockChain) prepareWarmUp() (*types.Block, state.Database, *statedb.Database, error) {
   664  	// There is a chance of concurrent access to quitWarmUp, though not likely to happen.
   665  	if bc.quitWarmUp != nil {
   666  		return nil, nil, nil, fmt.Errorf("already warming up")
   667  	}
   668  
   669  	block := bc.GetBlockByNumber(bc.lastCommittedBlock)
   670  	if block == nil {
   671  		return nil, nil, nil, fmt.Errorf("block #%d not found", bc.lastCommittedBlock)
   672  	}
   673  
   674  	mainTrieDB := bc.StateCache().TrieDB()
   675  	cache := mainTrieDB.TrieNodeCache()
   676  	if cache == nil {
   677  		return nil, nil, nil, fmt.Errorf("target cache is nil")
   678  	}
   679  	db := state.NewDatabaseWithExistingCache(bc.db, cache)
   680  	return block, db, mainTrieDB, nil
   681  }
   682  
   683  // iterateStorageTrie runs statedb.Iterator, generated from the given storage trie node hash,
   684  // until it reaches end. If it reaches end, it will send a nil error to errCh to indicate that
   685  // it has been finished.
   686  func (bc *BlockChain) iterateStorageTrie(child common.Hash, storageTrie state.Trie, resultCh chan struct{}, errCh chan error) (resultErr error) {
   687  	defer func() { errCh <- resultErr }()
   688  
   689  	itr := statedb.NewIterator(storageTrie.NodeIterator(child[:]))
   690  	for itr.Next() {
   691  		resultCh <- struct{}{}
   692  		select {
   693  		case <-bc.quitWarmUp:
   694  			return stopWarmUpErr
   695  		case <-bc.quit:
   696  			return blockChainStopWarmUpErr
   697  		default:
   698  		}
   699  	}
   700  	return nil
   701  }
   702  
   703  func prepareContractWarmUp(block *types.Block, db state.Database, contractAddr common.Address) (common.Hash, state.Trie, error) {
   704  	stateDB, err := state.New(block.Root(), db, nil)
   705  	if err != nil {
   706  		return common.Hash{}, nil, fmt.Errorf("failed to get StateDB, err: %w", err)
   707  	}
   708  	storageTrieRoot, err := stateDB.GetContractStorageRoot(contractAddr)
   709  	if err != nil {
   710  		return common.Hash{}, nil, err
   711  	}
   712  	storageTrie, err := db.OpenStorageTrie(storageTrieRoot)
   713  	if err != nil {
   714  		return common.Hash{}, nil, err
   715  	}
   716  	return storageTrieRoot, storageTrie, nil
   717  }
   718  
   719  // StartContractWarmUp retrieves a storage trie of the latest state root and caches the trie
   720  // corresponding to the given contract address.
   721  func (bc *BlockChain) StartContractWarmUp(contractAddr common.Address) error {
   722  	block, db, mainTrieDB, err := bc.prepareWarmUp()
   723  	if err != nil {
   724  		return err
   725  	}
   726  	// prepare contract storage trie specific resources - storageTrieRoot and storageTrie
   727  	storageTrieRoot, storageTrie, err := prepareContractWarmUp(block, db, contractAddr)
   728  	if err != nil {
   729  		return fmt.Errorf("failed to prepare contract warm-up, err: %w", err)
   730  	}
   731  	// retrieve children nodes of contract storage trie root node
   732  	children, err := db.TrieDB().NodeChildren(storageTrieRoot)
   733  	if err != nil {
   734  		return err
   735  	}
   736  	// run goroutine for each child node
   737  	resultCh := make(chan struct{}, 10000)
   738  	errCh := make(chan error)
   739  	bc.quitWarmUp = make(chan struct{})
   740  	for _, child := range children {
   741  		go bc.iterateStorageTrie(child, storageTrie, resultCh, errCh)
   742  	}
   743  	// run a warm-up checker routine
   744  	go bc.warmUpChecker(mainTrieDB, len(children), resultCh, errCh)
   745  	logger.Info("Contract storage trie warm-up is started",
   746  		"blockNum", block.NumberU64(), "root", block.Root().String(), "contractAddr", contractAddr.String(),
   747  		"contractStorageRoot", storageTrieRoot.String(), "len(children)", len(children))
   748  	return nil
   749  }