core: basic fork tests

This commit is contained in:
Jeffrey Wilcke 2016-05-30 13:47:13 +02:00
parent 25fddfb76f
commit 7d5e2443e2
3 changed files with 32 additions and 45 deletions

View file

@ -28,6 +28,8 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
) )
var errUnboundedParent = errors.New("core/fork: parent hash does not match last block") // unbounded parent
// 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 {
@ -57,41 +59,11 @@ type receipt struct {
// ChainFork is a temporary fork of the blockchain to which all block changes // ChainFork is a temporary fork of the blockchain to which all block changes
// must be applied before being written out to the database. // must be applied before being written out to the database.
//
// Example chain processing
//
// fork := Fork(chain, blocks[0].ParentHash)
// for _, block := range blocks {
// // State is an in-memory StateDB used throughout the fork
// err := ValidateBlock(block, fork.State())
// if err != nil {
// return err
// }
// fork.CommitBlock(block)
// }
// fork.ApplyTo(blockchain)
// fork.CommitToDb()
//
// Example block generation
//
// fork := Fork(chain, hash)
//
// // Create a new unsealed block
// block := fork.NewUnsealedBlock()
// // Apply transactions and uncles
// appliedTxs := block.ApplyTransactions(txpool.Transactions())
// appliedUncles := block.ApplyUncles(someUncles)
//
// // Seal the block, making it a valid sealed and signed block.
// block := Seal(powSealer, block)
//
// // Commit the block back to the fork.
// fork.CommitBlock(block)
//
type ChainFork struct { type ChainFork struct {
db ethdb.Database // backing database db ethdb.Database // backing database
reader BlockReader // block reader utility interface reader BlockReader // block reader utility interface
state *state.StateDB // the current state database state *state.StateDB // the current state database
config *ChainConfig // the chain configuration
originN uint64 // the origin block number originN uint64 // the origin block number
origin *types.Block // origin notes the start of the fork origin *types.Block // origin notes the start of the fork
@ -102,10 +74,11 @@ 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(blockReader BlockReader, origin common.Hash) (*ChainFork, error) { func Fork(config *ChainConfig, blockReader BlockReader, origin common.Hash) (*ChainFork, error) {
fork := &ChainFork{ fork := &ChainFork{
db: blockReader.Db(), db: blockReader.Db(),
reader: blockReader, reader: blockReader,
config: config,
} }
// get the origin block from which this fork originates // get the origin block from which this fork originates
@ -131,17 +104,19 @@ func (fork *ChainFork) GetNumHash(n uint64) common.Hash {
return common.Hash{} return common.Hash{}
} }
// check if we should have it cached // Check whether we should have it cached and retrieve it
// if present.
if n > fork.originN { if n > fork.originN {
return fork.changes[n-fork.originN].block.Hash() return fork.changes[n-(fork.originN+1)].block.Hash()
} }
// otherwise search in the database // Otherwise search in the database and retrieve it.
for block := fork.reader.GetBlock(fork.origin.Hash()); block != nil; block = fork.reader.GetBlock(block.ParentHash()) { for block := fork.reader.GetBlock(fork.origin.Hash()); block != nil; block = fork.reader.GetBlock(block.ParentHash()) {
if block.NumberU64() == n { if block.NumberU64() == n {
return block.Hash() return block.Hash()
} }
} }
// Returns empty hash indicating "not-found".
return common.Hash{} return common.Hash{}
} }
@ -149,8 +124,6 @@ func (fork *ChainFork) State() *state.StateDB {
return fork.state return fork.state
} }
var errUnboundedParent = errors.New("core/fork: parent hash does not match last block")
// CommitBlock commits a new block to the fork. The block that's being commited their parent hash must // CommitBlock commits a new block to the fork. The block that's being commited their parent hash must
// match the previously committed block or the origin if the fork is empty. // match the previously committed block or the origin if the fork is empty.
func (fork *ChainFork) CommitBlock(td *big.Int, block *types.Block, receipts types.Receipts) error { func (fork *ChainFork) CommitBlock(td *big.Int, block *types.Block, receipts types.Receipts) error {
@ -257,17 +230,17 @@ func (fork *ChainFork) NewUnsealedBlock(coinbase common.Address, extra []byte) *
tstamp = parent.Time.Int64() + 1 tstamp = parent.Time.Int64() + 1
} }
unsealedBlock.header = types.Header{ unsealedBlock.Block = types.NewBlockWithHeader(&types.Header{
Root: fork.state.IntermediateRoot(), Root: fork.state.IntermediateRoot(),
ParentHash: parent.Hash(), ParentHash: parent.Hash(),
Number: new(big.Int).Add(parent.Number, common.Big1), Number: new(big.Int).Add(parent.Number, common.Big1),
Difficulty: CalcDifficulty(nil, uint64(tstamp), parent.Time.Uint64(), parent.Number, parent.Difficulty), Difficulty: CalcDifficulty(fork.config, uint64(tstamp), parent.Time.Uint64(), parent.Number, parent.Difficulty),
GasLimit: CalcGasLimit(types.NewBlockWithHeader(parent)), GasLimit: CalcGasLimit(types.NewBlockWithHeader(parent)),
GasUsed: new(big.Int), GasUsed: new(big.Int),
Coinbase: coinbase, Coinbase: coinbase,
Extra: extra, Extra: extra,
Time: big.NewInt(tstamp), Time: big.NewInt(tstamp),
} })
return unsealedBlock return unsealedBlock
} }

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -30,8 +31,9 @@ func (c fakeChain) GetBlock(hash common.Hash) *types.Block {
func TestGetNumHash(t *testing.T) { func TestGetNumHash(t *testing.T) {
chain := newFakeChain() chain := newFakeChain()
genesis := WriteGenesisBlockForTesting(chain.db) genesis := WriteGenesisBlockForTesting(chain.db)
config := &ChainConfig{HomesteadBlock: new(big.Int)}
fork, err := Fork(chain, genesis.Hash()) fork, err := Fork(config, chain, genesis.Hash())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -44,4 +46,18 @@ func TestGetNumHash(t *testing.T) {
if fork.GetNumHash(1) != (common.Hash{}) { if fork.GetNumHash(1) != (common.Hash{}) {
t.Error("expected exmpty hash to be returned") t.Error("expected exmpty hash to be returned")
} }
const chainLength = 5
blocks := make([]*UnsealedBlock, chainLength)
for i := 0; i < chainLength; i++ {
unsealedBlock := fork.NewUnsealedBlock(common.Address{}, nil)
fork.CommitBlock(new(big.Int), unsealedBlock.Block, unsealedBlock.receipts)
blocks[i] = unsealedBlock
}
for i := uint64(0); i < chainLength; i++ {
if fork.GetNumHash(fork.originN+i+1) != blocks[i].Block.Hash() {
t.Errorf("%d failed: expected %x got %x", i, fork.GetNumHash(fork.originN+i+1), blocks[i].Block.Hash())
}
}
} }

View file

@ -5,10 +5,8 @@ import "github.com/ethereum/go-ethereum/core/types"
// UnsealedBlock is a not-yet finalised block to which you can // UnsealedBlock is a not-yet finalised block to which you can
// keep applying transactions until the gas-limit is met. // keep applying transactions until the gas-limit is met.
type UnsealedBlock struct { type UnsealedBlock struct {
header types.Header // block header *types.Block // embedded block
transactions types.Transactions // transactions previously applied
receipts types.Receipts // receipts generated by applying transactions receipts types.Receipts // receipts generated by applying transactions
uncles []types.Header // uncles previously applied
gasPool *GasPool // gas pool for this block gasPool *GasPool // gas pool for this block
} }