From 888b5c706e490252a03a7e7e6c49ea25a886b492 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 30 May 2016 14:48:48 +0200 Subject: [PATCH] core, miner: ChainFork as BlockReader. Updated VM Env to use reader fn ChainFork is now usable as a BlockReader interface and can be used to read out chain data. The VM Env is now taking a block-number-to-hash function instead of BlockChain to create a block-number-to-hash internally. This makes it slightly more flexible to use and allows us to pass around the ChainFork NumToHash method. --- accounts/abi/bind/backends/simulated.go | 4 ++-- common/registrar/ethreg/api.go | 2 +- core/blockchain.go | 7 +++++- core/blockchain_test.go | 4 ++-- core/fork.go | 31 +++++++++++++++++++++---- core/fork_test.go | 5 ++++ core/state_processor.go | 8 +++---- core/types.go | 2 +- core/vm_env.go | 18 +++++++------- eth/api.go | 10 ++++---- miner/worker.go | 2 +- 11 files changed, 63 insertions(+), 30 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 54b1ce6032..38846ca3d4 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -120,7 +120,7 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe data: data, } // Execute the call and return - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) + vmenv := core.NewEnv(statedb, chainConfig, core.GetHashFn(block.ParentHash(), b.blockchain), msg, block.Header(), vm.Config{}) gaspool := new(core.GasPool).AddGas(common.MaxBig) out, _, err := core.ApplyMessage(vmenv, msg, gaspool) @@ -168,7 +168,7 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com data: data, } // Execute the call and return - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) + vmenv := core.NewEnv(statedb, chainConfig, core.GetHashFn(block.ParentHash(), b.blockchain), msg, block.Header(), vm.Config{}) gaspool := new(core.GasPool).AddGas(common.MaxBig) _, gas, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go index 6d77a9385d..b3a9651936 100644 --- a/common/registrar/ethreg/api.go +++ b/common/registrar/ethreg/api.go @@ -191,7 +191,7 @@ func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr } header := be.bc.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, be.config, be.bc, msg, header, vm.Config{}) + vmenv := core.NewEnv(statedb, be.config, core.GetHashFn(header.ParentHash, be.bc), msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(common.MaxBig) res, gas, err := core.ApplyMessage(vmenv, msg, gp) diff --git a/core/blockchain.go b/core/blockchain.go index bd84adfe9a..ccae5b69b5 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -162,6 +162,11 @@ func NewBlockChain(chainDb ethdb.Database, config *ChainConfig, pow pow.PoW, mux return bc, nil } +// XXX temporary +func (self *BlockChain) Db() ethdb.Database { + return self.chainDb +} + func (self *BlockChain) getProcInterrupt() bool { return atomic.LoadInt32(&self.procInterrupt) == 1 } @@ -896,7 +901,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { return i, err } // Process block using the parent state as reference point. - receipts, logs, usedGas, err := self.processor.Process(block, statedb, self.config.VmConfig) + receipts, logs, usedGas, err := self.processor.Process(GetHashFn(block.ParentHash(), self), block, statedb, self.config.VmConfig) if err != nil { reportBlock(block, err) return i, err diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 876dd2ba15..70e95494f6 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -141,7 +141,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, vm.Config{}) + receipts, _, usedGas, err := blockchain.Processor().Process(GetHashFn(block.ParentHash(), blockchain), block, statedb, vm.Config{}) if err != nil { reportBlock(block, err) return err @@ -435,7 +435,7 @@ func (bproc) ValidateHeader(*types.Header, *types.Header, bool) error { return n func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error { return nil } -func (bproc) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { +func (bproc) Process(getHashFn BlockNoToHashFn, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { return nil, nil, nil, nil } diff --git a/core/fork.go b/core/fork.go index 45158b46df..eceda5403e 100644 --- a/core/fork.go +++ b/core/fork.go @@ -69,16 +69,18 @@ type ChainFork struct { origin *types.Block // origin notes the start of the fork currentBlock *types.Block // the current block within this transaction - changes []Changes // changes + changes []Changes // changes + hashToIdx map[common.Hash]int // mapping from block hash to array changes index } // Fork returns a new blockchain with the given database as backing layer // for the localised blockchain transaction. func Fork(config *ChainConfig, blockReader BlockReader, origin common.Hash) (*ChainFork, error) { fork := &ChainFork{ - db: blockReader.Db(), - reader: blockReader, - config: config, + db: blockReader.Db(), + reader: blockReader, + config: config, + hashToIdx: make(map[common.Hash]int), } // get the origin block from which this fork originates @@ -120,10 +122,30 @@ func (fork *ChainFork) GetNumHash(n uint64) common.Hash { return common.Hash{} } +// State returns the current pending state of the fork. This state is re-used throughout the entire +// session of the fork. func (fork *ChainFork) State() *state.StateDB { return fork.state } +// GetBlock returns the block within the fork that corresponds to the given hash. If the +// block is not found within the fork nil will be returned -- this is subject to change +// and might include blocks that lay outside of the fork, in the future. +func (fork *ChainFork) GetBlock(hash common.Hash) *types.Block { + if len(fork.changes) == 0 { + if fork.origin.Hash() == hash { + return fork.origin + } + return nil + } + + if idx, ok := fork.hashToIdx[hash]; ok { + return fork.changes[idx].block + } + return nil + +} + // CommitBlock commits a new block to the fork. The block that's being commited their parent hash must // match the previously committed block or the origin if the fork is empty. func (fork *ChainFork) CommitBlock(td *big.Int, block *types.Block, receipts types.Receipts) error { @@ -135,6 +157,7 @@ func (fork *ChainFork) CommitBlock(td *big.Int, block *types.Block, receipts typ return errUnboundedParent } + fork.hashToIdx[block.Hash()] = len(fork.changes) fork.changes = append(fork.changes, Changes{ td: td, block: block, diff --git a/core/fork_test.go b/core/fork_test.go index 7fcc65ed60..a2bbe2f597 100644 --- a/core/fork_test.go +++ b/core/fork_test.go @@ -59,5 +59,10 @@ func TestGetNumHash(t *testing.T) { if fork.GetNumHash(fork.originN+i+1) != blocks[i].Block.Hash() { t.Errorf("%d failed: expected %x got %x", i, fork.GetNumHash(fork.originN+i+1), blocks[i].Block.Hash()) } + + idx := fork.hashToIdx[blocks[i].Block.Hash()] + if uint64(idx) != i { + t.Errorf("%d failed: expected hash %x to map to %d got %d", i, blocks[i].Block.Hash(), i, idx) + } } } diff --git a/core/state_processor.go b/core/state_processor.go index 95b3057bbc..2287061482 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -56,7 +56,7 @@ func NewStateProcessor(config *ChainConfig, bc *BlockChain) *StateProcessor { // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { +func (p *StateProcessor) Process(getHashFn BlockNoToHashFn, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { var ( receipts types.Receipts totalUsedGas = big.NewInt(0) @@ -68,7 +68,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg for i, tx := range block.Transactions() { statedb.StartRecord(tx.Hash(), block.Hash(), i) - receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg) + receipt, logs, _, err := ApplyTransaction(p.config, getHashFn, gp, statedb, header, tx, totalUsedGas, cfg) if err != nil { return nil, nil, totalUsedGas, err } @@ -85,8 +85,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // // ApplyTransactions returns the generated receipts and vm logs during the // execution of the state transition phase. -func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) { - _, gas, err := ApplyMessage(NewEnv(statedb, config, bc, tx, header, cfg), tx, gp) +func ApplyTransaction(config *ChainConfig, getHashFn BlockNoToHashFn, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) { + _, gas, err := ApplyMessage(NewEnv(statedb, config, getHashFn, tx, header, cfg), tx, gp) if err != nil { return nil, nil, nil, err } diff --git a/core/types.go b/core/types.go index 20f33a153f..10038135d3 100644 --- a/core/types.go +++ b/core/types.go @@ -61,7 +61,7 @@ type HeaderValidator interface { // of gas used in the process and return an error if any of the internal rules // failed. type Processor interface { - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) + Process(getHashFn BlockNoToHashFn, block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, vm.Logs, *big.Int, error) } // Backend is an interface defining the basic functionality for an operable node diff --git a/core/vm_env.go b/core/vm_env.go index f50140c681..937c4e044a 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -28,9 +28,9 @@ import ( // GetHashFn returns a function for which the VM env can query block hashes through // up to the limit defined by the Yellow Paper and uses the given block chain // to query for information. -func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash { +func GetHashFn(ref common.Hash, bc *BlockChain) func(n uint64) common.Hash { return func(n uint64) common.Hash { - for block := chain.GetBlock(ref); block != nil; block = chain.GetBlock(block.ParentHash()) { + for block := bc.GetBlock(ref); block != nil; block = bc.GetBlock(block.ParentHash()) { if block.NumberU64() == n { return block.Hash() } @@ -47,20 +47,20 @@ type VMEnv struct { depth int // Current execution depth msg Message // Message appliod - header *types.Header // Header information - chain *BlockChain // Blockchain handle - logs []vm.StructLog // Logs for the custom structured logger - getHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes + header *types.Header // Header information + logs []vm.StructLog // Logs for the custom structured logger + getHashFn BlockNoToHashFn // getHashFn callback is used to retrieve block hashes } -func NewEnv(state *state.StateDB, chainConfig *ChainConfig, chain *BlockChain, msg Message, header *types.Header, cfg vm.Config) *VMEnv { +type BlockNoToHashFn func(n uint64) common.Hash + +func NewEnv(state *state.StateDB, chainConfig *ChainConfig, getHashFn BlockNoToHashFn, msg Message, header *types.Header, cfg vm.Config) *VMEnv { env := &VMEnv{ chainConfig: chainConfig, - chain: chain, state: state, header: header, msg: msg, - getHashFn: GetHashFn(header.ParentHash, chain), + getHashFn: getHashFn, //GetHashFn(header.ParentHash, reader), } // if no log collector is present set self as the collector diff --git a/eth/api.go b/eth/api.go index f5f942c27d..b8030ffa79 100644 --- a/eth/api.go +++ b/eth/api.go @@ -777,7 +777,7 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st } // Execute the call and return - vmenv := core.NewEnv(stateDb, s.config, s.bc, msg, block.Header(), s.config.VmConfig) + vmenv := core.NewEnv(stateDb, s.config, core.GetHashFn(block.ParentHash(), s.bc), msg, block.Header(), s.config.VmConfig) gp := new(core.GasPool).AddGas(common.MaxBig) res, requiredGas, _, err := core.NewStateTransition(vmenv, msg, gp).TransitionDb() @@ -1753,7 +1753,7 @@ func (api *PrivateDebugAPI) traceBlock(block *types.Block, config *vm.Config) (b return false, collector.traces, err } - receipts, _, usedGas, err := processor.Process(block, statedb, *config) + receipts, _, usedGas, err := processor.Process(core.GetHashFn(block.ParentHash(), api.eth.BlockChain()), block, statedb, *config) if err != nil { return false, collector.traces, err } @@ -1871,7 +1871,7 @@ func (api *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogC } // Mutate the state if we haven't reached the tracing transaction yet if uint64(idx) < txIndex { - vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{}) + vmenv := core.NewEnv(stateDb, api.config, core.GetHashFn(block.ParentHash(), api.eth.BlockChain()), msg, block.Header(), vm.Config{}) _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("mutation failed: %v", err) @@ -1879,7 +1879,7 @@ func (api *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger *vm.LogC continue } // Otherwise trace the transaction and return - vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Logger: *logger}) + vmenv := core.NewEnv(stateDb, api.config, core.GetHashFn(block.ParentHash(), api.eth.BlockChain()), msg, block.Header(), vm.Config{Debug: true, Logger: *logger}) ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) @@ -1933,7 +1933,7 @@ func (s *PublicBlockChainAPI) TraceCall(args CallArgs, blockNr rpc.BlockNumber) } // Execute the call and return - vmenv := core.NewEnv(stateDb, s.config, s.bc, msg, block.Header(), vm.Config{ + vmenv := core.NewEnv(stateDb, s.config, core.GetHashFn(block.ParentHash(), s.bc), msg, block.Header(), vm.Config{ Debug: true, }) gp := new(core.GasPool).AddGas(common.MaxBig) diff --git a/miner/worker.go b/miner/worker.go index fe759560c2..528d636ce6 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -656,7 +656,7 @@ func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, g } config.ForceJit = false // disable forcing jit - receipt, logs, _, err := core.ApplyTransaction(env.config, bc, gp, env.state, env.header, tx, env.header.GasUsed, config) + receipt, logs, _, err := core.ApplyTransaction(env.config, core.GetHashFn(env.header.ParentHash, bc), gp, env.state, env.header, tx, env.header.GasUsed, config) if err != nil { env.state.Set(snap) return err, nil