github.com/klaytn/klaytn@v1.12.1/node/cn/state_accessor.go (about) 1 // Modifications Copyright 2022 The klaytn Authors 2 // Copyright 2021 The go-ethereum Authors 3 // This file is part of the go-ethereum library. 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/state_accessor.go (2022/08/08). 19 // Modified and improved for the klaytn development. 20 21 package cn 22 23 import ( 24 "errors" 25 "fmt" 26 "time" 27 28 "github.com/klaytn/klaytn/blockchain" 29 "github.com/klaytn/klaytn/blockchain/state" 30 "github.com/klaytn/klaytn/blockchain/types" 31 "github.com/klaytn/klaytn/blockchain/vm" 32 "github.com/klaytn/klaytn/common" 33 statedb2 "github.com/klaytn/klaytn/storage/statedb" 34 ) 35 36 // stateAtBlock retrieves the state database associated with a certain block. 37 // If no state is locally available for the given block, a number of blocks 38 // are attempted to be reexecuted to generate the desired state. The optional 39 // base layer statedb can be passed then it's regarded as the statedb of the 40 // parent block. 41 // Parameters: 42 // - block: The block for which we want the state (== state at the stateRoot of the parent) 43 // - reexec: The maximum number of blocks to reprocess trying to obtain the desired state 44 // - base: If the caller is tracing multiple blocks, the caller can provide the parent state 45 // continuously from the callsite. 46 // - checklive: if true, then the live 'blockchain' state database is used. If the caller want to 47 // perform Commit or other 'save-to-disk' changes, this should be set to false to avoid 48 // storing trash persistently 49 // - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided, 50 // it would be preferrable to start from a fresh state, if we have it on disk. 51 func (cn *CN) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { 52 var ( 53 current *types.Block 54 database state.Database 55 report = true 56 origin = block.NumberU64() 57 ) 58 // Check the live database first if we have the state fully available, use that. 59 if checkLive { 60 statedb, err = cn.blockchain.StateAt(block.Root()) 61 if err == nil { 62 return statedb, nil 63 } 64 } 65 if base != nil { 66 if preferDisk { 67 // Create an ephemeral trie.Database for isolating the live one. Otherwise 68 // the internal junks created by tracing will be persisted into the disk. 69 database = state.NewDatabaseWithExistingCache(cn.ChainDB(), cn.blockchain.StateCache().TrieDB().TrieNodeCache()) 70 if statedb, err = state.New(block.Root(), database, nil, nil); err == nil { 71 logger.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number()) 72 return statedb, nil 73 } 74 } 75 // The optional base statedb is given, mark the start point as parent block 76 statedb, database, report = base, base.Database(), false 77 current = cn.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) 78 } else { 79 // Otherwise try to reexec blocks until we find a state or reach our limit 80 current = block 81 82 // Create an ephemeral trie.Database for isolating the live one. Otherwise 83 // the internal junks created by tracing will be persisted into the disk. 84 database = state.NewDatabaseWithExistingCache(cn.ChainDB(), cn.blockchain.StateCache().TrieDB().TrieNodeCache()) 85 86 for i := uint64(0); i < reexec; i++ { 87 if current.NumberU64() == 0 { 88 return nil, errors.New("genesis state is missing") 89 } 90 parent := cn.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1) 91 if parent == nil { 92 return nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1) 93 } 94 current = parent 95 96 statedb, err = state.New(current.Root(), database, nil, nil) 97 if err == nil { 98 break 99 } 100 } 101 if err != nil { 102 switch err.(type) { 103 case *statedb2.MissingNodeError: 104 return nil, fmt.Errorf("historical state unavailable. tried regeneration but not possible, possibly due to state migration/pruning or global state saving interval is bigger than reexec value (reexec=%d)", reexec) 105 default: 106 return nil, err 107 } 108 } 109 } 110 // State was available at historical point, regenerate 111 var ( 112 start = time.Now() 113 logged time.Time 114 parent common.Hash 115 ) 116 for current.NumberU64() < origin { 117 // Print progress logs if long enough time elapsed 118 if report && time.Since(logged) > 8*time.Second { 119 logger.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) 120 logged = time.Now() 121 } 122 // Quit the state regeneration if time limit exceeds 123 if cn.config.DisableUnsafeDebug && time.Since(start) > cn.config.StateRegenerationTimeLimit { 124 return nil, fmt.Errorf("this request has queried old states too long since it exceeds the state regeneration time limit(%s)", cn.config.StateRegenerationTimeLimit.String()) 125 } 126 // Retrieve the next block to regenerate and process it 127 next := current.NumberU64() + 1 128 if current = cn.blockchain.GetBlockByNumber(next); current == nil { 129 return nil, fmt.Errorf("block #%d not found", next) 130 } 131 _, _, _, _, _, err := cn.blockchain.Processor().Process(current, statedb, vm.Config{}) 132 if err != nil { 133 return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) 134 } 135 // Finalize the state so any modifications are written to the trie 136 root, err := statedb.Commit(true) 137 if err != nil { 138 return nil, err 139 } 140 if err := statedb.Reset(root); err != nil { 141 return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err) 142 } 143 database.TrieDB().ReferenceRoot(root) 144 if !common.EmptyHash(parent) { 145 database.TrieDB().Dereference(parent) 146 } 147 parent = root 148 } 149 if report { 150 nodes, _, imgs := database.TrieDB().Size() 151 logger.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) 152 } 153 154 return statedb, nil 155 } 156 157 // stateAtTransaction returns the execution environment of a certain transaction. 158 func (cn *CN) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (blockchain.Message, vm.BlockContext, vm.TxContext, *state.StateDB, error) { 159 // Short circuit if it's genesis block. 160 if block.NumberU64() == 0 { 161 return nil, vm.BlockContext{}, vm.TxContext{}, nil, errors.New("no transaction in genesis") 162 } 163 // Create the parent state database 164 parent := cn.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) 165 if parent == nil { 166 return nil, vm.BlockContext{}, vm.TxContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) 167 } 168 // Lookup the statedb of parent block from the live database, 169 // otherwise regenerate it on the flight. 170 statedb, err := cn.stateAtBlock(parent, reexec, nil, true, false) 171 if err != nil { 172 return nil, vm.BlockContext{}, vm.TxContext{}, nil, err 173 } 174 if txIndex == 0 && len(block.Transactions()) == 0 { 175 return nil, vm.BlockContext{}, vm.TxContext{}, statedb, nil 176 } 177 // Recompute transactions up to the target index. 178 signer := types.MakeSigner(cn.blockchain.Config(), block.Number()) 179 for idx, tx := range block.Transactions() { 180 // Assemble the transaction call message and return if the requested offset 181 msg, err := tx.AsMessageWithAccountKeyPicker(signer, statedb, block.NumberU64()) 182 if err != nil { 183 logger.Warn("stateAtTransition failed", "hash", tx.Hash(), "block", block.NumberU64(), "err", err) 184 return nil, vm.BlockContext{}, vm.TxContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) 185 } 186 187 txContext := blockchain.NewEVMTxContext(msg, block.Header()) 188 blockContext := blockchain.NewEVMBlockContext(block.Header(), cn.blockchain, nil) 189 if idx == txIndex { 190 return msg, blockContext, txContext, statedb, nil 191 } 192 // Not yet the searched for transaction, execute on top of the current state 193 vmenv := vm.NewEVM(blockContext, txContext, statedb, cn.blockchain.Config(), &vm.Config{}) 194 if _, err := blockchain.ApplyMessage(vmenv, msg); err != nil { 195 return nil, vm.BlockContext{}, vm.TxContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) 196 } 197 // Ensure any modifications are committed to the state 198 // Since klaytn is forked after EIP158/161 (a.k.a Spurious Dragon), deleting empty object is always effective 199 statedb.Finalise(true, true) 200 } 201 return nil, vm.BlockContext{}, vm.TxContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) 202 }