Merge branch 'fjl-block-tests' into develop
This commit is contained in:
		
						commit
						282d8c20fd
					
				
							
								
								
									
										66
									
								
								cmd/ethereum/blocktest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								cmd/ethereum/blocktest.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/codegangsta/cli" | ||||
| 	"github.com/ethereum/go-ethereum/cmd/utils" | ||||
| 	"github.com/ethereum/go-ethereum/eth" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/ethutil" | ||||
| 	"github.com/ethereum/go-ethereum/tests" | ||||
| ) | ||||
| 
 | ||||
| var blocktestCmd = cli.Command{ | ||||
| 	Action: runblocktest, | ||||
| 	Name:   "blocktest", | ||||
| 	Usage:  `loads a block test file`, | ||||
| 	Description: ` | ||||
| The first argument should be a block test file. | ||||
| The second argument is the name of a block test from the file. | ||||
| 
 | ||||
| The block test will be loaded into an in-memory database. | ||||
| If loading succeeds, the RPC server is started. Clients will | ||||
| be able to interact with the chain defined by the test. | ||||
| `, | ||||
| } | ||||
| 
 | ||||
| func runblocktest(ctx *cli.Context) { | ||||
| 	if len(ctx.Args()) != 2 { | ||||
| 		utils.Fatalf("This command requires two arguments.") | ||||
| 	} | ||||
| 	file, testname := ctx.Args()[0], ctx.Args()[1] | ||||
| 
 | ||||
| 	bt, err := tests.LoadBlockTests(file) | ||||
| 	if err != nil { | ||||
| 		utils.Fatalf("%v", err) | ||||
| 	} | ||||
| 	test, ok := bt[testname] | ||||
| 	if !ok { | ||||
| 		utils.Fatalf("Test file does not contain test named %q", testname) | ||||
| 	} | ||||
| 
 | ||||
| 	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) | ||||
| 	cfg.NewDB = func(path string) (ethutil.Database, error) { return ethdb.NewMemDatabase() } | ||||
| 	ethereum, err := eth.New(cfg) | ||||
| 	if err != nil { | ||||
| 		utils.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// import the genesis block
 | ||||
| 	ethereum.ResetWithGenesisBlock(test.Genesis) | ||||
| 
 | ||||
| 	// import pre accounts
 | ||||
| 	if err := test.InsertPreState(ethereum.StateDb()); err != nil { | ||||
| 		utils.Fatalf("could not insert genesis accounts: %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", err) | ||||
| 	} else { | ||||
| 		fmt.Println("Block Test chain loaded, starting ethereum.") | ||||
| 	} | ||||
| 	startEth(ctx, ethereum) | ||||
| } | ||||
| @ -53,6 +53,7 @@ func init() { | ||||
| 	app.Action = run | ||||
| 	app.HideVersion = true // we have a command to print the version
 | ||||
| 	app.Commands = []cli.Command{ | ||||
| 		blocktestCmd, | ||||
| 		{ | ||||
| 			Action: version, | ||||
| 			Name:   "version", | ||||
| @ -156,24 +157,26 @@ func main() { | ||||
| func run(ctx *cli.Context) { | ||||
| 	fmt.Printf("Welcome to the FRONTIER\n") | ||||
| 	utils.HandleInterrupt() | ||||
| 	eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx) | ||||
| 	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) | ||||
| 	ethereum, err := eth.New(cfg) | ||||
| 	if err != nil { | ||||
| 		utils.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	startEth(ctx, eth) | ||||
| 	startEth(ctx, ethereum) | ||||
| 	// this blocks the thread
 | ||||
| 	eth.WaitForShutdown() | ||||
| 	ethereum.WaitForShutdown() | ||||
| } | ||||
| 
 | ||||
| func runjs(ctx *cli.Context) { | ||||
| 	eth, err := utils.GetEthereum(ClientIdentifier, Version, ctx) | ||||
| 	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) | ||||
| 	ethereum, err := eth.New(cfg) | ||||
| 	if err != nil { | ||||
| 		utils.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	startEth(ctx, eth) | ||||
| 	repl := newJSRE(eth) | ||||
| 	startEth(ctx, ethereum) | ||||
| 	repl := newJSRE(ethereum) | ||||
| 	if len(ctx.Args()) == 0 { | ||||
| 		repl.interactive() | ||||
| 	} else { | ||||
| @ -181,8 +184,8 @@ func runjs(ctx *cli.Context) { | ||||
| 			repl.exec(file) | ||||
| 		} | ||||
| 	} | ||||
| 	eth.Stop() | ||||
| 	eth.WaitForShutdown() | ||||
| 	ethereum.Stop() | ||||
| 	ethereum.WaitForShutdown() | ||||
| } | ||||
| 
 | ||||
| func startEth(ctx *cli.Context, eth *eth.Ethereum) { | ||||
|  | ||||
| @ -28,6 +28,7 @@ import ( | ||||
| 
 | ||||
| 	"github.com/codegangsta/cli" | ||||
| 	"github.com/ethereum/go-ethereum/cmd/utils" | ||||
| 	"github.com/ethereum/go-ethereum/eth" | ||||
| 	"github.com/ethereum/go-ethereum/ethutil" | ||||
| 	"github.com/ethereum/go-ethereum/logger" | ||||
| 	"github.com/ethereum/go-ethereum/ui/qt/webengine" | ||||
| @ -95,7 +96,8 @@ func run(ctx *cli.Context) { | ||||
| 	tstart := time.Now() | ||||
| 
 | ||||
| 	// TODO: show qml popup instead of exiting if initialization fails.
 | ||||
| 	ethereum, err := utils.GetEthereum(ClientIdentifier, Version, ctx) | ||||
| 	cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) | ||||
| 	ethereum, err := eth.New(cfg) | ||||
| 	if err != nil { | ||||
| 		utils.Fatalf("%v", err) | ||||
| 	} | ||||
|  | ||||
| @ -191,8 +191,8 @@ func GetNodeKey(ctx *cli.Context) (key *ecdsa.PrivateKey) { | ||||
| 	return key | ||||
| } | ||||
| 
 | ||||
| func GetEthereum(clientID, version string, ctx *cli.Context) (*eth.Ethereum, error) { | ||||
| 	return eth.New(ð.Config{ | ||||
| func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { | ||||
| 	return ð.Config{ | ||||
| 		Name:           ethutil.MakeName(clientID, version), | ||||
| 		DataDir:        ctx.GlobalString(DataDirFlag.Name), | ||||
| 		LogFile:        ctx.GlobalString(LogFileFlag.Name), | ||||
| @ -208,7 +208,7 @@ func GetEthereum(clientID, version string, ctx *cli.Context) (*eth.Ethereum, err | ||||
| 		Shh:            true, | ||||
| 		Dial:           true, | ||||
| 		BootNodes:      ctx.GlobalString(BootnodesFlag.Name), | ||||
| 	}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func GetChain(ctx *cli.Context) (*core.ChainManager, ethutil.Database, ethutil.Database) { | ||||
|  | ||||
| @ -15,7 +15,7 @@ import ( | ||||
| 
 | ||||
| type Header struct { | ||||
| 	// Hash to the previous block
 | ||||
| 	ParentHash ethutil.Bytes | ||||
| 	ParentHash []byte | ||||
| 	// Uncles of this block
 | ||||
| 	UncleHash []byte | ||||
| 	// The coin base address
 | ||||
| @ -41,7 +41,7 @@ type Header struct { | ||||
| 	// Extra data
 | ||||
| 	Extra string | ||||
| 	// Mix digest for quick checking to prevent DOS
 | ||||
| 	MixDigest ethutil.Bytes | ||||
| 	MixDigest []byte | ||||
| 	// Nonce
 | ||||
| 	Nonce []byte | ||||
| } | ||||
|  | ||||
| @ -11,6 +11,7 @@ import ( | ||||
| 	"github.com/ethereum/go-ethereum/accounts" | ||||
| 	"github.com/ethereum/go-ethereum/blockpool" | ||||
| 	"github.com/ethereum/go-ethereum/core" | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"github.com/ethereum/go-ethereum/ethdb" | ||||
| 	"github.com/ethereum/go-ethereum/ethutil" | ||||
| @ -61,6 +62,10 @@ type Config struct { | ||||
| 
 | ||||
| 	MinerThreads   int | ||||
| 	AccountManager *accounts.Manager | ||||
| 
 | ||||
| 	// NewDB is used to create databases.
 | ||||
| 	// If nil, the default is to create leveldb databases on disk.
 | ||||
| 	NewDB func(path string) (ethutil.Database, error) | ||||
| } | ||||
| 
 | ||||
| func (cfg *Config) parseBootNodes() []*discover.Node { | ||||
| @ -120,6 +125,7 @@ type Ethereum struct { | ||||
| 	blockPool      *blockpool.BlockPool | ||||
| 	accountManager *accounts.Manager | ||||
| 	whisper        *whisper.Whisper | ||||
| 	pow            *ethash.Ethash | ||||
| 
 | ||||
| 	net      *p2p.Server | ||||
| 	eventMux *event.TypeMux | ||||
| @ -138,11 +144,15 @@ func New(config *Config) (*Ethereum, error) { | ||||
| 	// Boostrap database
 | ||||
| 	servlogger := logger.New(config.DataDir, config.LogFile, config.LogLevel, config.LogFormat) | ||||
| 
 | ||||
| 	blockDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "blockchain")) | ||||
| 	newdb := config.NewDB | ||||
| 	if newdb == nil { | ||||
| 		newdb = func(path string) (ethutil.Database, error) { return ethdb.NewLDBDatabase(path) } | ||||
| 	} | ||||
| 	blockDb, err := newdb(path.Join(config.DataDir, "blockchain")) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	stateDb, err := ethdb.NewLDBDatabase(path.Join(config.DataDir, "state")) | ||||
| 	stateDb, err := newdb(path.Join(config.DataDir, "state")) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -170,16 +180,16 @@ func New(config *Config) (*Ethereum, error) { | ||||
| 	} | ||||
| 
 | ||||
| 	eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux()) | ||||
| 	pow := ethash.New(eth.chainManager) | ||||
| 	eth.pow = ethash.New(eth.chainManager) | ||||
| 	eth.txPool = core.NewTxPool(eth.EventMux()) | ||||
| 	eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, pow, eth.txPool, eth.chainManager, eth.EventMux()) | ||||
| 	eth.blockProcessor = core.NewBlockProcessor(stateDb, extraDb, eth.pow, eth.txPool, eth.chainManager, eth.EventMux()) | ||||
| 	eth.chainManager.SetProcessor(eth.blockProcessor) | ||||
| 	eth.whisper = whisper.New() | ||||
| 	eth.miner = miner.New(eth, pow, config.MinerThreads) | ||||
| 	eth.miner = miner.New(eth, eth.pow, config.MinerThreads) | ||||
| 
 | ||||
| 	hasBlock := eth.chainManager.HasBlock | ||||
| 	insertChain := eth.chainManager.InsertChain | ||||
| 	eth.blockPool = blockpool.New(hasBlock, insertChain, pow.Verify) | ||||
| 	eth.blockPool = blockpool.New(hasBlock, insertChain, eth.pow.Verify) | ||||
| 
 | ||||
| 	netprv, err := config.nodeKey() | ||||
| 	if err != nil { | ||||
| @ -209,6 +219,11 @@ func New(config *Config) (*Ethereum, error) { | ||||
| 	return eth, nil | ||||
| } | ||||
| 
 | ||||
| func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { | ||||
| 	s.chainManager.ResetWithGenesisBlock(gb) | ||||
| 	s.pow.UpdateCache(true) | ||||
| } | ||||
| 
 | ||||
| func (s *Ethereum) StartMining() error { | ||||
| 	cb, err := s.accountManager.Coinbase() | ||||
| 	if err != nil { | ||||
|  | ||||
							
								
								
									
										240
									
								
								tests/blocktest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								tests/blocktest.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,240 @@ | ||||
| package tests | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/hex" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"math/big" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/core/types" | ||||
| 	"github.com/ethereum/go-ethereum/ethutil" | ||||
| 	"github.com/ethereum/go-ethereum/rlp" | ||||
| 	"github.com/ethereum/go-ethereum/state" | ||||
| ) | ||||
| 
 | ||||
| // Block Test JSON Format
 | ||||
| 
 | ||||
| type btJSON struct { | ||||
| 	Blocks             []btBlock | ||||
| 	GenesisBlockHeader btHeader | ||||
| 	Pre                map[string]btAccount | ||||
| } | ||||
| 
 | ||||
| type btAccount struct { | ||||
| 	Balance string | ||||
| 	Code    string | ||||
| 	Nonce   string | ||||
| 	Storage map[string]string | ||||
| } | ||||
| 
 | ||||
| type btHeader struct { | ||||
| 	Bloom            string | ||||
| 	Coinbase         string | ||||
| 	MixHash          string | ||||
| 	Nonce            string | ||||
| 	Number           string | ||||
| 	ParentHash       string | ||||
| 	ReceiptTrie      string | ||||
| 	SeedHash         string | ||||
| 	StateRoot        string | ||||
| 	TransactionsTrie string | ||||
| 	UncleHash        string | ||||
| 
 | ||||
| 	ExtraData  string | ||||
| 	Difficulty string | ||||
| 	GasLimit   string | ||||
| 	GasUsed    string | ||||
| 	Timestamp  string | ||||
| } | ||||
| 
 | ||||
| type btTransaction struct { | ||||
| 	Data     string | ||||
| 	GasLimit string | ||||
| 	GasPrice string | ||||
| 	Nonce    string | ||||
| 	R        string | ||||
| 	S        string | ||||
| 	To       string | ||||
| 	V        string | ||||
| 	Value    string | ||||
| } | ||||
| 
 | ||||
| type btBlock struct { | ||||
| 	BlockHeader  *btHeader | ||||
| 	Rlp          string | ||||
| 	Transactions []btTransaction | ||||
| 	UncleHeaders []string | ||||
| } | ||||
| 
 | ||||
| type BlockTest struct { | ||||
| 	Genesis *types.Block | ||||
| 	Blocks  []*types.Block | ||||
| 
 | ||||
| 	preAccounts map[string]btAccount | ||||
| } | ||||
| 
 | ||||
| // LoadBlockTests loads a block test JSON file.
 | ||||
| func LoadBlockTests(file string) (map[string]*BlockTest, error) { | ||||
| 	bt := make(map[string]*btJSON) | ||||
| 	if err := loadJSON(file, &bt); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	out := make(map[string]*BlockTest) | ||||
| 	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, nil | ||||
| } | ||||
| 
 | ||||
| // InsertPreState populates the given database with the genesis
 | ||||
| // accounts defined by the test.
 | ||||
| func (t *BlockTest) InsertPreState(db ethutil.Database) error { | ||||
| 	statedb := state.New(nil, db) | ||||
| 	for addrString, acct := range t.preAccounts { | ||||
| 		// XXX: is is worth it checking for errors here?
 | ||||
| 		addr, _ := hex.DecodeString(addrString) | ||||
| 		code, _ := hex.DecodeString(strings.TrimPrefix(acct.Code, "0x")) | ||||
| 		balance, _ := new(big.Int).SetString(acct.Balance, 0) | ||||
| 		nonce, _ := strconv.ParseUint(acct.Nonce, 16, 64) | ||||
| 
 | ||||
| 		obj := statedb.NewStateObject(addr) | ||||
| 		obj.SetCode(code) | ||||
| 		obj.SetBalance(balance) | ||||
| 		obj.SetNonce(nonce) | ||||
| 		// for k, v := range acct.Storage {
 | ||||
| 		// 	obj.SetState(k, v)
 | ||||
| 		// }
 | ||||
| 	} | ||||
| 	// sync objects to trie
 | ||||
| 	statedb.Update(nil) | ||||
| 	// sync trie to disk
 | ||||
| 	statedb.Sync() | ||||
| 
 | ||||
| 	if !bytes.Equal(t.Genesis.Root(), statedb.Root()) { | ||||
| 		return errors.New("computed state root does not match genesis block") | ||||
| 	} | ||||
| 	return 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)
 | ||||
| 	// 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) | ||||
| 		} | ||||
| 	}() | ||||
| 	out = &BlockTest{preAccounts: in.Pre} | ||||
| 	out.Genesis = mustConvertGenesis(in.GenesisBlockHeader) | ||||
| 	out.Blocks = mustConvertBlocks(in.Blocks) | ||||
| 	return out, err | ||||
| } | ||||
| 
 | ||||
| func mustConvertGenesis(testGenesis btHeader) *types.Block { | ||||
| 	hdr := mustConvertHeader(testGenesis) | ||||
| 	hdr.Number = big.NewInt(0) | ||||
| 	b := types.NewBlockWithHeader(hdr) | ||||
| 	b.Td = new(big.Int) | ||||
| 	b.Reward = new(big.Int) | ||||
| 	return b | ||||
| } | ||||
| 
 | ||||
| func mustConvertHeader(in btHeader) *types.Header { | ||||
| 	// hex decode these fields
 | ||||
| 	return &types.Header{ | ||||
| 		//SeedHash:    mustConvertBytes(in.SeedHash),
 | ||||
| 		MixDigest:   mustConvertBytes(in.MixHash), | ||||
| 		Bloom:       mustConvertBytes(in.Bloom), | ||||
| 		ReceiptHash: mustConvertBytes(in.ReceiptTrie), | ||||
| 		TxHash:      mustConvertBytes(in.TransactionsTrie), | ||||
| 		Root:        mustConvertBytes(in.StateRoot), | ||||
| 		Coinbase:    mustConvertBytes(in.Coinbase), | ||||
| 		UncleHash:   mustConvertBytes(in.UncleHash), | ||||
| 		ParentHash:  mustConvertBytes(in.ParentHash), | ||||
| 		Nonce:       mustConvertBytes(in.Nonce), | ||||
| 		Extra:       string(mustConvertBytes(in.ExtraData)), | ||||
| 		GasUsed:     mustConvertBigInt10(in.GasUsed), | ||||
| 		GasLimit:    mustConvertBigInt10(in.GasLimit), | ||||
| 		Difficulty:  mustConvertBigInt10(in.Difficulty), | ||||
| 		Time:        mustConvertUint(in.Timestamp), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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", i, inb.Rlp)) | ||||
| 		} | ||||
| 		out = append(out, &b) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| func mustConvertBytes(in string) []byte { | ||||
| 	out, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) | ||||
| 	if err != nil { | ||||
| 		panic(fmt.Errorf("invalid hex: %q", in)) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| func mustConvertBigInt10(in string) *big.Int { | ||||
| 	out, ok := new(big.Int).SetString(in, 10) | ||||
| 	if !ok { | ||||
| 		panic(fmt.Errorf("invalid integer: %q", in)) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| func mustConvertUint(in string) uint64 { | ||||
| 	out, err := strconv.ParseUint(in, 0, 64) | ||||
| 	if err != nil { | ||||
| 		panic(fmt.Errorf("invalid integer: %q", in)) | ||||
| 	} | ||||
| 	return out | ||||
| } | ||||
| 
 | ||||
| // loadJSON reads the given file and unmarshals its content.
 | ||||
| func loadJSON(file string, val interface{}) error { | ||||
| 	content, err := ioutil.ReadFile(file) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := json.Unmarshal(content, val); err != nil { | ||||
| 		if syntaxerr, ok := err.(*json.SyntaxError); ok { | ||||
| 			line := findLine(content, syntaxerr.Offset) | ||||
| 			return fmt.Errorf("JSON syntax error at %v:%v: %v", file, line, err) | ||||
| 		} | ||||
| 		return fmt.Errorf("JSON unmarshal error in %v: %v", file, err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // findLine returns the line number for the given offset into data.
 | ||||
| func findLine(data []byte, offset int64) (line int) { | ||||
| 	line = 1 | ||||
| 	for i, r := range string(data) { | ||||
| 		if int64(i) >= offset { | ||||
| 			return | ||||
| 		} | ||||
| 		if r == '\n' { | ||||
| 			line++ | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user