tests: use lastblockhash field to validate reorgs and block headers

This commit is contained in:
Gustav Simonsson 2015-09-18 17:15:59 +02:00
parent 075815e5ff
commit 47ca6904b3
2 changed files with 58 additions and 48 deletions

@ -111,25 +111,27 @@ func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, er
if err != nil {
return nil, err
}
// if err := ethereum.Start(); err != nil {
// return nil, err
// }
// import the genesis block
ethereum.ResetWithGenesisBlock(test.Genesis)
// import pre accounts
statedb, err := test.InsertPreState(ethereum)
_, err = test.InsertPreState(ethereum)
if err != nil {
return ethereum, fmt.Errorf("InsertPreState: %v", err)
}
if err := test.TryBlocksInsert(ethereum.ChainManager()); err != nil {
cm := ethereum.ChainManager()
validBlocks, err := test.TryBlocksInsert(cm)
if err != nil {
return ethereum, fmt.Errorf("Block Test load error: %v", err)
}
if err := test.ValidatePostState(statedb); err != nil {
newDB := cm.State()
if err := test.ValidatePostState(newDB); err != nil {
return ethereum, fmt.Errorf("post state validation failed: %v", err)
}
return ethereum, nil
return ethereum, test.ValidateImportedHeaders(cm, validBlocks)
}

@ -44,9 +44,10 @@ import (
type BlockTest struct {
Genesis *types.Block
Json *btJSON
preAccounts map[string]btAccount
postAccounts map[string]btAccount
Json *btJSON
preAccounts map[string]btAccount
postAccounts map[string]btAccount
lastblockhash string
}
type btJSON struct {
@ -54,6 +55,7 @@ type btJSON struct {
GenesisBlockHeader btHeader
Pre map[string]btAccount
PostState map[string]btAccount
Lastblockhash string
}
type btBlock struct {
@ -77,6 +79,7 @@ type btHeader struct {
MixHash string
Nonce string
Number string
Hash string
ParentHash string
ReceiptTrie string
SeedHash string
@ -178,16 +181,24 @@ func runBlockTest(test *BlockTest) error {
return fmt.Errorf("InsertPreState: %v", err)
}
err = test.TryBlocksInsert(ethereum.ChainManager())
cm := ethereum.ChainManager()
validBlocks, err := test.TryBlocksInsert(cm)
if err != nil {
return err
}
newDB := ethereum.ChainManager().State()
lastblockhash := common.HexToHash(test.lastblockhash)
cmlast := cm.LastBlockHash()
if lastblockhash != cmlast {
return fmt.Errorf("lastblockhash validation mismatch: want: %x, have: %x", lastblockhash, cmlast)
}
newDB := cm.State()
if err = test.ValidatePostState(newDB); err != nil {
return fmt.Errorf("post state validation failed: %v", err)
}
return nil
return test.ValidateImportedHeaders(cm, validBlocks)
}
func (test *BlockTest) makeEthConfig() *eth.Config {
@ -265,8 +276,8 @@ func (t *BlockTest) InsertPreState(ethereum *eth.Ethereum) (*state.StateDB, erro
expected we are expected to ignore it and continue processing and then validate the
post state.
*/
func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
blockNums := make(map[string]bool)
func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) ([]btBlock, error) {
validBlocks := make([]btBlock, 0)
// insert the test blocks, which will execute all transactions
for _, b := range t.Json.Blocks {
cb, err := mustConvertBlock(b)
@ -274,7 +285,7 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
if b.BlockHeader == nil {
continue // OK - block is supposed to be invalid, continue with next block
} else {
return fmt.Errorf("Block RLP decoding failed when expected to succeed: %v", err)
return nil, fmt.Errorf("Block RLP decoding failed when expected to succeed: %v", err)
}
}
// RLP decoding worked, try to insert into chain:
@ -283,47 +294,23 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
if b.BlockHeader == nil {
continue // OK - block is supposed to be invalid, continue with next block
} else {
return fmt.Errorf("Block insertion into chain failed: %v", err)
return nil, fmt.Errorf("Block insertion into chain failed: %v", err)
}
}
if b.BlockHeader == nil {
return fmt.Errorf("Block insertion should have failed")
return nil, fmt.Errorf("Block insertion should have failed")
}
// validate RLP decoding by checking all values against test file JSON
if err = t.validateBlockHeader(b.BlockHeader, cb.Header()); err != nil {
return fmt.Errorf("Deserialised block header validation failed: %v", err)
}
// validate the imported header against test file JSON
/*
TODO: currently test files do not contain information on what
reorg is expected other than possibly the post state (which may
or may not depend on a specific chain).
discussed with winswega and it was agreed to add this information
to the test files explicitly.
meanwhile we skip header validation on blocks with the same block
number as a prior block, since this test code cannot know what
blocks are in the longest chain without making use of the very
protocol rules the tests verify or rely on the correctness of the
code that is being tested.
*/
if !blockNums[b.BlockHeader.Number] {
importedBlock := chainManager.CurrentBlock()
if err = t.validateBlockHeader(b.BlockHeader, importedBlock.Header()); err != nil {
return fmt.Errorf("Imported block header validation failed: %v", err)
}
blockNums[b.BlockHeader.Number] = true
if err = validateHeader(b.BlockHeader, cb.Header()); err != nil {
return nil, fmt.Errorf("Deserialised block header validation failed: %v", err)
}
validBlocks = append(validBlocks, b)
}
return nil
return validBlocks, nil
}
func (s *BlockTest) validateBlockHeader(h *btHeader, h2 *types.Header) error {
func validateHeader(h *btHeader, h2 *types.Header) error {
expectedBloom := mustConvertBytes(h.Bloom)
if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) {
return fmt.Errorf("Bloom: want: %x have: %x", expectedBloom, h2.Bloom.Bytes())
@ -439,6 +426,27 @@ func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error {
return nil
}
func (test *BlockTest) ValidateImportedHeaders(cm *core.ChainManager, validBlocks []btBlock) error {
// to get constant lookup when verifying block headers by hash (some tests have many blocks)
bmap := make(map[string]btBlock, len(test.Json.Blocks))
for _, b := range validBlocks {
bmap[b.BlockHeader.Hash] = b
}
// iterate over blocks backwards from HEAD and validate imported
// headers vs test file. some tests have reorgs, and we import
// block-by-block, so we can only validate imported headers after
// all blocks have been processed by ChainManager, as they may not
// be part of the longest chain until last block is imported.
for b := cm.CurrentBlock(); b != nil && b.NumberU64() != 0; b = cm.GetBlock(b.Header().ParentHash) {
bHash := common.Bytes2Hex(b.Hash().Bytes()) // hex without 0x prefix
if err := validateHeader(bmap[bHash].BlockHeader, b.Header()); err != nil {
return fmt.Errorf("Imported block header validation failed: %v", err)
}
}
return nil
}
func convertBlockTests(in map[string]*btJSON) (map[string]*BlockTest, error) {
out := make(map[string]*BlockTest)
for name, test := range in {
@ -461,7 +469,7 @@ func convertBlockTest(in *btJSON) (out *BlockTest, err error) {
err = fmt.Errorf("%v\n%s", recovered, buf)
}
}()
out = &BlockTest{preAccounts: in.Pre, postAccounts: in.PostState, Json: in}
out = &BlockTest{preAccounts: in.Pre, postAccounts: in.PostState, Json: in, lastblockhash: in.Lastblockhash}
out.Genesis = mustConvertGenesis(in.GenesisBlockHeader)
return out, err
}