Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add blocktests as go tests #750

Closed
10 changes: 7 additions & 3 deletions blockpool/blockpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,10 @@ type BlockPool struct {

// waitgroup is used in tests to wait for result-critical routines
// as well as in determining idle / syncing status
wg sync.WaitGroup //
quit chan bool // chan used for quitting parallel routines
running bool //
wg sync.WaitGroup //
quit chan bool // chan used for quitting parallel routines
running bool //
evloopdone chan struct{}
}

// public constructor
Expand All @@ -204,6 +205,7 @@ func New(
verifyPoW: verifyPoW,
chainEvents: chainEvents,
td: td,
evloopdone: make(chan struct{}),
}
}

Expand Down Expand Up @@ -244,6 +246,7 @@ func (self *BlockPool) Start() {
// status update interval
timer := time.NewTicker(self.Config.StatusUpdateInterval)
go func() {
defer close(self.evloopdone)
for {
select {
case <-self.quit:
Expand Down Expand Up @@ -290,6 +293,7 @@ func (self *BlockPool) Stop() {

self.tdSub.Unsubscribe()
close(self.quit)
<-self.evloopdone

self.lock.Lock()
self.peers = nil
Expand Down
89 changes: 65 additions & 24 deletions cmd/geth/blocktest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"os"

"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils"
Expand All @@ -12,7 +13,7 @@ import (
)

var blocktestCmd = cli.Command{
Action: runblocktest,
Action: runBlockTest,
Name: "blocktest",
Usage: `loads a block test file`,
Description: `
Expand All @@ -25,27 +26,78 @@ be able to interact with the chain defined by the test.
`,
}

func runblocktest(ctx *cli.Context) {
if len(ctx.Args()) != 3 {
utils.Fatalf("Usage: ethereum blocktest <path-to-test-file> <test-name> {rpc, norpc}")
func runBlockTest(ctx *cli.Context) {
var (
file, testname string
rpc bool
)
args := ctx.Args()
switch {
case len(args) == 1:
file = args[0]
case len(args) == 2:
file, testname = args[0], args[1]
case len(args) == 3:
file, testname = args[0], args[1]
rpc = true
default:
utils.Fatalf(`Usage: ethereum blocktest <path-to-test-file> [ <test-name> [ "rpc" ] ]`)
}
file, testname, startrpc := ctx.Args()[0], ctx.Args()[1], ctx.Args()[2]

bt, err := tests.LoadBlockTests(file)
if err != nil {
utils.Fatalf("%v", err)
}

// run all tests if no test name is specified
if testname == "" {
ecode := 0
for name, test := range bt {
fmt.Printf("----------------- Running Block Test %q\n", name)
ethereum, err := runOneBlockTest(ctx, test)
if err != nil {
fmt.Println(err)
fmt.Println("FAIL")
ecode = 1
}
if ethereum != nil {
ethereum.Stop()
ethereum.WaitForShutdown()
}
}
os.Exit(ecode)
return
}
// otherwise, run the given test
test, ok := bt[testname]
if !ok {
utils.Fatalf("Test file does not contain test named %q", testname)
}
ethereum, err := runOneBlockTest(ctx, test)
if err != nil {
utils.Fatalf("%v", err)
}
defer ethereum.Stop()
if rpc {
fmt.Println("Block Test post state validated, starting RPC interface.")
startEth(ctx, ethereum)
utils.StartRPC(ethereum, ctx)
ethereum.WaitForShutdown()
}
}

func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, error) {
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
cfg.NewDB = func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }
cfg.MaxPeers = 0 // disable network
cfg.Shh = false // disable whisper
cfg.NAT = nil // disable port mapping

ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v", err)
return nil, err
}
if err := ethereum.Start(); err != nil {
return nil, err
}

// import the genesis block
Expand All @@ -54,27 +106,16 @@ func runblocktest(ctx *cli.Context) {
// import pre accounts
statedb, err := test.InsertPreState(ethereum.StateDb())
if err != nil {
utils.Fatalf("could not insert genesis accounts: %v", err)
return ethereum, fmt.Errorf("InsertPreState: %v", err)
}

// insert the test blocks, which will execute all transactions
chain := ethereum.ChainManager()
if err := chain.InsertChain(test.Blocks); err != nil {
utils.Fatalf("Block Test load error: %v %T", err, err)
} else {
fmt.Println("Block Test chain loaded")
if err := test.InsertBlocks(ethereum.ChainManager()); err != nil {
return ethereum, fmt.Errorf("Block Test load error: %v %T", err, err)
}

fmt.Println("chain loaded")
if err := test.ValidatePostState(statedb); err != nil {
utils.Fatalf("post state validation failed: %v", err)
}
fmt.Println("Block Test post state validated, starting ethereum.")

if startrpc == "rpc" {
startEth(ctx, ethereum)
utils.StartRPC(ethereum, ctx)
ethereum.WaitForShutdown()
} else {
startEth(ctx, ethereum)
return ethereum, fmt.Errorf("post state validation failed: %v", err)
}
return ethereum, nil
}
6 changes: 6 additions & 0 deletions core/chain_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type ChainManager struct {
futureBlocks *BlockCache

quit chan struct{}
wg sync.WaitGroup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, these sort of changes need to be well documented. Second I suggest you find some other way to solve this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made these changes while working out a crash in the tests together with Gustav. The blockpool was crashing because it didn't expect to be started and stopped in short succession.

While looking at the source of the events, I found that ChainManager now has a background goroutine which is never stopped. The changes ensure that it is stopped, and also that stopping blocks until the loop is down.

Second I suggest you find some other way to solve this.

sync.WaitGroup is the canonical way to wait for one or more goroutines to end. But it could also be done with a channel, if you prefer that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, the issue doesn't really exist anymore because the blockpool is no longer used. It would still be preferable to fix ChainManager so it exits properly.

@Gustav-Simonsson maybe you could strip those commits (changes to blockpool and chain manager) from the PR. I'll submit a separate PR with the change to core.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is fine with a little context :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing, will cherry pick the other commits from @fjl branch then to leave this out for a separate PR.

}

func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *ChainManager {
Expand All @@ -104,6 +105,7 @@ func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *Chai
bc.futureBlocks = NewBlockCache(254)
bc.makeCache()

bc.wg.Add(1)
go bc.update()

return bc
Expand Down Expand Up @@ -428,6 +430,7 @@ func (self *ChainManager) CalcTotalDiff(block *types.Block) (*big.Int, error) {

func (bc *ChainManager) Stop() {
close(bc.quit)
bc.wg.Wait()
}

type queueEvent struct {
Expand Down Expand Up @@ -593,6 +596,9 @@ func (self *ChainManager) merge(oldBlock, newBlock *types.Block) {
func (self *ChainManager) update() {
events := self.eventMux.Subscribe(queueEvent{})
futureTimer := time.NewTicker(5 * time.Second)
defer self.wg.Done()
defer futureTimer.Stop()

out:
for {
select {
Expand Down
8 changes: 5 additions & 3 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ func New(config *Config) (*Ethereum, error) {
if err != nil {
return nil, err
}
extraDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "extra"))
extraDb, err := newdb(path.Join(config.DataDir, "extra"))
if err != nil {
return nil, err
}

// Perform database sanity checks
d, _ := blockDb.Get([]byte("ProtocolVersion"))
Expand Down Expand Up @@ -213,8 +216,6 @@ func New(config *Config) (*Ethereum, error) {
eth.txPool = core.NewTxPool(eth.EventMux(), eth.chainManager.State)
eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux())
eth.chainManager.SetProcessor(eth.blockProcessor)
eth.whisper = whisper.New()
eth.shhVersionId = int(eth.whisper.Version())
eth.miner = miner.New(eth, eth.pow, config.MinerThreads)
eth.protocolManager = NewProtocolManager(config.ProtocolVersion, config.NetworkId, eth.txPool, eth.chainManager, eth.downloader)

Expand Down Expand Up @@ -419,6 +420,7 @@ func (s *Ethereum) Stop() {
s.txSub.Unsubscribe() // quits txBroadcastLoop
s.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop

s.chainManager.Stop()
s.txPool.Stop()
s.eventMux.Stop()
if s.whisper != nil {
Expand Down
78 changes: 78 additions & 0 deletions tests/block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tests

// TODO: figure out how to move this file to tests package and get imports working there
import (
// "os"
"path"

// TODO: refactor to avoid depending on CLI stuff
"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"
"testing"
)

// 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) {
//dir, _ := os.Getwd()
//t.Logf("CWD: ", dir)
runBlockTestsInFile("files/BlockTests/bcValidBlockTest.json", t)
}

func runBlockTestsInFile(filepath string, t *testing.T) {
bt, err := LoadBlockTests(filepath)
if err != nil {
t.Fatal(err)
}
for _, test := range bt {
runBlockTest(test, t)
}
}

func runBlockTest(test *BlockTest, t *testing.T) {
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)
}
t.Log("chain loaded")

if err := test.ValidatePostState(statedb); err != nil {
t.Fatal("post state validation failed: %v", err)
}
t.Log("Block Test post state validated.")
}

func testEthConfig() *eth.Config {
ks := crypto.NewKeyStorePassphrase(path.Join(common.DefaultDataDir(), "keys"))

return &eth.Config{
DataDir: common.DefaultDataDir(),
LogLevel: 5,
Etherbase: "primary",
AccountManager: accounts.NewManager(ks),
NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() },
}
}
Loading