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"
)
var errUnboundedParent = errors.New("core/fork: parent hash does not match last block") // unbounded parent
// ChainResolver should implement chain resolving and should be capable
// handling reorganisations.
type ChainResolver interface {
@ -57,41 +59,11 @@ type receipt struct {
// ChainFork is a temporary fork of the blockchain to which all block changes
// 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 {
db ethdb.Database // backing database
reader BlockReader // block reader utility interface
state *state.StateDB // the current state database
config *ChainConfig // the chain configuration
originN uint64 // the origin block number
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
// 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{
db: blockReader.Db(),
reader: blockReader,
config: config,
}
// get the origin block from which this fork originates
@ -131,17 +104,19 @@ func (fork *ChainFork) GetNumHash(n uint64) 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 {
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()) {
if block.NumberU64() == n {
return block.Hash()
}
}
// Returns empty hash indicating "not-found".
return common.Hash{}
}
@ -149,8 +124,6 @@ func (fork *ChainFork) State() *state.StateDB {
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
// 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 {
@ -257,17 +230,17 @@ func (fork *ChainFork) NewUnsealedBlock(coinbase common.Address, extra []byte) *
tstamp = parent.Time.Int64() + 1
}
unsealedBlock.header = types.Header{
unsealedBlock.Block = types.NewBlockWithHeader(&types.Header{
Root: fork.state.IntermediateRoot(),
ParentHash: parent.Hash(),
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)),
GasUsed: new(big.Int),
Coinbase: coinbase,
Extra: extra,
Time: big.NewInt(tstamp),
}
})
return unsealedBlock
}

View file

@ -1,6 +1,7 @@
package core
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
@ -30,8 +31,9 @@ func (c fakeChain) GetBlock(hash common.Hash) *types.Block {
func TestGetNumHash(t *testing.T) {
chain := newFakeChain()
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 {
t.Fatal(err)
}
@ -44,4 +46,18 @@ func TestGetNumHash(t *testing.T) {
if fork.GetNumHash(1) != (common.Hash{}) {
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
// keep applying transactions until the gas-limit is met.
type UnsealedBlock struct {
header types.Header // block header
transactions types.Transactions // transactions previously applied
receipts types.Receipts // receipts generated by applying transactions
uncles []types.Header // uncles previously applied
*types.Block // embedded block
receipts types.Receipts // receipts generated by applying transactions
gasPool *GasPool // gas pool for this block
}