Merge remote-tracking branch 'felix/blocktest-parsing-fix' into HEAD

* felix/blocktest-parsing-fix:
  tests: hopefully improve test conversion helpers
  blockpool: add missing shutdown synchronization
  core, eth: add missing shutdown synchronization to ChainManager
  cmd/geth, tests: enable running multiple tests from a single file
  eth: honour config.Shh
  eth: use NewDB hook also for extra DB

Conflicts:
	eth/backend.go
This commit is contained in:
Gustav Simonsson 2015-04-18 11:58:31 +02:00
commit 7c3319f4d7
6 changed files with 146 additions and 57 deletions

View file

@ -184,6 +184,7 @@ type BlockPool struct {
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

View file

@ -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 <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
@ -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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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"
func prepInt(base int, s string) string {
if base == 16 {
if strings.HasPrefix(s, "0x") {
s = s[2:]
}
if (len(s) % 2) != 0 { // motherfucking nibbles
return "0x0" + 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
}

View file

@ -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
}