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