diff --git a/blockpool/blockpool.go b/blockpool/blockpool.go index a60b6f43c9..da03431f81 100644 --- a/blockpool/blockpool.go +++ b/blockpool/blockpool.go @@ -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 @@ -204,6 +205,7 @@ func New( verifyPoW: verifyPoW, chainEvents: chainEvents, td: td, + evloopdone: make(chan struct{}), } } @@ -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: @@ -290,6 +293,7 @@ func (self *BlockPool) Stop() { self.tdSub.Unsubscribe() close(self.quit) + <-self.evloopdone self.lock.Lock() self.peers = nil diff --git a/cmd/geth/blocktest.go b/cmd/geth/blocktest.go index f0b6bb1a21..792981ec04 100644 --- a/cmd/geth/blocktest.go +++ b/cmd/geth/blocktest.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" @@ -12,7 +13,7 @@ import ( ) var blocktestCmd = cli.Command{ - Action: runblocktest, + Action: runBlockTest, Name: "blocktest", Usage: `loads a block test file`, Description: ` @@ -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 {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 [ [ "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 @@ -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 } diff --git a/core/chain_manager.go b/core/chain_manager.go index 4f1e1e68a9..8c75b5fa74 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -92,6 +92,7 @@ type ChainManager struct { futureBlocks *BlockCache quit chan struct{} + wg sync.WaitGroup } func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *ChainManager { @@ -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 @@ -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 { @@ -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 { diff --git a/eth/backend.go b/eth/backend.go index 264753aba8..669247de4a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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")) @@ -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) @@ -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 { diff --git a/tests/blocktest.go b/tests/blocktest.go index 2d6b119442..d0a9103361 100644 --- a/tests/blocktest.go +++ b/tests/blocktest.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" @@ -73,8 +74,8 @@ type btBlock struct { type BlockTest struct { Genesis *types.Block - Blocks []*types.Block + json *btJSON preAccounts map[string]btAccount } @@ -88,7 +89,7 @@ func LoadBlockTests(file string) (map[string]*BlockTest, error) { for name, in := range bt { var err error if out[name], err = convertTest(in); err != nil { - return nil, fmt.Errorf("bad test %q: %v", err) + return out, fmt.Errorf("bad test %q: %v", name, err) } } return out, nil @@ -124,6 +125,15 @@ 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 + } + return chain.InsertChain(blocks) +} + func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error { for addrString, acct := range t.preAccounts { // XXX: is is worth it checking for errors here? @@ -149,6 +159,21 @@ 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) @@ -160,9 +185,8 @@ func convertTest(in *btJSON) (out *BlockTest, err error) { err = fmt.Errorf("%v\n%s", recovered, buf) } }() - out = &BlockTest{preAccounts: in.Pre} + out = &BlockTest{preAccounts: in.Pre, json: in} out.Genesis = mustConvertGenesis(in.GenesisBlockHeader) - out.Blocks = mustConvertBlocks(in.Blocks) return out, err } @@ -187,13 +211,13 @@ func mustConvertHeader(in btHeader) *types.Header { UncleHash: mustConvertHash(in.UncleHash), ParentHash: mustConvertHash(in.ParentHash), Extra: mustConvertBytes(in.ExtraData), - GasUsed: mustConvertBigInt(in.GasUsed), - GasLimit: mustConvertBigInt(in.GasLimit), - Difficulty: mustConvertBigInt(in.Difficulty), - Time: mustConvertUint(in.Timestamp), + GasUsed: mustConvertBigInt(in.GasUsed, 10), + GasLimit: mustConvertBigInt(in.GasLimit, 10), + Difficulty: mustConvertBigInt(in.Difficulty, 10), + Time: mustConvertUint(in.Timestamp, 10), } // XXX cheats? :-) - header.SetNonce(common.BytesToHash(mustConvertBytes(in.Nonce)).Big().Uint64()) + header.SetNonce(mustConvertUint(in.Nonce, 16)) return header } @@ -203,7 +227,7 @@ func mustConvertBlocks(testBlocks []btBlock) []*types.Block { 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", i, inb.Rlp)) + panic(fmt.Errorf("invalid block %d: %q\nerror: %v", i, inb.Rlp, err)) } out = append(out, &b) } @@ -214,7 +238,7 @@ func mustConvertBytes(in string) []byte { if in == "0x" { return []byte{} } - h := strings.TrimPrefix(unfuckCPPHexInts(in), "0x") + h := nibbleFix(strings.TrimPrefix(in, "0x")) out, err := hex.DecodeString(h) if err != nil { panic(fmt.Errorf("invalid hex: %q", h)) @@ -231,7 +255,7 @@ func mustConvertHash(in string) common.Hash { } func mustConvertAddress(in string) common.Address { - out, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) + out, err := hex.DecodeString(nibbleFix(strings.TrimPrefix(in, "0x"))) if err != nil { panic(fmt.Errorf("invalid hex: %q", in)) } @@ -246,16 +270,18 @@ func mustConvertBloom(in string) types.Bloom { return types.BytesToBloom(out) } -func mustConvertBigInt(in string) *big.Int { - out, ok := new(big.Int).SetString(unfuckCPPHexInts(in), 0) +func mustConvertBigInt(in string, base int) *big.Int { + in = prepInt(base, in) + out, ok := new(big.Int).SetString(in, base) if !ok { panic(fmt.Errorf("invalid integer: %q", in)) } return out } -func mustConvertUint(in string) uint64 { - out, err := strconv.ParseUint(unfuckCPPHexInts(in), 0, 64) +func mustConvertUint(in string, base int) uint64 { + in = prepInt(base, in) + out, err := strconv.ParseUint(in, base, 64) if err != nil { panic(fmt.Errorf("invalid integer: %q", in)) } @@ -292,12 +318,22 @@ func findLine(data []byte, offset int64) (line int) { return } -func unfuckCPPHexInts(s string) string { - if s == "0x" { // no respect for the empty value :( - return "0x00" - } - if (len(s) % 2) != 0 { // motherfucking nibbles - return "0x0" + s[2:] +func prepInt(base int, s string) string { + if base == 16 { + if strings.HasPrefix(s, "0x") { + s = s[2:] + } + if len(s) == 0 { + s = "00" + } + s = nibbleFix(s) + } + return s +} + +func nibbleFix(s string) string { + if len(s)%2 != 0 { + s = "0" + s } return s } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 567aba66f2..d82946e200 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -127,15 +127,15 @@ func convertTestTypes(txTest TransactionTest) (sender, to common.Address, txInputData = mustConvertBytes(txTest.Transaction.Data) rlpBytes = mustConvertBytes(txTest.Rlp) - gasLimit = mustConvertBigInt(txTest.Transaction.GasLimit) - gasPrice = mustConvertBigInt(txTest.Transaction.GasPrice) - value = mustConvertBigInt(txTest.Transaction.Value) + gasLimit = mustConvertBigInt(txTest.Transaction.GasLimit, 16) + gasPrice = mustConvertBigInt(txTest.Transaction.GasPrice, 16) + value = mustConvertBigInt(txTest.Transaction.Value, 16) r = common.Bytes2Big(mustConvertBytes(txTest.Transaction.R)) s = common.Bytes2Big(mustConvertBytes(txTest.Transaction.S)) - nonce = mustConvertUint(txTest.Transaction.Nonce) - v = mustConvertUint(txTest.Transaction.V) + nonce = mustConvertUint(txTest.Transaction.Nonce, 16) + v = mustConvertUint(txTest.Transaction.V, 16) return sender, to, txInputData, rlpBytes, gasLimit, gasPrice, value, r, s, nonce, v, nil }