From 064a78507e419395627e21105014189649a018ca Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 30 May 2016 14:58:23 +0200 Subject: [PATCH] core: integrated fork procedure --- core/block_validator.go | 27 +++++++--- core/blockchain.go | 107 ++++++++++++++++++++++++++-------------- core/blockchain_test.go | 4 +- core/database_util.go | 4 +- core/fork.go | 56 ++++++++++++++++++--- core/fork_test.go | 10 ++-- core/types.go | 2 +- 7 files changed, 150 insertions(+), 60 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 801d2572b6..8af7ce61cf 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -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) } diff --git a/core/blockchain.go b/core/blockchain.go index ccae5b69b5..2ed0705e82 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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,50 +923,61 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { return i, err } - // coalesce logs for later processing - coalescedLogs = append(coalescedLogs, logs...) - - if err := WriteBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil { - return i, err - } - - txcount += len(block.Transactions()) - // write the block to the chain and get the status - status, err := self.WriteBlock(block) + err = fork.CommitBlock(new(big.Int).Add(fork.GetTd(block.ParentHash()), block.Difficulty()), block, receipts) if err != nil { return i, err } - switch status { - case CanonStatTy: - if glog.V(logger.Debug) { - glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) - } - events = append(events, ChainEvent{block, block.Hash(), logs}) + /* + // coalesce logs for later processing + coalescedLogs = append(coalescedLogs, logs...) - // This puts transactions in a extra db for rpc - if err := WriteTransactions(self.chainDb, block); err != nil { + if err := WriteBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil { return i, err } - // store the receipts - if err := WriteReceipts(self.chainDb, receipts); err != nil { - return i, err - } - // Write map map bloom filters - if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil { - return i, err - } - case SideStatTy: - if glog.V(logger.Detail) { - glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) - } - events = append(events, ChainSideEvent{block, logs}) - case SplitStatTy: - events = append(events, ChainSplitEvent{block, logs}) - } + txcount += len(block.Transactions()) + // write the block to the chain and get the status + status, err := self.WriteBlock(block) + if err != nil { + return i, err + } + + switch status { + case CanonStatTy: + if glog.V(logger.Debug) { + glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) + } + events = append(events, ChainEvent{block, block.Hash(), logs}) + + // This puts transactions in a extra db for rpc + if err := WriteTransactions(self.chainDb, block); err != nil { + return i, err + } + // store the receipts + if err := WriteReceipts(self.chainDb, receipts); err != nil { + return i, err + } + // Write map map bloom filters + if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil { + return i, err + } + case SideStatTy: + if glog.V(logger.Detail) { + glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) + } + events = append(events, ChainSideEvent{block, logs}) + + 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 diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 70e95494f6..3b63360a90 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -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 diff --git a/core/database_util.go b/core/database_util.go index 5f11ce15fd..28c53ea6bf 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -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 diff --git a/core/fork.go b/core/fork.go index eceda5403e..48e1b155c0 100644 --- a/core/fork.go +++ b/core/fork.go @@ -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 diff --git a/core/fork_test.go b/core/fork_test.go index a2bbe2f597..a708906771 100644 --- a/core/fork_test.go +++ b/core/fork_test.go @@ -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) } diff --git a/core/types.go b/core/types.go index 10038135d3..34fd8ca85b 100644 --- a/core/types.go +++ b/core/types.go @@ -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 }