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

View File

@ -111,25 +111,27 @@ func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if err := ethereum.Start(); err != nil {
// return nil, err
// }
// import the genesis block // import the genesis block
ethereum.ResetWithGenesisBlock(test.Genesis) ethereum.ResetWithGenesisBlock(test.Genesis)
// import pre accounts // import pre accounts
statedb, err := test.InsertPreState(ethereum) _, err = test.InsertPreState(ethereum)
if err != nil { if err != nil {
return ethereum, fmt.Errorf("InsertPreState: %v", err) 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) 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, fmt.Errorf("post state validation failed: %v", err)
} }
return ethereum, nil
return ethereum, test.ValidateImportedHeaders(cm, validBlocks)
} }

View File

@ -44,9 +44,10 @@ import (
type BlockTest struct { type BlockTest struct {
Genesis *types.Block Genesis *types.Block
Json *btJSON Json *btJSON
preAccounts map[string]btAccount preAccounts map[string]btAccount
postAccounts map[string]btAccount postAccounts map[string]btAccount
lastblockhash string
} }
type btJSON struct { type btJSON struct {
@ -54,6 +55,7 @@ type btJSON struct {
GenesisBlockHeader btHeader GenesisBlockHeader btHeader
Pre map[string]btAccount Pre map[string]btAccount
PostState map[string]btAccount PostState map[string]btAccount
Lastblockhash string
} }
type btBlock struct { type btBlock struct {
@ -77,6 +79,7 @@ type btHeader struct {
MixHash string MixHash string
Nonce string Nonce string
Number string Number string
Hash string
ParentHash string ParentHash string
ReceiptTrie string ReceiptTrie string
SeedHash string SeedHash string
@ -178,16 +181,24 @@ func runBlockTest(test *BlockTest) error {
return fmt.Errorf("InsertPreState: %v", err) return fmt.Errorf("InsertPreState: %v", err)
} }
err = test.TryBlocksInsert(ethereum.ChainManager()) cm := ethereum.ChainManager()
validBlocks, err := test.TryBlocksInsert(cm)
if err != nil { if err != nil {
return err 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 { if err = test.ValidatePostState(newDB); err != nil {
return fmt.Errorf("post state validation failed: %v", err) return fmt.Errorf("post state validation failed: %v", err)
} }
return nil
return test.ValidateImportedHeaders(cm, validBlocks)
} }
func (test *BlockTest) makeEthConfig() *eth.Config { 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 expected we are expected to ignore it and continue processing and then validate the
post state. post state.
*/ */
func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error { func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) ([]btBlock, error) {
blockNums := make(map[string]bool) validBlocks := make([]btBlock, 0)
// insert the test blocks, which will execute all transactions // insert the test blocks, which will execute all transactions
for _, b := range t.Json.Blocks { for _, b := range t.Json.Blocks {
cb, err := mustConvertBlock(b) cb, err := mustConvertBlock(b)
@ -274,7 +285,7 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
if b.BlockHeader == nil { if b.BlockHeader == nil {
continue // OK - block is supposed to be invalid, continue with next block continue // OK - block is supposed to be invalid, continue with next block
} else { } 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: // RLP decoding worked, try to insert into chain:
@ -283,47 +294,23 @@ func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
if b.BlockHeader == nil { if b.BlockHeader == nil {
continue // OK - block is supposed to be invalid, continue with next block continue // OK - block is supposed to be invalid, continue with next block
} else { } 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 { 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 // validate RLP decoding by checking all values against test file JSON
if err = t.validateBlockHeader(b.BlockHeader, cb.Header()); err != nil { if err = validateHeader(b.BlockHeader, cb.Header()); err != nil {
return fmt.Errorf("Deserialised block header validation failed: %v", err) return nil, 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
} }
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) expectedBloom := mustConvertBytes(h.Bloom)
if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) { if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) {
return fmt.Errorf("Bloom: want: %x have: %x", 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 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) { func convertBlockTests(in map[string]*btJSON) (map[string]*BlockTest, error) {
out := make(map[string]*BlockTest) out := make(map[string]*BlockTest)
for name, test := range in { 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) 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) out.Genesis = mustConvertGenesis(in.GenesisBlockHeader)
return out, err return out, err
} }