mirror of https://github.com/ethereum/go-ethereum
commit
282d8c20fd
@ -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) |
||||
} |
@ -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