github.com/klaytn/klaytn@v1.10.2/node/cn/api.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from eth/api.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package cn
    22  
    23  import (
    24  	"bytes"
    25  	"compress/gzip"
    26  	"context"
    27  	"errors"
    28  	"fmt"
    29  	"io"
    30  	"os"
    31  	"strings"
    32  	"time"
    33  
    34  	"github.com/klaytn/klaytn/blockchain"
    35  	"github.com/klaytn/klaytn/blockchain/state"
    36  	"github.com/klaytn/klaytn/blockchain/types"
    37  	"github.com/klaytn/klaytn/common"
    38  	"github.com/klaytn/klaytn/common/hexutil"
    39  	"github.com/klaytn/klaytn/networks/rpc"
    40  	"github.com/klaytn/klaytn/params"
    41  	"github.com/klaytn/klaytn/rlp"
    42  	"github.com/klaytn/klaytn/storage/statedb"
    43  	"github.com/klaytn/klaytn/work"
    44  )
    45  
    46  // PublicKlayAPI provides an API to access Klaytn CN-related
    47  // information.
    48  type PublicKlayAPI struct {
    49  	cn *CN
    50  }
    51  
    52  // NewPublicKlayAPI creates a new Klaytn protocol API for full nodes.
    53  func NewPublicKlayAPI(e *CN) *PublicKlayAPI {
    54  	return &PublicKlayAPI{e}
    55  }
    56  
    57  // Rewardbase is the address that consensus rewards will be send to
    58  func (api *PublicKlayAPI) Rewardbase() (common.Address, error) {
    59  	return api.cn.Rewardbase()
    60  }
    61  
    62  // PrivateAdminAPI is the collection of CN full node-related APIs
    63  // exposed over the private admin endpoint.
    64  type PrivateAdminAPI struct {
    65  	cn *CN
    66  }
    67  
    68  // NewPrivateAdminAPI creates a new API definition for the full node private
    69  // admin methods of the CN service.
    70  func NewPrivateAdminAPI(cn *CN) *PrivateAdminAPI {
    71  	return &PrivateAdminAPI{cn: cn}
    72  }
    73  
    74  // ExportChain exports the current blockchain into a local file.
    75  func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) {
    76  	if _, err := os.Stat(file); err == nil {
    77  		// File already exists. Allowing overwrite could be a DoS vecotor,
    78  		// since the 'file' may point to arbitrary paths on the drive
    79  		return false, errors.New("location would overwrite an existing file")
    80  	}
    81  
    82  	// Make sure we can create the file to export into
    83  	out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
    84  	if err != nil {
    85  		return false, err
    86  	}
    87  	defer out.Close()
    88  
    89  	var writer io.Writer = out
    90  	if strings.HasSuffix(file, ".gz") {
    91  		writer = gzip.NewWriter(writer)
    92  		defer writer.(*gzip.Writer).Close()
    93  	}
    94  
    95  	// Export the blockchain
    96  	if err := api.cn.BlockChain().Export(writer); err != nil {
    97  		return false, err
    98  	}
    99  	return true, nil
   100  }
   101  
   102  func hasAllBlocks(chain work.BlockChain, bs []*types.Block) bool {
   103  	for _, b := range bs {
   104  		if !chain.HasBlock(b.Hash(), b.NumberU64()) {
   105  			return false
   106  		}
   107  	}
   108  
   109  	return true
   110  }
   111  
   112  // ImportChain imports a blockchain from a local file.
   113  func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) {
   114  	// Make sure the can access the file to import
   115  	in, err := os.Open(file)
   116  	if err != nil {
   117  		return false, err
   118  	}
   119  	defer in.Close()
   120  
   121  	var reader io.Reader = in
   122  	if strings.HasSuffix(file, ".gz") {
   123  		if reader, err = gzip.NewReader(reader); err != nil {
   124  			return false, err
   125  		}
   126  	}
   127  	stream := rlp.NewStream(reader, 0)
   128  
   129  	return api.importChain(stream)
   130  }
   131  
   132  func (api *PrivateAdminAPI) ImportChainFromString(blockRlp string) (bool, error) {
   133  	// Run actual the import in pre-configured batches
   134  	stream := rlp.NewStream(bytes.NewReader(common.FromHex(blockRlp)), 0)
   135  
   136  	return api.importChain(stream)
   137  }
   138  
   139  func (api *PrivateAdminAPI) importChain(stream *rlp.Stream) (bool, error) {
   140  	blocks, index := make([]*types.Block, 0, 2500), 0
   141  	for batch := 0; ; batch++ {
   142  		// Load a batch of blocks from the input file
   143  		for len(blocks) < cap(blocks) {
   144  			block := new(types.Block)
   145  			if err := stream.Decode(block); err == io.EOF {
   146  				break
   147  			} else if err != nil {
   148  				return false, fmt.Errorf("block %d: failed to parse: %v", index, err)
   149  			}
   150  			blocks = append(blocks, block)
   151  			index++
   152  		}
   153  		if len(blocks) == 0 {
   154  			break
   155  		}
   156  
   157  		if hasAllBlocks(api.cn.BlockChain(), blocks) {
   158  			blocks = blocks[:0]
   159  			continue
   160  		}
   161  		// Import the batch and reset the buffer
   162  		if _, err := api.cn.BlockChain().InsertChain(blocks); err != nil {
   163  			return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err)
   164  		}
   165  		blocks = blocks[:0]
   166  	}
   167  	return true, nil
   168  }
   169  
   170  // StartStateMigration starts state migration.
   171  func (api *PrivateAdminAPI) StartStateMigration() error {
   172  	return api.cn.blockchain.PrepareStateMigration()
   173  }
   174  
   175  // StopStateMigration stops state migration and removes stateMigrationDB.
   176  func (api *PrivateAdminAPI) StopStateMigration() error {
   177  	return api.cn.BlockChain().StopStateMigration()
   178  }
   179  
   180  // StateMigrationStatus returns the status information of state trie migration.
   181  func (api *PrivateAdminAPI) StateMigrationStatus() map[string]interface{} {
   182  	isMigration, blkNum, read, committed, pending, progress, err := api.cn.BlockChain().StateMigrationStatus()
   183  
   184  	errStr := "null"
   185  	if err != nil {
   186  		errStr = err.Error()
   187  	}
   188  
   189  	return map[string]interface{}{
   190  		"isMigration":          isMigration,
   191  		"migrationBlockNumber": blkNum,
   192  		"read":                 read,
   193  		"committed":            committed,
   194  		"pending":              pending,
   195  		"progress":             progress,
   196  		"err":                  errStr,
   197  	}
   198  }
   199  
   200  func (api *PrivateAdminAPI) SaveTrieNodeCacheToDisk() error {
   201  	return api.cn.BlockChain().SaveTrieNodeCacheToDisk()
   202  }
   203  
   204  func (api *PrivateAdminAPI) SpamThrottlerConfig(ctx context.Context) (*blockchain.ThrottlerConfig, error) {
   205  	throttler := blockchain.GetSpamThrottler()
   206  	if throttler == nil {
   207  		return nil, errors.New("spam throttler is not running")
   208  	}
   209  	return throttler.GetConfig(), nil
   210  }
   211  
   212  func (api *PrivateAdminAPI) StopSpamThrottler(ctx context.Context) error {
   213  	throttler := blockchain.GetSpamThrottler()
   214  	if throttler == nil {
   215  		return errors.New("spam throttler was already stopped")
   216  	}
   217  	api.cn.txPool.StopSpamThrottler()
   218  	return nil
   219  }
   220  
   221  func (api *PrivateAdminAPI) StartSpamThrottler(ctx context.Context, config *blockchain.ThrottlerConfig) error {
   222  	throttler := blockchain.GetSpamThrottler()
   223  	if throttler != nil {
   224  		return errors.New("spam throttler is already running")
   225  	}
   226  	return api.cn.txPool.StartSpamThrottler(config)
   227  }
   228  
   229  func (api *PrivateAdminAPI) SetSpamThrottlerWhiteList(ctx context.Context, addrs []common.Address) error {
   230  	throttler := blockchain.GetSpamThrottler()
   231  	if throttler == nil {
   232  		return errors.New("spam throttler is not running")
   233  	}
   234  	throttler.SetAllowed(addrs)
   235  	return nil
   236  }
   237  
   238  func (api *PrivateAdminAPI) GetSpamThrottlerWhiteList(ctx context.Context) ([]common.Address, error) {
   239  	throttler := blockchain.GetSpamThrottler()
   240  	if throttler == nil {
   241  		return nil, errors.New("spam throttler is not running")
   242  	}
   243  	return throttler.GetAllowed(), nil
   244  }
   245  
   246  func (api *PrivateAdminAPI) GetSpamThrottlerThrottleList(ctx context.Context) ([]common.Address, error) {
   247  	throttler := blockchain.GetSpamThrottler()
   248  	if throttler == nil {
   249  		return nil, errors.New("spam throttler is not running")
   250  	}
   251  	return throttler.GetThrottled(), nil
   252  }
   253  
   254  func (api *PrivateAdminAPI) GetSpamThrottlerCandidateList(ctx context.Context) (map[common.Address]int, error) {
   255  	throttler := blockchain.GetSpamThrottler()
   256  	if throttler == nil {
   257  		return nil, errors.New("spam throttler is not running")
   258  	}
   259  	return throttler.GetCandidates(), nil
   260  }
   261  
   262  // PublicDebugAPI is the collection of Klaytn full node APIs exposed
   263  // over the public debugging endpoint.
   264  type PublicDebugAPI struct {
   265  	cn *CN
   266  }
   267  
   268  // NewPublicDebugAPI creates a new API definition for the full node-
   269  // related public debug methods of the Klaytn service.
   270  func NewPublicDebugAPI(cn *CN) *PublicDebugAPI {
   271  	return &PublicDebugAPI{cn: cn}
   272  }
   273  
   274  // DumpBlock retrieves the entire state of the database at a given block.
   275  func (api *PublicDebugAPI) DumpBlock(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (state.Dump, error) {
   276  	if *blockNrOrHash.BlockNumber == rpc.PendingBlockNumber {
   277  		// If we're dumping the pending state, we need to request
   278  		// both the pending block as well as the pending state from
   279  		// the miner and operate on those
   280  		_, stateDb := api.cn.miner.Pending()
   281  		return stateDb.RawDump(), nil
   282  	}
   283  
   284  	var block *types.Block
   285  	var err error
   286  	if *blockNrOrHash.BlockNumber == rpc.LatestBlockNumber {
   287  		block = api.cn.APIBackend.CurrentBlock()
   288  	} else {
   289  		block, err = api.cn.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash)
   290  		if err != nil {
   291  			blockNrOrHashString, _ := blockNrOrHash.NumberOrHashString()
   292  			return state.Dump{}, fmt.Errorf("block %v not found", blockNrOrHashString)
   293  		}
   294  	}
   295  	stateDb, err := api.cn.BlockChain().StateAtWithPersistent(block.Root())
   296  	if err != nil {
   297  		return state.Dump{}, err
   298  	}
   299  	return stateDb.RawDump(), nil
   300  }
   301  
   302  type Trie struct {
   303  	Type   string `json:"type"`
   304  	Hash   string `json:"hash"`
   305  	Parent string `json:"parent"`
   306  	Path   string `json:"path"`
   307  }
   308  
   309  type DumpStateTrieResult struct {
   310  	Root  string `json:"root"`
   311  	Tries []Trie `json:"tries"`
   312  }
   313  
   314  // DumpStateTrie retrieves all state/storage tries of the given state root.
   315  func (api *PublicDebugAPI) DumpStateTrie(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (DumpStateTrieResult, error) {
   316  	block, err := api.cn.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash)
   317  	if err != nil {
   318  		blockNrOrHashString, _ := blockNrOrHash.NumberOrHashString()
   319  		return DumpStateTrieResult{}, fmt.Errorf("block #%v not found", blockNrOrHashString)
   320  	}
   321  
   322  	result := DumpStateTrieResult{
   323  		Root:  block.Root().String(),
   324  		Tries: make([]Trie, 0),
   325  	}
   326  
   327  	db := state.NewDatabaseWithExistingCache(api.cn.chainDB, api.cn.blockchain.StateCache().TrieDB().TrieNodeCache())
   328  	stateDB, err := state.New(block.Root(), db, nil)
   329  	if err != nil {
   330  		return DumpStateTrieResult{}, err
   331  	}
   332  	it := state.NewNodeIterator(stateDB)
   333  	for it.Next() {
   334  		t := Trie{
   335  			it.Type,
   336  			it.Hash.String(),
   337  			it.Parent.String(),
   338  			statedb.HexPathToString(it.Path),
   339  		}
   340  
   341  		result.Tries = append(result.Tries, t)
   342  	}
   343  	return result, nil
   344  }
   345  
   346  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   347  // StartWarmUp retrieves all state/storage tries of the latest committed state root and caches the tries.
   348  func (api *PrivateDebugAPI) StartWarmUp() error {
   349  	return api.cn.blockchain.StartWarmUp()
   350  }
   351  
   352  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   353  // StartContractWarmUp retrieves a storage trie of the latest state root and caches the trie
   354  // corresponding to the given contract address.
   355  func (api *PrivateDebugAPI) StartContractWarmUp(contractAddr common.Address) error {
   356  	return api.cn.blockchain.StartContractWarmUp(contractAddr)
   357  }
   358  
   359  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   360  // StopWarmUp stops the warming up process.
   361  func (api *PrivateDebugAPI) StopWarmUp() error {
   362  	return api.cn.blockchain.StopWarmUp()
   363  }
   364  
   365  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   366  // StartCollectingTrieStats  collects state/storage trie statistics and print in the log.
   367  func (api *PrivateDebugAPI) StartCollectingTrieStats(contractAddr common.Address) error {
   368  	return api.cn.blockchain.StartCollectingTrieStats(contractAddr)
   369  }
   370  
   371  // PrivateDebugAPI is the collection of CN full node APIs exposed over
   372  // the private debugging endpoint.
   373  type PrivateDebugAPI struct {
   374  	config *params.ChainConfig
   375  	cn     *CN
   376  }
   377  
   378  // NewPrivateDebugAPI creates a new API definition for the full node-related
   379  // private debug methods of the CN service.
   380  func NewPrivateDebugAPI(config *params.ChainConfig, cn *CN) *PrivateDebugAPI {
   381  	return &PrivateDebugAPI{config: config, cn: cn}
   382  }
   383  
   384  // Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
   385  func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
   386  	if preimage := api.cn.ChainDB().ReadPreimage(hash); preimage != nil {
   387  		return preimage, nil
   388  	}
   389  	return nil, errors.New("unknown preimage")
   390  }
   391  
   392  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   393  // GetBadBLocks returns a list of the last 'bad blocks' that the client has seen on the network
   394  // and returns them as a JSON list of block-hashes
   395  func (api *PublicDebugAPI) GetBadBlocks(ctx context.Context) ([]blockchain.BadBlockArgs, error) {
   396  	return api.cn.BlockChain().BadBlocks()
   397  }
   398  
   399  // StorageRangeResult is the result of a debug_storageRangeAt API call.
   400  type StorageRangeResult struct {
   401  	Storage storageMap   `json:"storage"`
   402  	NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the statedb.
   403  }
   404  
   405  type storageMap map[common.Hash]storageEntry
   406  
   407  type storageEntry struct {
   408  	Key   *common.Hash `json:"key"`
   409  	Value common.Hash  `json:"value"`
   410  }
   411  
   412  // StorageRangeAt returns the storage at the given block height and transaction index.
   413  func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
   414  	// Retrieve the block
   415  	block := api.cn.blockchain.GetBlockByHash(blockHash)
   416  	if block == nil {
   417  		return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash)
   418  	}
   419  	_, _, statedb, err := api.cn.stateAtTransaction(block, txIndex, 0)
   420  	if err != nil {
   421  		return StorageRangeResult{}, err
   422  	}
   423  	st := statedb.StorageTrie(contractAddress)
   424  	if st == nil {
   425  		return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
   426  	}
   427  	return storageRangeAt(st, keyStart, maxResult)
   428  }
   429  
   430  func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeResult, error) {
   431  	it := statedb.NewIterator(st.NodeIterator(start))
   432  	result := StorageRangeResult{Storage: storageMap{}}
   433  	for i := 0; i < maxResult && it.Next(); i++ {
   434  		_, content, _, err := rlp.Split(it.Value)
   435  		if err != nil {
   436  			return StorageRangeResult{}, err
   437  		}
   438  		e := storageEntry{Value: common.BytesToHash(content)}
   439  		if preimage := st.GetKey(it.Key); preimage != nil {
   440  			preimage := common.BytesToHash(preimage)
   441  			e.Key = &preimage
   442  		}
   443  		result.Storage[common.BytesToHash(it.Key)] = e
   444  	}
   445  	// Add the 'next key' so clients can continue downloading.
   446  	if it.Next() {
   447  		next := common.BytesToHash(it.Key)
   448  		result.NextKey = &next
   449  	}
   450  	return result, nil
   451  }
   452  
   453  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   454  // GetModifiedAccountsByNumber returns all accounts that have changed between the
   455  // two blocks specified. A change is defined as a difference in nonce, balance,
   456  // code hash, or storage hash.
   457  //
   458  // With one parameter, returns the list of accounts modified in the specified block.
   459  func (api *PublicDebugAPI) GetModifiedAccountsByNumber(ctx context.Context, startNum rpc.BlockNumber, endNum *rpc.BlockNumber) ([]common.Address, error) {
   460  	startBlock, endBlock, err := api.getStartAndEndBlock(ctx, startNum, endNum)
   461  	if err != nil {
   462  		return nil, err
   463  	}
   464  	return api.getModifiedAccounts(startBlock, endBlock)
   465  }
   466  
   467  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   468  // GetModifiedAccountsByHash returns all accounts that have changed between the
   469  // two blocks specified. A change is defined as a difference in nonce, balance,
   470  // code hash, or storage hash.
   471  //
   472  // With one parameter, returns the list of accounts modified in the specified block.
   473  func (api *PublicDebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) {
   474  	var startBlock, endBlock *types.Block
   475  	startBlock = api.cn.blockchain.GetBlockByHash(startHash)
   476  	if startBlock == nil {
   477  		return nil, fmt.Errorf("start block %x not found", startHash)
   478  	}
   479  
   480  	if endHash == nil {
   481  		endBlock = startBlock
   482  		startBlock = api.cn.blockchain.GetBlockByHash(startBlock.ParentHash())
   483  		if startBlock == nil {
   484  			return nil, fmt.Errorf("block %x has no parent", startHash)
   485  		}
   486  	} else {
   487  		endBlock = api.cn.blockchain.GetBlockByHash(*endHash)
   488  		if endBlock == nil {
   489  			return nil, fmt.Errorf("end block %x not found", *endHash)
   490  		}
   491  	}
   492  	return api.getModifiedAccounts(startBlock, endBlock)
   493  }
   494  
   495  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   496  func (api *PublicDebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) {
   497  	trieDB := api.cn.blockchain.StateCache().TrieDB()
   498  
   499  	oldTrie, err := statedb.NewSecureTrie(startBlock.Root(), trieDB)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	newTrie, err := statedb.NewSecureTrie(endBlock.Root(), trieDB)
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  
   508  	diff, _ := statedb.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
   509  	iter := statedb.NewIterator(diff)
   510  
   511  	var dirty []common.Address
   512  	for iter.Next() {
   513  		key := newTrie.GetKey(iter.Key)
   514  		if key == nil {
   515  			return nil, fmt.Errorf("no preimage found for hash %x", iter.Key)
   516  		}
   517  		dirty = append(dirty, common.BytesToAddress(key))
   518  	}
   519  	return dirty, nil
   520  }
   521  
   522  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   523  // getStartAndEndBlock returns start and end block based on the given startNum and endNum.
   524  func (api *PublicDebugAPI) getStartAndEndBlock(ctx context.Context, startNum rpc.BlockNumber, endNum *rpc.BlockNumber) (*types.Block, *types.Block, error) {
   525  	var startBlock, endBlock *types.Block
   526  
   527  	startBlock, err := api.cn.APIBackend.BlockByNumber(ctx, startNum)
   528  	if err != nil {
   529  		return nil, nil, fmt.Errorf("start block number #%d not found", startNum.Uint64())
   530  	}
   531  
   532  	if endNum == nil {
   533  		endBlock = startBlock
   534  		startBlock, err = api.cn.APIBackend.BlockByHash(ctx, startBlock.ParentHash())
   535  		if err != nil {
   536  			return nil, nil, fmt.Errorf("block number #%d has no parent", startNum.Uint64())
   537  		}
   538  	} else {
   539  		endBlock, err = api.cn.APIBackend.BlockByNumber(ctx, *endNum)
   540  		if err != nil {
   541  			return nil, nil, fmt.Errorf("end block number #%d not found", (*endNum).Uint64())
   542  		}
   543  	}
   544  
   545  	if startBlock.Number().Uint64() >= endBlock.Number().Uint64() {
   546  		return nil, nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64())
   547  	}
   548  
   549  	return startBlock, endBlock, nil
   550  }
   551  
   552  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   553  // GetModifiedStorageNodesByNumber returns the number of storage nodes of a contract account
   554  // that have been changed between the two blocks specified.
   555  //
   556  // With the first two parameters, it returns the number of storage trie nodes modified in the specified block.
   557  func (api *PublicDebugAPI) GetModifiedStorageNodesByNumber(ctx context.Context, contractAddr common.Address, startNum rpc.BlockNumber, endNum *rpc.BlockNumber, printDetail *bool) (int, error) {
   558  	startBlock, endBlock, err := api.getStartAndEndBlock(ctx, startNum, endNum)
   559  	if err != nil {
   560  		return 0, err
   561  	}
   562  	return api.getModifiedStorageNodes(contractAddr, startBlock, endBlock, printDetail)
   563  }
   564  
   565  // TODO-klaytn: Rearrange PublicDebugAPI and PrivateDebugAPI receivers
   566  func (api *PublicDebugAPI) getModifiedStorageNodes(contractAddr common.Address, startBlock, endBlock *types.Block, printDetail *bool) (int, error) {
   567  	startBlockRoot, err := api.cn.blockchain.GetContractStorageRoot(startBlock, api.cn.blockchain.StateCache(), contractAddr)
   568  	if err != nil {
   569  		return 0, err
   570  	}
   571  	endBlockRoot, err := api.cn.blockchain.GetContractStorageRoot(endBlock, api.cn.blockchain.StateCache(), contractAddr)
   572  	if err != nil {
   573  		return 0, err
   574  	}
   575  
   576  	trieDB := api.cn.blockchain.StateCache().TrieDB()
   577  	oldTrie, err := statedb.NewSecureTrie(startBlockRoot, trieDB)
   578  	if err != nil {
   579  		return 0, err
   580  	}
   581  	newTrie, err := statedb.NewSecureTrie(endBlockRoot, trieDB)
   582  	if err != nil {
   583  		return 0, err
   584  	}
   585  
   586  	diff, _ := statedb.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{}))
   587  	iter := statedb.NewIterator(diff)
   588  
   589  	logger.Info("Start collecting the modified storage nodes", "contractAddr", contractAddr.String(),
   590  		"startBlock", startBlock.NumberU64(), "endBlock", endBlock.NumberU64())
   591  	start := time.Now()
   592  	numModifiedNodes := 0
   593  	for iter.Next() {
   594  		numModifiedNodes++
   595  		if printDetail != nil && *printDetail {
   596  			logger.Info("modified storage trie nodes", "contractAddr", contractAddr.String(),
   597  				"nodeHash", common.BytesToHash(iter.Key).String())
   598  		}
   599  	}
   600  	logger.Info("Finished collecting the modified storage nodes", "contractAddr", contractAddr.String(),
   601  		"startBlock", startBlock.NumberU64(), "endBlock", endBlock.NumberU64(), "numModifiedNodes", numModifiedNodes, "elapsed", time.Since(start))
   602  	return numModifiedNodes, nil
   603  }