diff --git a/cmd/ethereum/blocktest.go b/cmd/ethereum/blocktest.go new file mode 100644 index 000000000..1bb3809cf --- /dev/null +++ b/cmd/ethereum/blocktest.go @@ -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) +} diff --git a/cmd/ethereum/main.go b/cmd/ethereum/main.go index 4cb2d9979..53746627a 100644 --- a/cmd/ethereum/main.go +++ b/cmd/ethereum/main.go @@ -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) { diff --git a/cmd/mist/main.go b/cmd/mist/main.go index 9a773e33a..4116783c9 100644 --- a/cmd/mist/main.go +++ b/cmd/mist/main.go @@ -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) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 990fb08e0..6bcd7e811 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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) { diff --git a/core/types/block.go b/core/types/block.go index 50973c804..ba6ef6014 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -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 } diff --git a/eth/backend.go b/eth/backend.go index bb203b4a6..346fc43bc 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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 { diff --git a/tests/blocktest.go b/tests/blocktest.go new file mode 100644 index 000000000..6a9cf5f6d --- /dev/null +++ b/tests/blocktest.go @@ -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 +}