core: integrated fork procedure

This commit is contained in:
Jeffrey Wilcke 2016-05-30 14:58:23 +02:00
parent 888b5c706e
commit 064a78507e
7 changed files with 150 additions and 60 deletions

View file

@ -66,19 +66,23 @@ func NewBlockValidator(config *ChainConfig, blockchain *BlockChain, pow pow.PoW)
// state that might or might not be present is checked to make sure that fast // 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 // 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. // false positives where a header is present but the state is not.
func (v *BlockValidator) ValidateBlock(block *types.Block) error { func (v *BlockValidator) ValidateBlock(reader BlockReader, block *types.Block) error {
if v.bc.HasBlock(block.Hash()) { /* wtf?
if _, err := state.New(block.Root(), v.bc.chainDb); err == nil { if reader.GetBlock(block.Hash()) != nil {
if _, err := state.New(block.Root(), v.chainDb); err == nil {
return &KnownBlockError{block.Number(), block.Hash()} return &KnownBlockError{block.Number(), block.Hash()}
} }
} }
parent := v.bc.GetBlock(block.ParentHash()) */
parent := reader.GetBlock(block.ParentHash())
if parent == nil { if parent == nil {
return ParentError(block.ParentHash()) return ParentError(block.ParentHash())
} }
/* wtf?
if _, err := state.New(parent.Root(), v.bc.chainDb); err != nil { if _, err := state.New(parent.Root(), v.bc.chainDb); err != nil {
return ParentError(block.ParentHash()) return ParentError(block.ParentHash())
} }
*/
header := block.Header() header := block.Header()
// validate the block header // validate the block header
@ -86,7 +90,7 @@ func (v *BlockValidator) ValidateBlock(block *types.Block) error {
return err return err
} }
// verify the uncles are correctly rewarded // verify the uncles are correctly rewarded
if err := v.VerifyUncles(block, parent); err != nil { if err := v.VerifyUncles(reader, block, parent); err != nil {
return err return err
} }
@ -138,15 +142,20 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
// consensus rules to the various block headers included; it will return an // 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 // error if any of the included uncle headers were invalid. It returns an error
// if the validation failed. // if the validation failed.
func (v *BlockValidator) VerifyUncles(block, parent *types.Block) error { func (v *BlockValidator) VerifyUncles(reader BlockReader, block, parent *types.Block) error {
usize := len(block.Uncles())
if usize == 0 {
return nil
}
// validate that there at most 2 uncles included in this block // validate that there at most 2 uncles included in this block
if len(block.Uncles()) > 2 { if usize > 2 {
return ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(block.Uncles())) return ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(block.Uncles()))
} }
uncles := set.New() uncles := set.New()
ancestors := make(map[common.Hash]*types.Block) ancestors := make(map[common.Hash]*types.Block)
for _, ancestor := range v.bc.GetBlocksFromHash(block.ParentHash(), 7) { for _, ancestor := range reader.GetBlocksFromHash(block.ParentHash(), 7) {
ancestors[ancestor.Hash()] = ancestor ancestors[ancestor.Hash()] = ancestor
// Include ancestors uncles in the uncle set. Uncles must be unique. // Include ancestors uncles in the uncle set. Uncles must be unique.
for _, uncle := range ancestor.Uncles() { for _, uncle := range ancestor.Uncles() {
@ -193,10 +202,12 @@ func (v *BlockValidator) ValidateHeader(header, parent *types.Header, checkPow b
if parent == nil { if parent == nil {
return ParentError(header.ParentHash) return ParentError(header.ParentHash)
} }
/* XXX i really don't like this here
// Short circuit if the header's already known or its parent missing // Short circuit if the header's already known or its parent missing
if v.bc.HasHeader(header.Hash()) { if v.bc.HasHeader(header.Hash()) {
return nil return nil
} }
*/
return ValidateHeader(v.config, v.Pow, header, parent, checkPow, false) return ValidateHeader(v.config, v.Pow, header, parent, checkPow, false)
} }

View file

@ -826,6 +826,10 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
nonceChecked = make([]bool, len(chain)) nonceChecked = make([]bool, len(chain))
statedb *state.StateDB statedb *state.StateDB
) )
fork, err := Fork(self.chainDb, self.Config(), self, chain[0].ParentHash(), len(chain))
if err != nil {
return 0, err
}
// Start the parallel nonce verifier. // Start the parallel nonce verifier.
nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain) nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain)
@ -838,7 +842,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
break break
} }
bstart := time.Now() //bstart := time.Now()
// Wait for block i's nonce to be verified before processing // Wait for block i's nonce to be verified before processing
// its state transition. // its state transition.
for !nonceChecked[i] { for !nonceChecked[i] {
@ -857,7 +861,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
} }
// Stage 1 validation of the block using the chain's validator // Stage 1 validation of the block using the chain's validator
// interface. // interface.
err := self.Validator().ValidateBlock(block) err := self.Validator().ValidateBlock(fork, block)
if err != nil { if err != nil {
if IsKnownBlockErr(err) { if IsKnownBlockErr(err) {
stats.ignored++ stats.ignored++
@ -892,6 +896,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// Create a new statedb using the parent block and report an // Create a new statedb using the parent block and report an
// error if it fails. // error if it fails.
if statedb == nil { if statedb == nil {
//statedb = fork.State()
statedb, err = state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb) statedb, err = state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb)
} else { } else {
err = statedb.Reset(chain[i-1].Root()) err = statedb.Reset(chain[i-1].Root())
@ -901,13 +906,13 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return i, err return i, err
} }
// Process block using the parent state as reference point. // Process block using the parent state as reference point.
receipts, logs, usedGas, err := self.processor.Process(GetHashFn(block.ParentHash(), self), block, statedb, self.config.VmConfig) receipts, _, usedGas, err := self.processor.Process(fork.GetNumHash, block, statedb, self.config.VmConfig)
if err != nil { if err != nil {
reportBlock(block, err) reportBlock(block, err)
return i, err return i, err
} }
// Validate the state using the default validator // Validate the state using the default validator
err = self.Validator().ValidateState(block, self.GetBlock(block.ParentHash()), statedb, receipts, usedGas) err = self.Validator().ValidateState(block, fork.GetBlock(block.ParentHash()), statedb, receipts, usedGas)
if err != nil { if err != nil {
reportBlock(block, err) reportBlock(block, err)
return i, err return i, err
@ -918,6 +923,12 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return i, err return i, err
} }
err = fork.CommitBlock(new(big.Int).Add(fork.GetTd(block.ParentHash()), block.Difficulty()), block, receipts)
if err != nil {
return i, err
}
/*
// coalesce logs for later processing // coalesce logs for later processing
coalescedLogs = append(coalescedLogs, logs...) coalescedLogs = append(coalescedLogs, logs...)
@ -960,8 +971,13 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
case SplitStatTy: case SplitStatTy:
events = append(events, ChainSplitEvent{block, logs}) events = append(events, ChainSplitEvent{block, logs})
} }
*/
stats.processed++ stats.processed++
} }
if err := fork.CommitToDb(); err != nil {
return 0, err
}
fork.ApplyTo(self)
if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) { if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) {
tend := time.Since(tstart) tend := time.Since(tstart)
@ -973,6 +989,21 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return 0, nil return 0, nil
} }
func (bc *BlockChain) Resolve(writer ethdb.Writer, changes []Changes) error {
for _, change := range changes {
block := change.block
// Add the block to the canonical chain number scheme and mark as the head
if err := WriteCanonicalHash(writer, block.Hash(), block.NumberU64()); err != nil {
return fmt.Errorf("failed to insert block number: %v", err)
}
}
if err := WriteHeadBlockHash(writer, changes[len(changes)-1].block.Hash()); err != nil {
return fmt.Errorf("failed to insert head block hash: %v", err)
}
return nil
}
// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them // reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
// to be part of the new canonical chain and accumulates potential missing transactions and post an // to be part of the new canonical chain and accumulates potential missing transactions and post an
// event about them // event about them

