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
// 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 {
func (v *BlockValidator) ValidateBlock(reader BlockReader, block *types.Block) error {
/* wtf?
if reader.GetBlock(block.Hash()) != nil {
if _, err := state.New(block.Root(), v.chainDb); err == nil {
return &KnownBlockError{block.Number(), block.Hash()}
}
}
parent := v.bc.GetBlock(block.ParentHash())
*/
parent := reader.GetBlock(block.ParentHash())
if parent == nil {
return ParentError(block.ParentHash())
}
/* wtf?
if _, err := state.New(parent.Root(), v.bc.chainDb); err != nil {
return ParentError(block.ParentHash())
}
*/
header := block.Header()
// validate the block header
@ -86,7 +90,7 @@ func (v *BlockValidator) ValidateBlock(block *types.Block) error {
return err
}
// 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
}
@ -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
// 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 {
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
if len(block.Uncles()) > 2 {
if usize > 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) {
for _, ancestor := range reader.GetBlocksFromHash(block.ParentHash(), 7) {
ancestors[ancestor.Hash()] = ancestor
// Include ancestors uncles in the uncle set. Uncles must be unique.
for _, uncle := range ancestor.Uncles() {
@ -193,10 +202,12 @@ func (v *BlockValidator) ValidateHeader(header, parent *types.Header, checkPow b
if parent == nil {
return ParentError(header.ParentHash)
}
/* XXX i really don't like this here
// Short circuit if the header's already known or its parent missing
if v.bc.HasHeader(header.Hash()) {
return nil
}
*/
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))
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.
nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain)
@ -838,7 +842,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
break
}
bstart := time.Now()
//bstart := time.Now()
// Wait for block i's nonce to be verified before processing
// its state transition.
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
// interface.
err := self.Validator().ValidateBlock(block)
err := self.Validator().ValidateBlock(fork, block)
if err != nil {
if IsKnownBlockErr(err) {
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
// error if it fails.
if statedb == nil {
//statedb = fork.State()
statedb, err = state.New(self.GetBlock(block.ParentHash()).Root(), self.chainDb)
} else {
err = statedb.Reset(chain[i-1].Root())
@ -901,13 +906,13 @@ 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(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 {
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)
err = self.Validator().ValidateState(block, fork.GetBlock(block.ParentHash()), statedb, receipts, usedGas)
if err != nil {
reportBlock(block, err)
return i, err
@ -918,6 +923,12 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
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
coalescedLogs = append(coalescedLogs, logs...)
@ -960,8 +971,13 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
case SplitStatTy:
events = append(events, ChainSplitEvent{block, logs})
}
*/
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)) {
tend := time.Since(tstart)
@ -973,6 +989,21 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
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
// to be part of the new canonical chain and accumulates potential missing transactions and post an
// event about them

View file

@ -130,7 +130,7 @@ func printChain(bc *BlockChain) {
func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
for _, block := range chain {
// Try and process the block
err := blockchain.Validator().ValidateBlock(block)
err := blockchain.Validator().ValidateBlock(blockchain, block)
if err != nil {
if IsKnownBlockErr(err) {
continue
@ -430,7 +430,7 @@ func TestChainMultipleInsertions(t *testing.T) {
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) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error {
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.
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()...)
if err := db.Put(key, hash.Bytes()); err != nil {
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.
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 {
glog.Fatalf("failed to store last block's hash into database: %v", err)
return err

View file

@ -19,6 +19,7 @@ package core
import (
"errors"
"fmt"
"math"
"math/big"
"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
// handling reorganisations.
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.
@ -46,7 +47,8 @@ type Changes struct {
// BlockReader is the basic chain reader interface for reading blocks of the blockchain
type BlockReader interface {
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
@ -75,12 +77,13 @@ type ChainFork struct {
// 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) {
func Fork(db ethdb.Database, config *ChainConfig, blockReader BlockReader, origin common.Hash, size int) (*ChainFork, error) {
fork := &ChainFork{
db: blockReader.Db(),
db: db,
reader: blockReader,
config: config,
hashToIdx: make(map[common.Hash]int),
changes: make([]Changes, 0, size),
}
// 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 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()
if err := WriteHeader(tx, change.block.Header()); err != nil {
if err := WriteBlock(tx, change.block); err != nil {
return err
}
if err := WriteTd(tx, hash, change.td); err != nil {
@ -231,7 +271,11 @@ func (fork *ChainFork) ApplyTo(resolver ChainResolver) error {
if err != nil {
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

View file

@ -20,8 +20,12 @@ func newFakeChain() fakeChain {
}
}
func (c fakeChain) Db() ethdb.Database {
return c.db
func (c fakeChain) GetTd(common.Hash) *big.Int {
return new(big.Int)
}
func (c fakeChain) GetBlocksFromHash(common.Hash, int) []*types.Block {
return nil
}
func (c fakeChain) GetBlock(hash common.Hash) *types.Block {
@ -33,7 +37,7 @@ func TestGetNumHash(t *testing.T) {
genesis := WriteGenesisBlockForTesting(chain.db)
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 {
t.Fatal(err)
}

View file

@ -42,7 +42,7 @@ import (
// gas used. The implementer should decide what to do with the given input.
type Validator interface {
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
}