diff options
author | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-11-19 17:57:00 +0800 |
---|---|---|
committer | Jeffrey Wilcke <jeffrey@ethereum.org> | 2015-11-19 17:57:00 +0800 |
commit | ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8 (patch) | |
tree | 5ce6b23c32fc1f47cc688ac954b3d26d4eec9cad /core | |
parent | 23f42d9463e55fe86100b86c2ab0b7c95f181f91 (diff) | |
parent | a1d9ef48c505ab4314ca8e3ee1fc272032da3034 (diff) | |
download | dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar.gz dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar.bz2 dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar.lz dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar.xz dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.tar.zst dexon-ae37a8013d5a348bdb21d4a66d5f462e0baf7cd8.zip |
Merge pull request #1917 from obscuren/validator-interface
core, eth, rpc: split out block validator and state processor
Diffstat (limited to 'core')
-rw-r--r-- | core/bench_test.go | 1 | ||||
-rw-r--r-- | core/block_processor.go | 460 | ||||
-rw-r--r-- | core/block_validator.go | 243 | ||||
-rw-r--r-- | core/block_validator_test.go (renamed from core/block_processor_test.go) | 4 | ||||
-rw-r--r-- | core/blockchain.go | 134 | ||||
-rw-r--r-- | core/blockchain_test.go | 139 | ||||
-rw-r--r-- | core/chain_makers.go | 11 | ||||
-rw-r--r-- | core/chain_makers_test.go | 9 | ||||
-rw-r--r-- | core/gaspool.go (renamed from core/types/common.go) | 33 | ||||
-rw-r--r-- | core/manager.go | 34 | ||||
-rw-r--r-- | core/state_processor.go | 107 | ||||
-rw-r--r-- | core/types.go | 70 |
12 files changed, 646 insertions, 599 deletions
diff --git a/core/bench_test.go b/core/bench_test.go index b5eb51803..6fa7659b9 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -169,7 +169,6 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // State and blocks are stored in the same DB. evmux := new(event.TypeMux) chainman, _ := NewBlockChain(db, FakePow{}, evmux) - chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux)) defer chainman.Stop() b.ReportAllocs() b.ResetTimer() diff --git a/core/block_processor.go b/core/block_processor.go deleted file mode 100644 index e7b2f63e5..000000000 --- a/core/block_processor.go +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package core - -import ( - "fmt" - "math/big" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/pow" - "gopkg.in/fatih/set.v0" -) - -const ( - // must be bumped when consensus algorithm is changed, this forces the upgradedb - // command to be run (forces the blocks to be imported again using the new algorithm) - BlockChainVersion = 3 -) - -type BlockProcessor struct { - chainDb ethdb.Database - // Mutex for locking the block processor. Blocks can only be handled one at a time - mutex sync.Mutex - // Canonical block chain - bc *BlockChain - // non-persistent key/value memory storage - mem map[string]*big.Int - // Proof of work used for validating - Pow pow.PoW - - events event.Subscription - - eventMux *event.TypeMux -} - -// GasPool tracks the amount of gas available during -// execution of the transactions in a block. -// The zero value is a pool with zero gas available. -type GasPool big.Int - -// AddGas makes gas available for execution. -func (gp *GasPool) AddGas(amount *big.Int) *GasPool { - i := (*big.Int)(gp) - i.Add(i, amount) - return gp -} - -// SubGas deducts the given amount from the pool if enough gas is -// available and returns an error otherwise. -func (gp *GasPool) SubGas(amount *big.Int) error { - i := (*big.Int)(gp) - if i.Cmp(amount) < 0 { - return &GasLimitErr{Have: new(big.Int).Set(i), Want: amount} - } - i.Sub(i, amount) - return nil -} - -func (gp *GasPool) String() string { - return (*big.Int)(gp).String() -} - -func NewBlockProcessor(db ethdb.Database, pow pow.PoW, blockchain *BlockChain, eventMux *event.TypeMux) *BlockProcessor { - sm := &BlockProcessor{ - chainDb: db, - mem: make(map[string]*big.Int), - Pow: pow, - bc: blockchain, - eventMux: eventMux, - } - return sm -} - -func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block *types.Block, transientProcess bool) (receipts types.Receipts, err error) { - gp := new(GasPool).AddGas(block.GasLimit()) - if glog.V(logger.Core) { - glog.Infof("%x: gas (+ %v)", block.Coinbase(), gp) - } - - // Process the transactions on to parent state - receipts, err = sm.ApplyTransactions(gp, statedb, block, block.Transactions(), transientProcess) - if err != nil { - return nil, err - } - - return receipts, nil -} - -func (self *BlockProcessor) ApplyTransaction(gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { - _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, header), tx, gp) - if err != nil { - return nil, nil, err - } - - // Update the state with pending changes - usedGas.Add(usedGas, gas) - receipt := types.NewReceipt(statedb.IntermediateRoot().Bytes(), usedGas) - receipt.TxHash = tx.Hash() - receipt.GasUsed = new(big.Int).Set(gas) - if MessageCreatesContract(tx) { - from, _ := tx.From() - receipt.ContractAddress = crypto.CreateAddress(from, tx.Nonce()) - } - - logs := statedb.GetLogs(tx.Hash()) - receipt.Logs = logs - receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - - glog.V(logger.Debug).Infoln(receipt) - - // Notify all subscribers - if !transientProcess { - go self.eventMux.Post(TxPostEvent{tx}) - go self.eventMux.Post(logs) - } - - return receipt, gas, err -} -func (self *BlockProcessor) BlockChain() *BlockChain { - return self.bc -} - -func (self *BlockProcessor) ApplyTransactions(gp *GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) { - var ( - receipts types.Receipts - totalUsedGas = big.NewInt(0) - err error - cumulativeSum = new(big.Int) - header = block.Header() - ) - - for i, tx := range txs { - statedb.StartRecord(tx.Hash(), block.Hash(), i) - - receipt, txGas, err := self.ApplyTransaction(gp, statedb, header, tx, totalUsedGas, transientProcess) - if err != nil { - return nil, err - } - - if err != nil { - glog.V(logger.Core).Infoln("TX err:", err) - } - receipts = append(receipts, receipt) - - cumulativeSum.Add(cumulativeSum, new(big.Int).Mul(txGas, tx.GasPrice())) - } - - if block.GasUsed().Cmp(totalUsedGas) != 0 { - return nil, ValidationError(fmt.Sprintf("gas used error (%v / %v)", block.GasUsed(), totalUsedGas)) - } - - if transientProcess { - go self.eventMux.Post(PendingBlockEvent{block, statedb.Logs()}) - } - - return receipts, err -} - -func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs vm.Logs, err error) { - // Processing a blocks may never happen simultaneously - sm.mutex.Lock() - defer sm.mutex.Unlock() - - if !sm.bc.HasBlock(block.ParentHash()) { - return nil, ParentError(block.ParentHash()) - } - parent := sm.bc.GetBlock(block.ParentHash()) - - // FIXME Change to full header validation. See #1225 - errch := make(chan bool) - go func() { errch <- sm.Pow.Verify(block) }() - - logs, _, err = sm.processWithParent(block, parent) - if !<-errch { - return nil, ValidationError("Block's nonce is invalid (= %x)", block.Nonce) - } - - return logs, err -} - -// Process block will attempt to process the given block's transactions and applies them -// on top of the block's parent state (given it exists) and will return wether it was -// successful or not. -func (sm *BlockProcessor) Process(block *types.Block) (logs vm.Logs, receipts types.Receipts, err error) { - // Processing a blocks may never happen simultaneously - sm.mutex.Lock() - defer sm.mutex.Unlock() - - if sm.bc.HasBlock(block.Hash()) { - if _, err := state.New(block.Root(), sm.chainDb); err == nil { - return nil, nil, &KnownBlockError{block.Number(), block.Hash()} - } - } - if parent := sm.bc.GetBlock(block.ParentHash()); parent != nil { - if _, err := state.New(parent.Root(), sm.chainDb); err == nil { - return sm.processWithParent(block, parent) - } - } - return nil, nil, ParentError(block.ParentHash()) -} - -func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs vm.Logs, receipts types.Receipts, err error) { - // Create a new state based on the parent's root (e.g., create copy) - state, err := state.New(parent.Root(), sm.chainDb) - if err != nil { - return nil, nil, err - } - header := block.Header() - uncles := block.Uncles() - txs := block.Transactions() - - // Block validation - if err = ValidateHeader(sm.Pow, header, parent.Header(), false, false); err != nil { - return - } - - // There can be at most two uncles - if len(uncles) > 2 { - return nil, nil, ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(uncles)) - } - - receipts, err = sm.TransitionState(state, parent, block, false) - if err != nil { - return - } - - // Validate the received block's bloom with the one derived from the generated receipts. - // For valid blocks this should always validate to true. - rbloom := types.CreateBloom(receipts) - if rbloom != header.Bloom { - err = fmt.Errorf("unable to replicate block's bloom=%x", rbloom) - return - } - - // The transactions Trie's root (R = (Tr [[i, RLP(T1)], [i, RLP(T2)], ... [n, RLP(Tn)]])) - // can be used by light clients to make sure they've received the correct Txs - txSha := types.DeriveSha(txs) - if txSha != header.TxHash { - err = fmt.Errorf("invalid transaction root hash. received=%x calculated=%x", header.TxHash, txSha) - return - } - - // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]])) - receiptSha := types.DeriveSha(receipts) - if receiptSha != header.ReceiptHash { - err = fmt.Errorf("invalid receipt root hash. received=%x calculated=%x", header.ReceiptHash, receiptSha) - return - } - - // Verify UncleHash before running other uncle validations - unclesSha := types.CalcUncleHash(uncles) - if unclesSha != header.UncleHash { - err = fmt.Errorf("invalid uncles root hash. received=%x calculated=%x", header.UncleHash, unclesSha) - return - } - - // Verify uncles - if err = sm.VerifyUncles(state, block, parent); err != nil { - return - } - // Accumulate static rewards; block reward, uncle's and uncle inclusion. - AccumulateRewards(state, header, uncles) - - // Commit state objects/accounts to a database batch and calculate - // the state root. The database is not modified if the root - // doesn't match. - root, batch := state.CommitBatch() - if header.Root != root { - return nil, nil, fmt.Errorf("invalid merkle root: header=%x computed=%x", header.Root, root) - } - - // Execute the database writes. - batch.Write() - - return state.Logs(), receipts, nil -} - -var ( - big8 = big.NewInt(8) - big32 = big.NewInt(32) -) - -// AccumulateRewards credits the coinbase of the given block with the -// mining reward. The total reward consists of the static block reward -// and rewards for included uncles. The coinbase of each uncle block is -// also rewarded. -func AccumulateRewards(statedb *state.StateDB, header *types.Header, uncles []*types.Header) { - reward := new(big.Int).Set(BlockReward) - r := new(big.Int) - for _, uncle := range uncles { - r.Add(uncle.Number, big8) - r.Sub(r, header.Number) - r.Mul(r, BlockReward) - r.Div(r, big8) - statedb.AddBalance(uncle.Coinbase, r) - - r.Div(BlockReward, big32) - reward.Add(reward, r) - } - statedb.AddBalance(header.Coinbase, reward) -} - -func (sm *BlockProcessor) VerifyUncles(statedb *state.StateDB, block, parent *types.Block) error { - uncles := set.New() - ancestors := make(map[common.Hash]*types.Block) - for _, ancestor := range sm.bc.GetBlocksFromHash(block.ParentHash(), 7) { - ancestors[ancestor.Hash()] = ancestor - // Include ancestors uncles in the uncle set. Uncles must be unique. - for _, uncle := range ancestor.Uncles() { - uncles.Add(uncle.Hash()) - } - } - ancestors[block.Hash()] = block - uncles.Add(block.Hash()) - - for i, uncle := range block.Uncles() { - hash := uncle.Hash() - if uncles.Has(hash) { - // Error not unique - return UncleError("uncle[%d](%x) not unique", i, hash[:4]) - } - uncles.Add(hash) - - if ancestors[hash] != nil { - branch := fmt.Sprintf(" O - %x\n |\n", block.Hash()) - for h := range ancestors { - branch += fmt.Sprintf(" O - %x\n |\n", h) - } - glog.Infoln(branch) - return UncleError("uncle[%d](%x) is ancestor", i, hash[:4]) - } - - if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == parent.Hash() { - return UncleError("uncle[%d](%x)'s parent is not ancestor (%x)", i, hash[:4], uncle.ParentHash[0:4]) - } - - if err := ValidateHeader(sm.Pow, uncle, ancestors[uncle.ParentHash].Header(), true, true); err != nil { - return ValidationError(fmt.Sprintf("uncle[%d](%x) header invalid: %v", i, hash[:4], err)) - } - } - - return nil -} - -// GetBlockReceipts returns the receipts beloniging to the block hash -func (sm *BlockProcessor) GetBlockReceipts(bhash common.Hash) types.Receipts { - if block := sm.BlockChain().GetBlock(bhash); block != nil { - return GetBlockReceipts(sm.chainDb, block.Hash()) - } - - return nil -} - -// GetLogs returns the logs of the given block. This method is using a two step approach -// where it tries to get it from the (updated) method which gets them from the receipts or -// the depricated way by re-processing the block. -func (sm *BlockProcessor) GetLogs(block *types.Block) (logs vm.Logs, err error) { - receipts := GetBlockReceipts(sm.chainDb, block.Hash()) - // coalesce logs - for _, receipt := range receipts { - logs = append(logs, receipt.Logs...) - } - return logs, nil -} - -// ValidateHeader verifies the validity of a header, relying on the database and -// POW behind the block processor. -func (sm *BlockProcessor) ValidateHeader(header *types.Header, checkPow, uncle bool) error { - // Short circuit if the header's already known or its parent missing - if sm.bc.HasHeader(header.Hash()) { - return nil - } - if parent := sm.bc.GetHeader(header.ParentHash); parent == nil { - return ParentError(header.ParentHash) - } else { - return ValidateHeader(sm.Pow, header, parent, checkPow, uncle) - } -} - -// ValidateHeaderWithParent verifies the validity of a header, relying on the database and -// POW behind the block processor. -func (sm *BlockProcessor) ValidateHeaderWithParent(header, parent *types.Header, checkPow, uncle bool) error { - if sm.bc.HasHeader(header.Hash()) { - return nil - } - return ValidateHeader(sm.Pow, header, parent, checkPow, uncle) -} - -// See YP section 4.3.4. "Block Header Validity" -// Validates a header. Returns an error if the header is invalid. -func ValidateHeader(pow pow.PoW, header *types.Header, parent *types.Header, checkPow, uncle bool) error { - if big.NewInt(int64(len(header.Extra))).Cmp(params.MaximumExtraDataSize) == 1 { - return fmt.Errorf("Header extra data too long (%d)", len(header.Extra)) - } - if uncle { - if header.Time.Cmp(common.MaxBig) == 1 { - return BlockTSTooBigErr - } - } else { - if header.Time.Cmp(big.NewInt(time.Now().Unix())) == 1 { - return BlockFutureErr - } - } - if header.Time.Cmp(parent.Time) != 1 { - return BlockEqualTSErr - } - - expd := CalcDifficulty(header.Time.Uint64(), parent.Time.Uint64(), parent.Number, parent.Difficulty) - if expd.Cmp(header.Difficulty) != 0 { - return fmt.Errorf("Difficulty check failed for header %v, %v", header.Difficulty, expd) - } - - a := new(big.Int).Set(parent.GasLimit) - a = a.Sub(a, header.GasLimit) - a.Abs(a) - b := new(big.Int).Set(parent.GasLimit) - b = b.Div(b, params.GasLimitBoundDivisor) - if !(a.Cmp(b) < 0) || (header.GasLimit.Cmp(params.MinGasLimit) == -1) { - return fmt.Errorf("GasLimit check failed for header %v (%v > %v)", header.GasLimit, a, b) - } - - num := new(big.Int).Set(parent.Number) - num.Sub(header.Number, num) - if num.Cmp(big.NewInt(1)) != 0 { - return BlockNumberErr - } - - if checkPow { - // Verify the nonce of the header. Return an error if it's not valid - if !pow.Verify(types.NewBlockWithHeader(header)) { - return &BlockNonceErr{Hash: header.Hash(), Number: header.Number, Nonce: header.Nonce.Uint64()} - } - } - return nil -} diff --git a/core/block_validator.go b/core/block_validator.go new file mode 100644 index 000000000..62d096d02 --- /dev/null +++ b/core/block_validator.go @@ -0,0 +1,243 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/pow" + "gopkg.in/fatih/set.v0" +) + +// BlockValidator is responsible for validating block headers, uncles and +// processed state. +// +// BlockValidator implements Validator. +type BlockValidator struct { + bc *BlockChain // Canonical block chain + Pow pow.PoW // Proof of work used for validating +} + +// NewBlockValidator returns a new block validator which is safe for re-use +func NewBlockValidator(blockchain *BlockChain, pow pow.PoW) *BlockValidator { + validator := &BlockValidator{ + Pow: pow, + bc: blockchain, + } + return validator +} + +// ValidateBlock validates the given block's header and uncles and verifies the +// the block header's transaction and uncle roots. +// +// ValidateBlock does not validate the header's pow. The pow work validated +// seperately so we can process them in paralel. +// +// ValidateBlock also validates and makes sure that any previous state (or present) +// state that might or might not be present is checked to make sure that fast +// sync has done it's job proper. This prevents the block validator form accepting +// false positives where a header is present but the state is not. +func (v *BlockValidator) ValidateBlock(block *types.Block) error { + if v.bc.HasBlock(block.Hash()) { + if _, err := state.New(block.Root(), v.bc.chainDb); err == nil { + return &KnownBlockError{block.Number(), block.Hash()} + } + } + parent := v.bc.GetBlock(block.ParentHash()) + if parent == nil { + return ParentError(block.ParentHash()) + } + if _, err := state.New(parent.Root(), v.bc.chainDb); err != nil { + return ParentError(block.ParentHash()) + } + + header := block.Header() + // validate the block header + if err := ValidateHeader(v.Pow, header, parent.Header(), false, false); err != nil { + return err + } + // verify the uncles are correctly rewarded + if err := v.VerifyUncles(block, parent); err != nil { + return err + } + + // Verify UncleHash before running other uncle validations + unclesSha := types.CalcUncleHash(block.Uncles()) + if unclesSha != header.UncleHash { + return fmt.Errorf("invalid uncles root hash. received=%x calculated=%x", header.UncleHash, unclesSha) + } + + // The transactions Trie's root (R = (Tr [[i, RLP(T1)], [i, RLP(T2)], ... [n, RLP(Tn)]])) + // can be used by light clients to make sure they've received the correct Txs + txSha := types.DeriveSha(block.Transactions()) + if txSha != header.TxHash { + return fmt.Errorf("invalid transaction root hash. received=%x calculated=%x", header.TxHash, txSha) + } + + return nil +} + +// ValidateState validates the various changes that happen after a state +// transition, such as amount of used gas, the receipt roots and the state root +// itself. ValidateState returns a database batch if the validation was a succes +// otherwise nil and an error is returned. +func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas *big.Int) (err error) { + header := block.Header() + if block.GasUsed().Cmp(usedGas) != 0 { + return ValidationError(fmt.Sprintf("gas used error (%v / %v)", block.GasUsed(), usedGas)) + } + // Validate the received block's bloom with the one derived from the generated receipts. + // For valid blocks this should always validate to true. + rbloom := types.CreateBloom(receipts) + if rbloom != header.Bloom { + return fmt.Errorf("unable to replicate block's bloom=%x", rbloom) + } + // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]])) + receiptSha := types.DeriveSha(receipts) + if receiptSha != header.ReceiptHash { + return fmt.Errorf("invalid receipt root hash. received=%x calculated=%x", header.ReceiptHash, receiptSha) + } + // Validate the state root against the received state root and throw + // an error if they don't match. + if root := statedb.IntermediateRoot(); header.Root != root { + return fmt.Errorf("invalid merkle root: header=%x computed=%x", header.Root, root) + } + return nil +} + +// VerifyUncles verifies the given block's uncles and applies the Ethereum +// consensus rules to the various block headers included; it will return an +// error if any of the included uncle headers were invalid. It returns an error +// if the validation failed. +func (v *BlockValidator) VerifyUncles(block, parent *types.Block) error { + // validate that there at most 2 uncles included in this block + if len(block.Uncles()) > 2 { + return ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(block.Uncles())) + } + + uncles := set.New() + ancestors := make(map[common.Hash]*types.Block) + for _, ancestor := range v.bc.GetBlocksFromHash(block.ParentHash(), 7) { + ancestors[ancestor.Hash()] = ancestor + // Include ancestors uncles in the uncle set. Uncles must be unique. + for _, uncle := range ancestor.Uncles() { + uncles.Add(uncle.Hash()) + } + } + ancestors[block.Hash()] = block + uncles.Add(block.Hash()) + + for i, uncle := range block.Uncles() { + hash := uncle.Hash() + if uncles.Has(hash) { + // Error not unique + return UncleError("uncle[%d](%x) not unique", i, hash[:4]) + } + uncles.Add(hash) + + if ancestors[hash] != nil { + branch := fmt.Sprintf(" O - %x\n |\n", block.Hash()) + for h := range ancestors { + branch += fmt.Sprintf(" O - %x\n |\n", h) + } + glog.Infoln(branch) + return UncleError("uncle[%d](%x) is ancestor", i, hash[:4]) + } + + if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == parent.Hash() { + return UncleError("uncle[%d](%x)'s parent is not ancestor (%x)", i, hash[:4], uncle.ParentHash[0:4]) + } + + if err := ValidateHeader(v.Pow, uncle, ancestors[uncle.ParentHash].Header(), true, true); err != nil { + return ValidationError(fmt.Sprintf("uncle[%d](%x) header invalid: %v", i, hash[:4], err)) + } + } + + return nil +} + +// ValidateHeader validates the given header and, depending on the pow arg, +// checks the proof of work of the given header. Returns an error if the +// validation failed. +func (v *BlockValidator) ValidateHeader(header, parent *types.Header, checkPow bool) error { + // Short circuit if the parent is missing. + if parent == nil { + return ParentError(header.ParentHash) + } + // Short circuit if the header's already known or its parent missing + if v.bc.HasHeader(header.Hash()) { + return nil + } + return ValidateHeader(v.Pow, header, parent, checkPow, false) +} + +// Validates a header. Returns an error if the header is invalid. +// +// See YP section 4.3.4. "Block Header Validity" +func ValidateHeader(pow pow.PoW, header *types.Header, parent *types.Header, checkPow, uncle bool) error { + if big.NewInt(int64(len(header.Extra))).Cmp(params.MaximumExtraDataSize) == 1 { + return fmt.Errorf("Header extra data too long (%d)", len(header.Extra)) + } + + if uncle { + if header.Time.Cmp(common.MaxBig) == 1 { + return BlockTSTooBigErr + } + } else { + if header.Time.Cmp(big.NewInt(time.Now().Unix())) == 1 { + return BlockFutureErr + } + } + if header.Time.Cmp(parent.Time) != 1 { + return BlockEqualTSErr + } + + expd := CalcDifficulty(header.Time.Uint64(), parent.Time.Uint64(), parent.Number, parent.Difficulty) + if expd.Cmp(header.Difficulty) != 0 { + return fmt.Errorf("Difficulty check failed for header %v, %v", header.Difficulty, expd) + } + + a := new(big.Int).Set(parent.GasLimit) + a = a.Sub(a, header.GasLimit) + a.Abs(a) + b := new(big.Int).Set(parent.GasLimit) + b = b.Div(b, params.GasLimitBoundDivisor) + if !(a.Cmp(b) < 0) || (header.GasLimit.Cmp(params.MinGasLimit) == -1) { + return fmt.Errorf("GasLimit check failed for header %v (%v > %v)", header.GasLimit, a, b) + } + + num := new(big.Int).Set(parent.Number) + num.Sub(header.Number, num) + if num.Cmp(big.NewInt(1)) != 0 { + return BlockNumberErr + } + + if checkPow { + // Verify the nonce of the header. Return an error if it's not valid + if !pow.Verify(types.NewBlockWithHeader(header)) { + return &BlockNonceErr{header.Number, header.Hash(), header.Nonce.Uint64()} + } + } + return nil +} diff --git a/core/block_processor_test.go b/core/block_validator_test.go index 3050456b4..a0694f067 100644 --- a/core/block_processor_test.go +++ b/core/block_validator_test.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/pow/ezp" ) -func proc() (*BlockProcessor, *BlockChain) { +func proc() (Validator, *BlockChain) { db, _ := ethdb.NewMemDatabase() var mux event.TypeMux @@ -39,7 +39,7 @@ func proc() (*BlockProcessor, *BlockChain) { if err != nil { fmt.Println(err) } - return NewBlockProcessor(db, ezp.New(), blockchain, &mux), blockchain + return blockchain.validator, blockchain } func TestNumber(t *testing.T) { diff --git a/core/blockchain.go b/core/blockchain.go index cea346e38..b6b00ca04 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -61,17 +62,34 @@ const ( blockCacheLimit = 256 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 + // must be bumped when consensus algorithm is changed, this forces the upgradedb + // command to be run (forces the blocks to be imported again using the new algorithm) + BlockChainVersion = 3 ) +// BlockChain represents the canonical chain given a database with a genesis +// block. The Blockchain manages chain imports, reverts, chain reorganisations. +// +// Importing blocks in to the block chain happens according to the set of rules +// defined by the two stage Validator. Processing of blocks is done using the +// Processor which processes the included transaction. The validation of the state +// is done in the second part of the Validator. Failing results in aborting of +// the import. +// +// The BlockChain also helps in returning blocks from **any** chain included +// in the database as well as blocks that represents the canonical chain. It's +// important to note that GetBlock can return any block and does not need to be +// included in the canonical one where as GetBlockByNumber always represents the +// canonical chain. type BlockChain struct { chainDb ethdb.Database - processor types.BlockProcessor eventMux *event.TypeMux genesisBlock *types.Block // Last known total difficulty mu sync.RWMutex chainmu sync.RWMutex tsmu sync.RWMutex + procmu sync.RWMutex checkpoint int // checkpoint counts towards the new checkpoint currentHeader *types.Header // Current head of the header chain (may be above the block chain!) @@ -91,10 +109,15 @@ type BlockChain struct { procInterrupt int32 // interrupt signaler for block processing wg sync.WaitGroup - pow pow.PoW - rand *mrand.Rand + pow pow.PoW + rand *mrand.Rand + processor Processor + validator Validator } +// NewBlockChain returns a fully initialised block chain using information +// available in the database. It initialiser the default Ethereum Validator and +// Processor. func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*BlockChain, error) { headerCache, _ := lru.New(headerCacheLimit) bodyCache, _ := lru.New(bodyCacheLimit) @@ -121,6 +144,8 @@ func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*Bl return nil, err } bc.rand = mrand.New(mrand.NewSource(seed.Int64())) + bc.SetValidator(NewBlockValidator(bc, pow)) + bc.SetProcessor(NewStateProcessor(bc)) bc.genesisBlock = bc.GetBlockByNumber(0) if bc.genesisBlock == nil { @@ -292,6 +317,7 @@ func (self *BlockChain) FastSyncCommitHead(hash common.Hash) error { return nil } +// GasLimit returns the gas limit of the current HEAD block. func (self *BlockChain) GasLimit() *big.Int { self.mu.RLock() defer self.mu.RUnlock() @@ -299,6 +325,7 @@ func (self *BlockChain) GasLimit() *big.Int { return self.currentBlock.GasLimit() } +// LastBlockHash return the hash of the HEAD block. func (self *BlockChain) LastBlockHash() common.Hash { self.mu.RLock() defer self.mu.RUnlock() @@ -333,6 +360,8 @@ func (self *BlockChain) CurrentFastBlock() *types.Block { return self.currentFastBlock } +// Status returns status information about the current chain such as the HEAD Td, +// the HEAD hash and the hash of the genesis block. func (self *BlockChain) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) { self.mu.RLock() defer self.mu.RUnlock() @@ -340,10 +369,38 @@ func (self *BlockChain) Status() (td *big.Int, currentBlock common.Hash, genesis return self.GetTd(self.currentBlock.Hash()), self.currentBlock.Hash(), self.genesisBlock.Hash() } -func (self *BlockChain) SetProcessor(proc types.BlockProcessor) { - self.processor = proc +// SetProcessor sets the processor required for making state modifications. +func (self *BlockChain) SetProcessor(processor Processor) { + self.procmu.Lock() + defer self.procmu.Unlock() + self.processor = processor +} + +// SetValidator sets the validator which is used to validate incoming blocks. +func (self *BlockChain) SetValidator(validator Validator) { + self.procmu.Lock() + defer self.procmu.Unlock() + self.validator = validator } +// Validator returns the current validator. +func (self *BlockChain) Validator() Validator { + self.procmu.RLock() + defer self.procmu.RUnlock() + return self.validator +} + +// Processor returns the current processor. +func (self *BlockChain) Processor() Processor { + self.procmu.RLock() + defer self.procmu.RUnlock() + return self.processor +} + +// AuxValidator returns the auxiliary validator (Proof of work atm) +func (self *BlockChain) AuxValidator() pow.PoW { return self.pow } + +// State returns a new mutable state based on the current HEAD block. func (self *BlockChain) State() (*state.StateDB, error) { return state.New(self.CurrentBlock().Root(), self.chainDb) } @@ -606,6 +663,8 @@ func (self *BlockChain) GetUnclesInChain(block *types.Block, length int) []*type return uncles } +// Stop stops the blockchain service. If any imports are currently in progress +// it will abort them using the procInterrupt. func (bc *BlockChain) Stop() { if !atomic.CompareAndSwapInt32(&bc.running, 0, 1) { return @@ -758,9 +817,9 @@ func (self *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) var err error if index == 0 { - err = self.processor.ValidateHeader(header, checkPow, false) + err = self.Validator().ValidateHeader(header, self.GetHeader(header.ParentHash), checkPow) } else { - err = self.processor.ValidateHeaderWithParent(header, chain[index-1], checkPow, false) + err = self.Validator().ValidateHeader(header, chain[index-1], checkPow) } if err != nil { errs[index] = err @@ -1025,9 +1084,10 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { // faster than direct delivery and requires much less mutex // acquiring. var ( - stats struct{ queued, processed, ignored int } - events = make([]interface{}, 0, len(chain)) - tstart = time.Now() + stats struct{ queued, processed, ignored int } + events = make([]interface{}, 0, len(chain)) + coalescedLogs vm.Logs + tstart = time.Now() nonceChecked = make([]bool, len(chain)) ) @@ -1057,12 +1117,12 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { if BadHashes[block.Hash()] { err := BadHashError(block.Hash()) - blockErr(block, err) + reportBlock(block, err) return i, err } - // Call in to the block processor and check for errors. It's likely that if one block fails - // all others will fail too (unless a known block is returned). - logs, receipts, err := self.processor.Process(block) + // Stage 1 validation of the block using the chain's validator + // interface. + err := self.Validator().ValidateBlock(block) if err != nil { if IsKnownBlockErr(err) { stats.ignored++ @@ -1089,14 +1149,41 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { continue } - blockErr(block, err) + reportBlock(block, err) - go ReportBlock(block, err) + return i, err + } + // Create a new statedb using the parent block and report an + // error if it fails. + statedb, err := state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb) + if err != nil { + reportBlock(block, err) return i, err } + // Process block using the parent state as reference point. + receipts, logs, usedGas, err := self.processor.Process(block, statedb) + if err != nil { + reportBlock(block, err) + return i, err + } + // Validate the state using the default validator + err = self.Validator().ValidateState(block, self.GetBlock(block.ParentHash()), statedb, receipts, usedGas) + if err != nil { + reportBlock(block, err) + return i, err + } + // Write state changes to database + _, err = statedb.Commit() + if err != nil { + return i, err + } + + // coalesce logs for later processing + coalescedLogs = append(coalescedLogs, logs...) + if err := PutBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil { - glog.V(logger.Warn).Infoln("error writing block receipts:", err) + return i, err } txcount += len(block.Transactions()) @@ -1105,6 +1192,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { if err != nil { return i, err } + switch status { case CanonStatTy: if glog.V(logger.Debug) { @@ -1141,7 +1229,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { start, end := chain[0], chain[len(chain)-1] glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", stats.processed, stats.queued, stats.ignored, txcount, tend, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4]) } - go self.postChainEvents(events) + go self.postChainEvents(events, coalescedLogs) return 0, nil } @@ -1239,7 +1327,9 @@ func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // postChainEvents iterates over the events generated by a chain insertion and // posts them into the event mux. -func (self *BlockChain) postChainEvents(events []interface{}) { +func (self *BlockChain) postChainEvents(events []interface{}, logs vm.Logs) { + // post event logs for further processing + self.eventMux.Post(logs) for _, event := range events { if event, ok := event.(ChainEvent); ok { // We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long @@ -1265,9 +1355,13 @@ func (self *BlockChain) update() { } } -func blockErr(block *types.Block, err error) { +// reportBlock reports the given block and error using the canonical block +// reporting tool. Reporting the block to the service is handled in a separate +// goroutine. +func reportBlock(block *types.Block, err error) { if glog.V(logger.Error) { glog.Errorf("Bad block #%v (%s)\n", block.Number(), block.Hash().Hex()) glog.Errorf(" %v", err) } + go ReportBlock(block, err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 8ddc5032b..e5ed66377 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -53,31 +54,29 @@ func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain { WriteTestNetGenesisBlock(db, 0) blockchain, err := NewBlockChain(db, thePow(), &eventMux) if err != nil { - t.Error("failed creating chainmanager:", err) + t.Error("failed creating blockchain:", err) t.FailNow() return nil } - blockMan := NewBlockProcessor(db, nil, blockchain, &eventMux) - blockchain.SetProcessor(blockMan) return blockchain } // Test fork of length N starting from block i -func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comparator func(td1, td2 *big.Int)) { +func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { // Copy old chain up to #i into a new db - db, processor2, err := newCanonical(i, full) + db, blockchain2, err := newCanonical(i, full) if err != nil { t.Fatal("could not make new canonical in testFork", err) } // Assert the chains have the same header/block at #i var hash1, hash2 common.Hash if full { - hash1 = processor.bc.GetBlockByNumber(uint64(i)).Hash() - hash2 = processor2.bc.GetBlockByNumber(uint64(i)).Hash() + hash1 = blockchain.GetBlockByNumber(uint64(i)).Hash() + hash2 = blockchain2.GetBlockByNumber(uint64(i)).Hash() } else { - hash1 = processor.bc.GetHeaderByNumber(uint64(i)).Hash() - hash2 = processor2.bc.GetHeaderByNumber(uint64(i)).Hash() + hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash() + hash2 = blockchain2.GetHeaderByNumber(uint64(i)).Hash() } if hash1 != hash2 { t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) @@ -88,13 +87,13 @@ func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comp headerChainB []*types.Header ) if full { - blockChainB = makeBlockChain(processor2.bc.CurrentBlock(), n, db, forkSeed) - if _, err := processor2.bc.InsertChain(blockChainB); err != nil { + blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, db, forkSeed) + if _, err := blockchain2.InsertChain(blockChainB); err != nil { t.Fatalf("failed to insert forking chain: %v", err) } } else { - headerChainB = makeHeaderChain(processor2.bc.CurrentHeader(), n, db, forkSeed) - if _, err := processor2.bc.InsertHeaderChain(headerChainB, 1); err != nil { + headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, db, forkSeed) + if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil { t.Fatalf("failed to insert forking chain: %v", err) } } @@ -102,17 +101,17 @@ func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comp var tdPre, tdPost *big.Int if full { - tdPre = processor.bc.GetTd(processor.bc.CurrentBlock().Hash()) - if err := testBlockChainImport(blockChainB, processor); err != nil { + tdPre = blockchain.GetTd(blockchain.CurrentBlock().Hash()) + if err := testBlockChainImport(blockChainB, blockchain); err != nil { t.Fatalf("failed to import forked block chain: %v", err) } - tdPost = processor.bc.GetTd(blockChainB[len(blockChainB)-1].Hash()) + tdPost = blockchain.GetTd(blockChainB[len(blockChainB)-1].Hash()) } else { - tdPre = processor.bc.GetTd(processor.bc.CurrentHeader().Hash()) - if err := testHeaderChainImport(headerChainB, processor); err != nil { + tdPre = blockchain.GetTd(blockchain.CurrentHeader().Hash()) + if err := testHeaderChainImport(headerChainB, blockchain); err != nil { t.Fatalf("failed to import forked header chain: %v", err) } - tdPost = processor.bc.GetTd(headerChainB[len(headerChainB)-1].Hash()) + tdPost = blockchain.GetTd(headerChainB[len(headerChainB)-1].Hash()) } // Compare the total difficulties of the chains comparator(tdPre, tdPost) @@ -127,37 +126,52 @@ func printChain(bc *BlockChain) { // testBlockChainImport tries to process a chain of blocks, writing them into // the database if successful. -func testBlockChainImport(chain []*types.Block, processor *BlockProcessor) error { +func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { for _, block := range chain { // Try and process the block - if _, _, err := processor.Process(block); err != nil { + err := blockchain.Validator().ValidateBlock(block) + if err != nil { if IsKnownBlockErr(err) { continue } return err } - // Manually insert the block into the database, but don't reorganize (allows subsequent testing) - processor.bc.mu.Lock() - WriteTd(processor.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), processor.bc.GetTd(block.ParentHash()))) - WriteBlock(processor.chainDb, block) - processor.bc.mu.Unlock() + statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), blockchain.chainDb) + if err != nil { + return err + } + receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb) + if err != nil { + reportBlock(block, err) + return err + } + err = blockchain.Validator().ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas) + if err != nil { + reportBlock(block, err) + return err + } + blockchain.mu.Lock() + WriteTd(blockchain.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), blockchain.GetTd(block.ParentHash()))) + WriteBlock(blockchain.chainDb, block) + statedb.Commit() + blockchain.mu.Unlock() } return nil } // testHeaderChainImport tries to process a chain of header, writing them into // the database if successful. -func testHeaderChainImport(chain []*types.Header, processor *BlockProcessor) error { +func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error { for _, header := range chain { // Try and validate the header - if err := processor.ValidateHeader(header, false, false); err != nil { + if err := blockchain.Validator().ValidateHeader(header, blockchain.GetHeader(header.ParentHash), false); err != nil { return err } // Manually insert the header into the database, but don't reorganize (allows subsequent testing) - processor.bc.mu.Lock() - WriteTd(processor.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, processor.bc.GetTd(header.ParentHash))) - WriteHeader(processor.chainDb, header) - processor.bc.mu.Unlock() + blockchain.mu.Lock() + WriteTd(blockchain.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, blockchain.GetTd(header.ParentHash))) + WriteHeader(blockchain.chainDb, header) + blockchain.mu.Unlock() } return nil } @@ -313,19 +327,19 @@ func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) } func testBrokenChain(t *testing.T, full bool) { // Make chain starting from genesis - db, processor, err := newCanonical(10, full) + db, blockchain, err := newCanonical(10, full) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } // Create a forked chain, and try to insert with a missing link if full { - chain := makeBlockChain(processor.bc.CurrentBlock(), 5, db, forkSeed)[1:] - if err := testBlockChainImport(chain, processor); err == nil { + chain := makeBlockChain(blockchain.CurrentBlock(), 5, db, forkSeed)[1:] + if err := testBlockChainImport(chain, blockchain); err == nil { t.Errorf("broken block chain not reported") } } else { - chain := makeHeaderChain(processor.bc.CurrentHeader(), 5, db, forkSeed)[1:] - if err := testHeaderChainImport(chain, processor); err == nil { + chain := makeHeaderChain(blockchain.CurrentHeader(), 5, db, forkSeed)[1:] + if err := testHeaderChainImport(chain, blockchain); err == nil { t.Errorf("broken header chain not reported") } } @@ -415,9 +429,14 @@ func TestChainMultipleInsertions(t *testing.T) { type bproc struct{} -func (bproc) Process(*types.Block) (vm.Logs, types.Receipts, error) { return nil, nil, nil } -func (bproc) ValidateHeader(*types.Header, bool, bool) error { return nil } -func (bproc) ValidateHeaderWithParent(*types.Header, *types.Header, bool, bool) error { return nil } +func (bproc) ValidateBlock(*types.Block) error { return nil } +func (bproc) ValidateHeader(*types.Header, *types.Header, bool) error { return nil } +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) (types.Receipts, vm.Logs, *big.Int, error) { + return nil, nil, nil, nil +} func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { blocks := makeBlockChainWithDiff(genesis, d, seed) @@ -459,7 +478,8 @@ func chm(genesis *types.Block, db ethdb.Database) *BlockChain { bc.tdCache, _ = lru.New(100) bc.blockCache, _ = lru.New(100) bc.futureBlocks, _ = lru.New(100) - bc.processor = bproc{} + bc.SetValidator(bproc{}) + bc.SetProcessor(bproc{}) bc.ResetWithGenesisBlock(genesis) return bc @@ -612,12 +632,10 @@ func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) } func testInsertNonceError(t *testing.T, full bool) { for i := 1; i < 25 && !t.Failed(); i++ { // Create a pristine chain and database - db, processor, err := newCanonical(0, full) + db, blockchain, err := newCanonical(0, full) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } - bc := processor.bc - // Create and insert a chain with a failing nonce var ( failAt int @@ -626,34 +644,33 @@ func testInsertNonceError(t *testing.T, full bool) { failHash common.Hash ) if full { - blocks := makeBlockChain(processor.bc.CurrentBlock(), i, db, 0) + blocks := makeBlockChain(blockchain.CurrentBlock(), i, db, 0) failAt = rand.Int() % len(blocks) failNum = blocks[failAt].NumberU64() failHash = blocks[failAt].Hash() - processor.bc.pow = failPow{failNum} - processor.Pow = failPow{failNum} + blockchain.pow = failPow{failNum} - failRes, err = processor.bc.InsertChain(blocks) + failRes, err = blockchain.InsertChain(blocks) } else { - headers := makeHeaderChain(processor.bc.CurrentHeader(), i, db, 0) + headers := makeHeaderChain(blockchain.CurrentHeader(), i, db, 0) failAt = rand.Int() % len(headers) failNum = headers[failAt].Number.Uint64() failHash = headers[failAt].Hash() - processor.bc.pow = failPow{failNum} - processor.Pow = failPow{failNum} + blockchain.pow = failPow{failNum} + blockchain.validator = NewBlockValidator(blockchain, failPow{failNum}) - failRes, err = processor.bc.InsertHeaderChain(headers, 1) + failRes, err = blockchain.InsertHeaderChain(headers, 1) } // Check that the returned error indicates the nonce failure. if failRes != failAt { t.Errorf("test %d: failure index mismatch: have %d, want %d", i, failRes, failAt) } if !IsBlockNonceErr(err) { - t.Fatalf("test %d: error mismatch: have %v, want nonce error", i, err) + t.Fatalf("test %d: error mismatch: have %v, want nonce error %T", i, err, err) } nerr := err.(*BlockNonceErr) if nerr.Number.Uint64() != failNum { @@ -665,11 +682,11 @@ func testInsertNonceError(t *testing.T, full bool) { // Check that all no blocks after the failing block have been inserted. for j := 0; j < i-failAt; j++ { if full { - if block := bc.GetBlockByNumber(failNum + uint64(j)); block != nil { + if block := blockchain.GetBlockByNumber(failNum + uint64(j)); block != nil { t.Errorf("test %d: invalid block in chain: %v", i, block) } } else { - if header := bc.GetHeaderByNumber(failNum + uint64(j)); header != nil { + if header := blockchain.GetHeaderByNumber(failNum + uint64(j)); header != nil { t.Errorf("test %d: invalid header in chain: %v", i, header) } } @@ -711,7 +728,6 @@ func TestFastVsFullChains(t *testing.T) { WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds}) archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux)) - archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux))) if n, err := archive.InsertChain(blocks); err != nil { t.Fatalf("failed to process block %d: %v", n, err) @@ -720,7 +736,6 @@ func TestFastVsFullChains(t *testing.T) { fastDb, _ := ethdb.NewMemDatabase() WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds}) fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux)) - fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux))) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -797,7 +812,6 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds}) archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux)) - archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux))) if n, err := archive.InsertChain(blocks); err != nil { t.Fatalf("failed to process block %d: %v", n, err) @@ -810,7 +824,6 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { fastDb, _ := ethdb.NewMemDatabase() WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds}) fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux)) - fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux))) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -830,7 +843,6 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { lightDb, _ := ethdb.NewMemDatabase() WriteGenesisBlockForTesting(lightDb, GenesisAccount{address, funds}) light, _ := NewBlockChain(lightDb, FakePow{}, new(event.TypeMux)) - light.SetProcessor(NewBlockProcessor(lightDb, FakePow{}, light, new(event.TypeMux))) if n, err := light.InsertHeaderChain(headers, 1); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) @@ -895,9 +907,8 @@ func TestChainTxReorgs(t *testing.T) { }) // Import the chain. This runs all block validation rules. evmux := &event.TypeMux{} - chainman, _ := NewBlockChain(db, FakePow{}, evmux) - chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux)) - if i, err := chainman.InsertChain(chain); err != nil { + blockchain, _ := NewBlockChain(db, FakePow{}, evmux) + if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } @@ -920,7 +931,7 @@ func TestChainTxReorgs(t *testing.T) { gen.AddTx(futureAdd) // This transaction will be added after a full reorg } }) - if _, err := chainman.InsertChain(chain); err != nil { + if _, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert forked chain: %v", err) } diff --git a/core/chain_makers.go b/core/chain_makers.go index 56e37a0fc..f1ada487f 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -214,7 +214,7 @@ func makeHeader(parent *types.Block, state *state.StateDB) *types.Header { // newCanonical creates a chain database, and injects a deterministic canonical // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. -func newCanonical(n int, full bool) (ethdb.Database, *BlockProcessor, error) { +func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) { // Create te new chain database db, _ := ethdb.NewMemDatabase() evmux := &event.TypeMux{} @@ -223,23 +223,20 @@ func newCanonical(n int, full bool) (ethdb.Database, *BlockProcessor, error) { genesis, _ := WriteTestNetGenesisBlock(db, 0) blockchain, _ := NewBlockChain(db, FakePow{}, evmux) - processor := NewBlockProcessor(db, FakePow{}, blockchain, evmux) - processor.bc.SetProcessor(processor) - // Create and inject the requested chain if n == 0 { - return db, processor, nil + return db, blockchain, nil } if full { // Full block-chain requested blocks := makeBlockChain(genesis, n, db, canonicalSeed) _, err := blockchain.InsertChain(blocks) - return db, processor, err + return db, blockchain, err } // Header-only chain requested headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed) _, err := blockchain.InsertHeaderChain(headers, 1) - return db, processor, err + return db, blockchain, err } // makeHeaderChain creates a deterministic chain of headers rooted at parent. diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 7f47cf288..b9c1d89b7 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -77,15 +77,14 @@ func ExampleGenerateChain() { // Import the chain. This runs all block validation rules. evmux := &event.TypeMux{} - chainman, _ := NewBlockChain(db, FakePow{}, evmux) - chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux)) - if i, err := chainman.InsertChain(chain); err != nil { + blockchain, _ := NewBlockChain(db, FakePow{}, evmux) + if i, err := blockchain.InsertChain(chain); err != nil { fmt.Printf("insert error (block %d): %v\n", i, err) return } - state, _ := chainman.State() - fmt.Printf("last block: #%d\n", chainman.CurrentBlock().Number()) + state, _ := blockchain.State() + fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number()) fmt.Println("balance of addr1:", state.GetBalance(addr1)) fmt.Println("balance of addr2:", state.GetBalance(addr2)) fmt.Println("balance of addr3:", state.GetBalance(addr3)) diff --git a/core/types/common.go b/core/gaspool.go index fe682f98a..2ef07c754 100644 --- a/core/types/common.go +++ b/core/gaspool.go @@ -14,12 +14,33 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. -package types +package core -import "github.com/ethereum/go-ethereum/core/vm" +import "math/big" -type BlockProcessor interface { - Process(*Block) (vm.Logs, Receipts, error) - ValidateHeader(*Header, bool, bool) error - ValidateHeaderWithParent(*Header, *Header, bool, bool) error +// GasPool tracks the amount of gas available during +// execution of the transactions in a block. +// The zero value is a pool with zero gas available. +type GasPool big.Int + +// AddGas makes gas available for execution. +func (gp *GasPool) AddGas(amount *big.Int) *GasPool { + i := (*big.Int)(gp) + i.Add(i, amount) + return gp +} + +// SubGas deducts the given amount from the pool if enough gas is +// available and returns an error otherwise. +func (gp *GasPool) SubGas(amount *big.Int) error { + i := (*big.Int)(gp) + if i.Cmp(amount) < 0 { + return &GasLimitErr{Have: new(big.Int).Set(i), Want: amount} + } + i.Sub(i, amount) + return nil +} + +func (gp *GasPool) String() string { + return (*big.Int)(gp).String() } diff --git a/core/manager.go b/core/manager.go deleted file mode 100644 index 289c87c11..000000000 --- a/core/manager.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package core - -import ( - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" -) - -// TODO move this to types? -type Backend interface { - AccountManager() *accounts.Manager - BlockProcessor() *BlockProcessor - BlockChain() *BlockChain - TxPool() *TxPool - ChainDb() ethdb.Database - DappDb() ethdb.Database - EventMux() *event.TypeMux -} diff --git a/core/state_processor.go b/core/state_processor.go new file mode 100644 index 000000000..d9c24935d --- /dev/null +++ b/core/state_processor.go @@ -0,0 +1,107 @@ +package core + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +var ( + big8 = big.NewInt(8) + big32 = big.NewInt(32) +) + +type StateProcessor struct { + bc *BlockChain +} + +func NewStateProcessor(bc *BlockChain) *StateProcessor { + return &StateProcessor{bc} +} + +// Process processes the state changes according to the Ethereum rules by running +// the transaction messages using the statedb and applying any rewards to both +// the processor (coinbase) and any included uncles. +// +// 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) (types.Receipts, vm.Logs, *big.Int, error) { + var ( + receipts types.Receipts + totalUsedGas = big.NewInt(0) + err error + header = block.Header() + allLogs vm.Logs + gp = new(GasPool).AddGas(block.GasLimit()) + ) + + for i, tx := range block.Transactions() { + statedb.StartRecord(tx.Hash(), block.Hash(), i) + + receipt, logs, _, err := ApplyTransaction(p.bc, gp, statedb, header, tx, totalUsedGas) + if err != nil { + return nil, nil, totalUsedGas, err + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, logs...) + } + AccumulateRewards(statedb, header, block.Uncles()) + + return receipts, allLogs, totalUsedGas, err +} + +// ApplyTransaction attemps to apply a transaction to the given state database +// and uses the input parameters for its environment. +// +// ApplyTransactions returns the generated receipts and vm logs during the +// execution of the state transition phase. +func ApplyTransaction(bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int) (*types.Receipt, vm.Logs, *big.Int, error) { + _, gas, err := ApplyMessage(NewEnv(statedb, bc, tx, header), tx, gp) + if err != nil { + return nil, nil, nil, err + } + + // Update the state with pending changes + usedGas.Add(usedGas, gas) + receipt := types.NewReceipt(statedb.IntermediateRoot().Bytes(), usedGas) + receipt.TxHash = tx.Hash() + receipt.GasUsed = new(big.Int).Set(gas) + if MessageCreatesContract(tx) { + from, _ := tx.From() + receipt.ContractAddress = crypto.CreateAddress(from, tx.Nonce()) + } + + logs := statedb.GetLogs(tx.Hash()) + receipt.Logs = logs + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + + glog.V(logger.Debug).Infoln(receipt) + + return receipt, logs, gas, err +} + +// AccumulateRewards credits the coinbase of the given block with the +// mining reward. The total reward consists of the static block reward +// and rewards for included uncles. The coinbase of each uncle block is +// also rewarded. +func AccumulateRewards(statedb *state.StateDB, header *types.Header, uncles []*types.Header) { + reward := new(big.Int).Set(BlockReward) + r := new(big.Int) + for _, uncle := range uncles { + r.Add(uncle.Number, big8) + r.Sub(r, header.Number) + r.Mul(r, BlockReward) + r.Div(r, big8) + statedb.AddBalance(uncle.Coinbase, r) + + r.Div(BlockReward, big32) + reward.Add(reward, r) + } + statedb.AddBalance(header.Coinbase, reward) +} diff --git a/core/types.go b/core/types.go new file mode 100644 index 000000000..027f628b1 --- /dev/null +++ b/core/types.go @@ -0,0 +1,70 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package core + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" +) + +// Validator is an interface which defines the standard for block validation. +// +// The validator is responsible for validating incoming block or, if desired, +// validates headers for fast validation. +// +// ValidateBlock validates the given block and should return an error if it +// failed to do so and should be used for "full" validation. +// +// ValidateHeader validates the given header and parent and returns an error +// if it failed to do so. +// +// ValidateStack validates the given statedb and optionally the receipts and +// gas used. The implementor should decide what to do with the given input. +type Validator interface { + ValidateBlock(block *types.Block) error + ValidateHeader(header, parent *types.Header, checkPow bool) error + ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error +} + +// Processor is an interface for processing blocks using a given initial state. +// +// Process takes the block to be processed and the statedb upon which the +// initial state is based. It should return the receipts generated, amount +// 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) (types.Receipts, vm.Logs, *big.Int, error) +} + +// Backend is an interface defining the basic functionality for an operable node +// with all the functionality to be a functional, valid Ethereum operator. +// +// TODO Remove this +type Backend interface { + AccountManager() *accounts.Manager + BlockChain() *BlockChain + TxPool() *TxPool + ChainDb() ethdb.Database + DappDb() ethdb.Database + EventMux() *event.TypeMux +} |