From b448390889685fc87f221c152b16b82849f7797f Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Mon, 20 Apr 2015 18:14:57 +0200 Subject: [PATCH] Further fixes to block test wrapper * Move go test wrapper for block tests from cmd/geth to tests * Fix logic for when tests are valid or not, by adding correct validations for expected valid/invalid blocks * Change block insertion helper to work on single blocks * Add one test case for each file in BlockTests and comment out the tests which are currently failing * Add Skip call in all block tests in lieu of performance fixes around ethash cache which are needed before it will be fast enough to start / stop the node between each test --- cmd/geth/block_go_test.go | 80 ------------------------ cmd/geth/blocktest.go | 6 +- tests/block_test.go | 126 ++++++++++++++++++++++++++++++++++++++ tests/block_test_util.go | 103 +++++++++++++++++-------------- 4 files changed, 185 insertions(+), 130 deletions(-) delete mode 100644 cmd/geth/block_go_test.go create mode 100644 tests/block_test.go diff --git a/cmd/geth/block_go_test.go b/cmd/geth/block_go_test.go deleted file mode 100644 index 1980e4798e..0000000000 --- a/cmd/geth/block_go_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "path" - "testing" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/tests" -) - -// TODO: refactor test setup & execution to better align with vm and tx tests -// TODO: refactor to avoid duplication with cmd/geth/blocktest.go -func TestBcValidBlockTests(t *testing.T) { - runBlockTestsInFile("../../tests/files/BlockTests/bcValidBlockTest.json", t) -} - -/* -func TestBcUncleTests(t *testing.T) { - runBlockTestsInFile("../../tests/files/BlockTests/bcUncleTest.json", t) -} -*/ - -func runBlockTestsInFile(filepath string, t *testing.T) { - bt, err := tests.LoadBlockTests(filepath) - if err != nil { - t.Fatal(err) - } - for name, test := range bt { - runTest(name, test, t) - } -} - -func runTest(name string, test *tests.BlockTest, t *testing.T) { - t.Log("Running test: ", name) - cfg := testEthConfig() - ethereum, err := eth.New(cfg) - if err != nil { - t.Fatalf("%v", err) - } - - err = ethereum.Start() - if err != nil { - t.Fatalf("%v", err) - } - - // import the genesis block - ethereum.ResetWithGenesisBlock(test.Genesis) - - // import pre accounts - statedb, err := test.InsertPreState(ethereum.StateDb()) - if err != nil { - t.Fatalf("InsertPreState: %v", err) - } - - // insert the test blocks, which will execute all transactions - if err := test.InsertBlocks(ethereum.ChainManager()); err != nil { - t.Fatalf("Block Test load error: %v %T", err, err) - } - - if err := test.ValidatePostState(statedb); err != nil { - t.Fatal("post state validation failed: %v", err) - } - t.Log("Test passed: ", name) -} - -func testEthConfig() *eth.Config { - ks := crypto.NewKeyStorePassphrase(path.Join(common.DefaultDataDir(), "keys")) - - return ð.Config{ - DataDir: common.DefaultDataDir(), - LogLevel: 5, - Etherbase: "primary", - AccountManager: accounts.NewManager(ks), - NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }, - } -} diff --git a/cmd/geth/blocktest.go b/cmd/geth/blocktest.go index 792981ec04..343a0bf28c 100644 --- a/cmd/geth/blocktest.go +++ b/cmd/geth/blocktest.go @@ -109,10 +109,10 @@ func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, er return ethereum, fmt.Errorf("InsertPreState: %v", err) } - // insert the test blocks, which will execute all transactions - if err := test.InsertBlocks(ethereum.ChainManager()); err != nil { - return ethereum, fmt.Errorf("Block Test load error: %v %T", err, err) + if err := test.TryBlocksInsert(ethereum.ChainManager()); err != nil { + return ethereum, fmt.Errorf("Block Test load error: %v", err) } + fmt.Println("chain loaded") if err := test.ValidatePostState(statedb); err != nil { return ethereum, fmt.Errorf("post state validation failed: %v", err) diff --git a/tests/block_test.go b/tests/block_test.go new file mode 100644 index 0000000000..bffaf50daf --- /dev/null +++ b/tests/block_test.go @@ -0,0 +1,126 @@ +package tests + +import ( + "path" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethdb" +) + +// TODO: refactor test setup & execution to better align with vm and tx tests +func TestBcValidBlockTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcValidBlockTest.json", []string{}, t) +} + +func TestBcUncleTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcUncleTest.json", []string{}, t) +} + +func TestBcUncleHeaderValidityTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcUncleHeaderValiditiy.json", []string{}, t) +} + +func TestBcInvalidHeaderTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + snafus := []string{ + "wrongUncleHash", // TODO: why does this fail? + } + runBlockTestsInFile("files/BlockTests/bcInvalidHeaderTest.json", snafus, t) +} + +func TestBcInvalidRLPTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + snafus := []string{ + // TODO: why does these fail? + "TRANSCT__ZeroByteAtTheEnd", + "TRANSCT__RandomByteAtTheEnd", + "BLOCK__ZeroByteAtTheEnd", + "BLOCK__RandomByteAtTheEnd", + } + runBlockTestsInFile("files/BlockTests/bcInvalidRLPTest.json", snafus, t) +} + +func TestBcJSAPITests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcJS_API_Test.json", []string{}, t) +} + +func TestBcRPCAPITests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcRPC_API_Test.json", []string{}, t) +} + +func TestBcForkBlockTests(t *testing.T) { + t.Skip("Skipped in lieu of performance fixes.") + runBlockTestsInFile("files/BlockTests/bcForkBlockTest.json", []string{}, t) +} + +func runBlockTestsInFile(filepath string, snafus []string, t *testing.T) { + bt, err := LoadBlockTests(filepath) + if err != nil { + t.Fatal(err) + } + + notWorking := make(map[string]bool, 100) + for _, name := range snafus { + notWorking[name] = true + } + + for name, test := range bt { + if !notWorking[name] { + runBlockTest(name, test, t) + } + } +} + +func runBlockTest(name string, test *BlockTest, t *testing.T) { + t.Log("Running test: ", name) + cfg := testEthConfig() + ethereum, err := eth.New(cfg) + if err != nil { + t.Fatalf("%v", err) + } + + err = ethereum.Start() + if err != nil { + t.Fatalf("%v", err) + } + + // import the genesis block + ethereum.ResetWithGenesisBlock(test.Genesis) + + // import pre accounts + statedb, err := test.InsertPreState(ethereum.StateDb()) + if err != nil { + t.Fatalf("InsertPreState: %v", err) + } + + err = test.TryBlocksInsert(ethereum.ChainManager()) + if err != nil { + t.Fatal(err) + } + + if err = test.ValidatePostState(statedb); err != nil { + t.Fatal("post state validation failed: %v", err) + } + t.Log("Test passed: ", name) +} + +func testEthConfig() *eth.Config { + ks := crypto.NewKeyStorePassphrase(path.Join(common.DefaultDataDir(), "keys")) + + return ð.Config{ + DataDir: common.DefaultDataDir(), + LogLevel: 5, + Etherbase: "primary", + AccountManager: accounts.NewManager(ks), + NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }, + } +} diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 5c42ae18c9..ed2a9fca24 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -19,6 +19,13 @@ import ( ) // Block Test JSON Format +type BlockTest struct { + Genesis *types.Block + + Json *btJSON + preAccounts map[string]btAccount +} + type btJSON struct { Blocks []btBlock GenesisBlockHeader btHeader @@ -26,6 +33,13 @@ type btJSON struct { PostState map[string]btAccount } +type btBlock struct { + BlockHeader *btHeader + Rlp string + Transactions []btTransaction + UncleHeaders []*btHeader +} + type btAccount struct { Balance string Code string @@ -65,20 +79,6 @@ type btTransaction struct { Value string } -type btBlock struct { - BlockHeader *btHeader - Rlp string - Transactions []btTransaction - UncleHeaders []*btHeader -} - -type BlockTest struct { - Genesis *types.Block - - json *btJSON - preAccounts map[string]btAccount -} - // LoadBlockTests loads a block test JSON file. func LoadBlockTests(file string) (map[string]*BlockTest, error) { bt := make(map[string]*btJSON) @@ -125,13 +125,43 @@ func (t *BlockTest) InsertPreState(db common.Database) (*state.StateDB, error) { return statedb, nil } -// InsertBlocks loads the test's blocks into the given chain. -func (t *BlockTest) InsertBlocks(chain *core.ChainManager) error { - blocks, err := t.convertBlocks() - if err != nil { - return err +/* See https://github.com/ethereum/tests/wiki/Blockchain-Tests-II + + Whether a block is valid or not is a bit subtle, it's defined by presence of + blockHeader, transactions and uncleHeaders fields. If they are missing, the block is + invalid and we must verify that we do not accept it. + + Since some tests mix valid and invalid blocks we need to check this for every block. + + If a block is invalid it does not necessarily fail the test, if it's invalidness is + expected we are expected to ignore it and continue processing and then validate the + post state. +*/ +func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error { + // insert the test blocks, which will execute all transactions + for _, b := range t.Json.Blocks { + cb, err := mustConvertBlock(b) + if err != nil { + 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: ", err) + } + } + // RLP decoding worked, try to insert into chain: + err = chainManager.InsertChain(types.Blocks{cb}) + if err != nil { + 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: ", err) + } + } + if b.BlockHeader == nil { + return fmt.Errorf("Block insertion should have failed") + } } - return chain.InsertChain(blocks) + return nil } func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error { @@ -159,21 +189,6 @@ func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error { return nil } -func (t *BlockTest) convertBlocks() (blocks []*types.Block, err error) { - // the conversion handles errors by catching panics. - // you might consider this ugly, but the alternative (passing errors) - // would be much harder to read. - defer func() { - if recovered := recover(); recovered != nil { - buf := make([]byte, 64<<10) - buf = buf[:runtime.Stack(buf, false)] - err = fmt.Errorf("%v\n%s", recovered, buf) - } - }() - blocks = mustConvertBlocks(t.json.Blocks) - return blocks, nil -} - func convertTest(in *btJSON) (out *BlockTest, err error) { // the conversion handles errors by catching panics. // you might consider this ugly, but the alternative (passing errors) @@ -185,7 +200,7 @@ func convertTest(in *btJSON) (out *BlockTest, err error) { err = fmt.Errorf("%v\n%s", recovered, buf) } }() - out = &BlockTest{preAccounts: in.Pre, json: in} + out = &BlockTest{preAccounts: in.Pre, Json: in} out.Genesis = mustConvertGenesis(in.GenesisBlockHeader) return out, err } @@ -221,17 +236,11 @@ func mustConvertHeader(in btHeader) *types.Header { return header } -func mustConvertBlocks(testBlocks []btBlock) []*types.Block { - var out []*types.Block - for i, inb := range testBlocks { - var b types.Block - r := bytes.NewReader(mustConvertBytes(inb.Rlp)) - if err := rlp.Decode(r, &b); err != nil { - panic(fmt.Errorf("invalid block %d: %q\nerror: %v", i, inb.Rlp, err)) - } - out = append(out, &b) - } - return out +func mustConvertBlock(testBlock btBlock) (*types.Block, error) { + var b types.Block + r := bytes.NewReader(mustConvertBytes(testBlock.Rlp)) + err := rlp.Decode(r, &b) + return &b, err } func mustConvertBytes(in string) []byte {