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 }