Merge branch 'fjl-block-tests' into develop

This commit is contained in:
obscuren 2015-03-14 23:37:37 +01:00
commit 282d8c20fd
7 changed files with 346 additions and 20 deletions

66
cmd/ethereum/blocktest.go Normal file
View 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)
}

View File

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

View File

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

View File

@ -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(&eth.Config{
func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
return &eth.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) {

View File

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

View File

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