View file

@ -130,7 +130,7 @@ func printChain(bc *BlockChain) {
func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
for _, block := range chain { for _, block := range chain {
// Try and process the block // Try and process the block
err := blockchain.Validator().ValidateBlock(block) err := blockchain.Validator().ValidateBlock(blockchain, block)
if err != nil { if err != nil {
if IsKnownBlockErr(err) { if IsKnownBlockErr(err) {
continue continue
@ -430,7 +430,7 @@ func TestChainMultipleInsertions(t *testing.T) {
type bproc struct{} type bproc struct{}
func (bproc) ValidateBlock(*types.Block) error { return nil } func (bproc) ValidateBlock(BlockReader, *types.Block) error { return nil }
func (bproc) ValidateHeader(*types.Header, *types.Header, bool) 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 { func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error {
return nil return nil

View file

@ -233,7 +233,7 @@ func GetReceipt(db ethdb.ReadWriter, txHash common.Hash) *types.Receipt {
} }
// WriteCanonicalHash stores the canonical hash for the given block number. // WriteCanonicalHash stores the canonical hash for the given block number.
func WriteCanonicalHash(db ethdb.ReadWriter, hash common.Hash, number uint64) error { func WriteCanonicalHash(db ethdb.Writer, hash common.Hash, number uint64) error {
key := append(blockNumPrefix, big.NewInt(int64(number)).Bytes()...) key := append(blockNumPrefix, big.NewInt(int64(number)).Bytes()...)
if err := db.Put(key, hash.Bytes()); err != nil { if err := db.Put(key, hash.Bytes()); err != nil {
glog.Fatalf("failed to store number to hash mapping into database: %v", err) glog.Fatalf("failed to store number to hash mapping into database: %v", err)
@ -252,7 +252,7 @@ func WriteHeadHeaderHash(db ethdb.ReadWriter, hash common.Hash) error {
} }
// WriteHeadBlockHash stores the head block's hash. // WriteHeadBlockHash stores the head block's hash.
func WriteHeadBlockHash(db ethdb.ReadWriter, hash common.Hash) error { func WriteHeadBlockHash(db ethdb.Writer, hash common.Hash) error {
if err := db.Put(headBlockKey, hash.Bytes()); err != nil { if err := db.Put(headBlockKey, hash.Bytes()); err != nil {
glog.Fatalf("failed to store last block's hash into database: %v", err) glog.Fatalf("failed to store last block's hash into database: %v", err)
return err return err

View file

@ -19,6 +19,7 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"time" "time"
@ -33,7 +34,7 @@ var errUnboundedParent = errors.New("core/fork: parent hash does not match last
// ChainResolver should implement chain resolving and should be capable // ChainResolver should implement chain resolving and should be capable
// handling reorganisations. // handling reorganisations.
type ChainResolver interface { type ChainResolver interface {
Resolve(ethdb.ReadWriter, []Changes) error Resolve(ethdb.Writer, []Changes) error
} }
// Changes are changes that have previously been applied to the forked blockchain. // Changes are changes that have previously been applied to the forked blockchain.
@ -46,7 +47,8 @@ type Changes struct {
// BlockReader is the basic chain reader interface for reading blocks of the blockchain // BlockReader is the basic chain reader interface for reading blocks of the blockchain
type BlockReader interface { type BlockReader interface {
GetBlock(common.Hash) *types.Block // GetBlock returns the block that corresponds to the given hash GetBlock(common.Hash) *types.Block // GetBlock returns the block that corresponds to the given hash
Db() ethdb.Database // XXX this doesn't really belong here. GetBlocksFromHash(common.Hash, int) []*types.Block
GetTd(common.Hash) *big.Int
} }
// receipt keeps a log of changes for a specific block number which can // receipt keeps a log of changes for a specific block number which can
@ -75,12 +77,13 @@ type ChainFork struct {
// Fork returns a new blockchain with the given database as backing layer // Fork returns a new blockchain with the given database as backing layer
// for the localised blockchain transaction. // for the localised blockchain transaction.
func Fork(config *ChainConfig, blockReader BlockReader, origin common.Hash) (*ChainFork, error) { func Fork(db ethdb.Database, config *ChainConfig, blockReader BlockReader, origin common.Hash, size int) (*ChainFork, error) {
fork := &ChainFork{ fork := &ChainFork{
db: blockReader.Db(), db: db,
reader: blockReader, reader: blockReader,
config: config, config: config,
hashToIdx: make(map[common.Hash]int), hashToIdx: make(map[common.Hash]int),
changes: make([]Changes, 0, size),
} }
// get the origin block from which this fork originates // get the origin block from which this fork originates
@ -143,6 +146,43 @@ func (fork *ChainFork) GetBlock(hash common.Hash) *types.Block {
return fork.changes[idx].block return fork.changes[idx].block
} }
return nil return nil
}
func (fork *ChainFork) GetBlocksFromHash(hash common.Hash, size int) []*types.Block {
blocks := make([]*types.Block, 0, size)
max := int(math.Min(float64(size), float64(len(fork.changes))))
for i := 0; i < max; i++ {
blocks = append(blocks, fork.changes[len(fork.changes)-(i+1)].block)
}
// fetch the rest from the blockchain
if max < size {
blocks = append(blocks, fork.origin)
hash := fork.origin.ParentHash()
for i := max + 1; i < size; i++ {
block := GetBlock(fork.db, hash)
if block == nil {
break
}
blocks = append(blocks, block)
hash = block.ParentHash()
}
}
return blocks
}
func (fork *ChainFork) GetTd(hash common.Hash) *big.Int {
if len(fork.changes) == 0 {
if fork.origin.Hash() == hash {
return fork.reader.GetTd(hash)
}
return new(big.Int)
}
if idx, ok := fork.hashToIdx[hash]; ok {
return fork.changes[idx].td
}
return new(big.Int)
} }
@ -196,7 +236,7 @@ func (fork *ChainFork) CommitToDb() error {
} }
hash := change.block.Hash() hash := change.block.Hash()
if err := WriteHeader(tx, change.block.Header()); err != nil { if err := WriteBlock(tx, change.block); err != nil {
return err return err
} }
if err := WriteTd(tx, hash, change.td); err != nil { if err := WriteTd(tx, hash, change.td); err != nil {
@ -231,7 +271,11 @@ func (fork *ChainFork) ApplyTo(resolver ChainResolver) error {
if err != nil { if err != nil {
return err return err
} }
return resolver.Resolve(tx, fork.changes) err = resolver.Resolve(tx, fork.changes)
if err != nil {
return err
}
return tx.Commit()
} }
// NewUnsealedBlock creates a new unsealed block using the last block in the fork // NewUnsealedBlock creates a new unsealed block using the last block in the fork

View file

@ -20,8 +20,12 @@ func newFakeChain() fakeChain {
} }
} }
func (c fakeChain) Db() ethdb.Database { func (c fakeChain) GetTd(common.Hash) *big.Int {
return c.db return new(big.Int)
}
func (c fakeChain) GetBlocksFromHash(common.Hash, int) []*types.Block {
return nil
} }
func (c fakeChain) GetBlock(hash common.Hash) *types.Block { func (c fakeChain) GetBlock(hash common.Hash) *types.Block {
@ -33,7 +37,7 @@ func TestGetNumHash(t *testing.T) {
genesis := WriteGenesisBlockForTesting(chain.db) genesis := WriteGenesisBlockForTesting(chain.db)
config := &ChainConfig{HomesteadBlock: new(big.Int)} config := &ChainConfig{HomesteadBlock: new(big.Int)}
fork, err := Fork(config, chain, genesis.Hash()) fork, err := Fork(chain.db, config, chain, genesis.Hash(), 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -42,7 +42,7 @@ import (
// gas used. The implementer should decide what to do with the given input. // gas used. The implementer should decide what to do with the given input.
type Validator interface { type Validator interface {
HeaderValidator HeaderValidator
ValidateBlock(block *types.Block) error ValidateBlock(reader BlockReader, block *types.Block) error
ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error
} }