| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- package tests
- import (
- "bytes"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "math/big"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "testing"
- "time"
- "github.com/ethereum/go-ethereum/accounts"
- "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/crypto"
- "github.com/ethereum/go-ethereum/eth"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/rlp"
- )
- // Block Test JSON Format
- type BlockTest struct {
- Genesis *types.Block
- Json *btJSON
- preAccounts map[string]btAccount
- }
- type btJSON struct {
- Blocks []btBlock
- GenesisBlockHeader btHeader
- Pre map[string]btAccount
- PostState map[string]btAccount
- }
- type btBlock struct {
- BlockHeader *btHeader
- Rlp string
- Transactions []btTransaction
- UncleHeaders []*btHeader
- }
- type btAccount struct {
- Balance string
- Code string
- Nonce string
- Storage map[string]string
- PrivateKey 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
- }
- func runBlockTestsInFile(filepath string, snafus []string, t *testing.T) {
- bt, err := LoadBlockTests(filepath)
- if err != nil {
- t.Fatal(err)
- }
- notWorking := make(map[string]bool, 100)
- for _, name := range snafus {
- notWorking[name] = true
- }
- for name, test := range bt {
- if !notWorking[name] {
- runBlockTest(name, test, t)
- }
- }
- }
- func runBlockTest(name string, test *BlockTest, t *testing.T) {
- cfg := testEthConfig()
- ethereum, err := eth.New(cfg)
- if err != nil {
- t.Fatalf("%v", err)
- }
- err = ethereum.Start()
- if err != nil {
- t.Fatalf("%v", err)
- }
- // import the genesis block
- ethereum.ResetWithGenesisBlock(test.Genesis)
- // import pre accounts
- statedb, err := test.InsertPreState(ethereum)
- if err != nil {
- t.Fatalf("InsertPreState: %v", err)
- }
- err = test.TryBlocksInsert(ethereum.ChainManager())
- if err != nil {
- t.Fatal(err)
- }
- if err = test.ValidatePostState(statedb); err != nil {
- t.Fatal("post state validation failed: %v", err)
- }
- fmt.Println("Block test passed: ", name)
- // t.Log("Block test passed: ", name)
- }
- func testEthConfig() *eth.Config {
- ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore"))
- return ð.Config{
- DataDir: common.DefaultDataDir(),
- Verbosity: 5,
- Etherbase: "primary",
- AccountManager: accounts.NewManager(ks),
- NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() },
- }
- }
- // 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 out, fmt.Errorf("bad test %q: %v", name, err)
- }
- }
- return out, nil
- }
- // InsertPreState populates the given database with the genesis
- // accounts defined by the test.
- func (t *BlockTest) InsertPreState(ethereum *eth.Ethereum) (*state.StateDB, error) {
- db := ethereum.StateDb()
- statedb := state.New(common.Hash{}, db)
- for addrString, acct := range t.preAccounts {
- 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)
- if acct.PrivateKey != "" {
- privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x"))
- err = crypto.ImportBlockTestKey(privkey)
- err = ethereum.AccountManager().TimedUnlock(common.BytesToAddress(addr), "", 999999*time.Second)
- if err != nil {
- return nil, err
- }
- }
- obj := statedb.CreateAccount(common.HexToAddress(addrString))
- obj.SetCode(code)
- obj.SetBalance(balance)
- obj.SetNonce(nonce)
- for k, v := range acct.Storage {
- statedb.SetState(common.HexToAddress(addrString), common.HexToHash(k), common.HexToHash(v))
- }
- }
- // sync objects to trie
- statedb.Update()
- // sync trie to disk
- statedb.Sync()
- if !bytes.Equal(t.Genesis.Root().Bytes(), statedb.Root().Bytes()) {
- return nil, fmt.Errorf("computed state root does not match genesis block %x %x", t.Genesis.Root().Bytes()[:4], statedb.Root().Bytes()[:4])
- }
- return statedb, nil
- }
- /* See https://github.com/ethereum/tests/wiki/Blockchain-Tests-II
- Whether a block is valid or not is a bit subtle, it's defined by presence of
- blockHeader, transactions and uncleHeaders fields. If they are missing, the block is
- invalid and we must verify that we do not accept it.
- Since some tests mix valid and invalid blocks we need to check this for every block.
- If a block is invalid it does not necessarily fail the test, if it's invalidness is
- expected we are expected to ignore it and continue processing and then validate the
- post state.
- */
- func (t *BlockTest) TryBlocksInsert(chainManager *core.ChainManager) error {
- // insert the test blocks, which will execute all transactions
- for _, b := range t.Json.Blocks {
- cb, err := mustConvertBlock(b)
- if err != nil {
- if b.BlockHeader == nil {
- continue // OK - block is supposed to be invalid, continue with next block
- } else {
- return fmt.Errorf("Block RLP decoding failed when expected to succeed: ", err)
- }
- }
- // RLP decoding worked, try to insert into chain:
- _, err = chainManager.InsertChain(types.Blocks{cb})
- if err != nil {
- if b.BlockHeader == nil {
- continue // OK - block is supposed to be invalid, continue with next block
- } else {
- return fmt.Errorf("Block insertion into chain failed: ", err)
- }
- }
- if b.BlockHeader == nil {
- return fmt.Errorf("Block insertion should have failed")
- }
- err = validateBlockHeader(b.BlockHeader, cb.Header())
- if err != nil {
- return fmt.Errorf("Block header validation failed: ", err)
- }
- }
- return nil
- }
- func validateBlockHeader(h *btHeader, h2 *types.Header) error {
- expectedBloom := mustConvertBytes(h.Bloom)
- if !bytes.Equal(expectedBloom, h2.Bloom.Bytes()) {
- return fmt.Errorf("Bloom: expected: %v, decoded: %v", expectedBloom, h2.Bloom.Bytes())
- }
- expectedCoinbase := mustConvertBytes(h.Coinbase)
- if !bytes.Equal(expectedCoinbase, h2.Coinbase.Bytes()) {
- return fmt.Errorf("Coinbase: expected: %v, decoded: %v", expectedCoinbase, h2.Coinbase.Bytes())
- }
- expectedMixHashBytes := mustConvertBytes(h.MixHash)
- if !bytes.Equal(expectedMixHashBytes, h2.MixDigest.Bytes()) {
- return fmt.Errorf("MixHash: expected: %v, decoded: %v", expectedMixHashBytes, h2.MixDigest.Bytes())
- }
- expectedNonce := mustConvertBytes(h.Nonce)
- if !bytes.Equal(expectedNonce, h2.Nonce[:]) {
- return fmt.Errorf("Nonce: expected: %v, decoded: %v", expectedNonce, h2.Nonce[:])
- }
- expectedNumber := mustConvertBigInt(h.Number, 16)
- if expectedNumber.Cmp(h2.Number) != 0 {
- return fmt.Errorf("Number: expected: %v, decoded: %v", expectedNumber, h2.Number)
- }
- expectedParentHash := mustConvertBytes(h.ParentHash)
- if !bytes.Equal(expectedParentHash, h2.ParentHash.Bytes()) {
- return fmt.Errorf("Parent hash: expected: %v, decoded: %v", expectedParentHash, h2.ParentHash.Bytes())
- }
- expectedReceiptHash := mustConvertBytes(h.ReceiptTrie)
- if !bytes.Equal(expectedReceiptHash, h2.ReceiptHash.Bytes()) {
- return fmt.Errorf("Receipt hash: expected: %v, decoded: %v", expectedReceiptHash, h2.ReceiptHash.Bytes())
- }
- expectedTxHash := mustConvertBytes(h.TransactionsTrie)
- if !bytes.Equal(expectedTxHash, h2.TxHash.Bytes()) {
- return fmt.Errorf("Tx hash: expected: %v, decoded: %v", expectedTxHash, h2.TxHash.Bytes())
- }
- expectedStateHash := mustConvertBytes(h.StateRoot)
- if !bytes.Equal(expectedStateHash, h2.Root.Bytes()) {
- return fmt.Errorf("State hash: expected: %v, decoded: %v", expectedStateHash, h2.Root.Bytes())
- }
- expectedUncleHash := mustConvertBytes(h.UncleHash)
- if !bytes.Equal(expectedUncleHash, h2.UncleHash.Bytes()) {
- return fmt.Errorf("Uncle hash: expected: %v, decoded: %v", expectedUncleHash, h2.UncleHash.Bytes())
- }
- expectedExtraData := mustConvertBytes(h.ExtraData)
- if !bytes.Equal(expectedExtraData, h2.Extra) {
- return fmt.Errorf("Extra data: expected: %v, decoded: %v", expectedExtraData, h2.Extra)
- }
- expectedDifficulty := mustConvertBigInt(h.Difficulty, 16)
- if expectedDifficulty.Cmp(h2.Difficulty) != 0 {
- return fmt.Errorf("Difficulty: expected: %v, decoded: %v", expectedDifficulty, h2.Difficulty)
- }
- expectedGasLimit := mustConvertBigInt(h.GasLimit, 16)
- if expectedGasLimit.Cmp(h2.GasLimit) != 0 {
- return fmt.Errorf("GasLimit: expected: %v, decoded: %v", expectedGasLimit, h2.GasLimit)
- }
- expectedGasUsed := mustConvertBigInt(h.GasUsed, 16)
- if expectedGasUsed.Cmp(h2.GasUsed) != 0 {
- return fmt.Errorf("GasUsed: expected: %v, decoded: %v", expectedGasUsed, h2.GasUsed)
- }
- expectedTimestamp := mustConvertUint(h.Timestamp, 16)
- if expectedTimestamp != h2.Time {
- return fmt.Errorf("Timestamp: expected: %v, decoded: %v", expectedTimestamp, h2.Time)
- }
- return nil
- }
- func (t *BlockTest) ValidatePostState(statedb *state.StateDB) error {
- 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)
- // address is indirectly verified by the other fields, as it's the db key
- code2 := statedb.GetCode(common.BytesToAddress(addr))
- balance2 := statedb.GetBalance(common.BytesToAddress(addr))
- nonce2 := statedb.GetNonce(common.BytesToAddress(addr))
- if !bytes.Equal(code2, code) {
- return fmt.Errorf("account code mismatch, addr, found, expected: ", addrString, hex.EncodeToString(code2), hex.EncodeToString(code))
- }
- if balance2.Cmp(balance) != 0 {
- return fmt.Errorf("account balance mismatch, addr, found, expected: ", addrString, balance2, balance)
- }
- if nonce2 != nonce {
- return fmt.Errorf("account nonce mismatch, addr, found, expected: ", addrString, nonce2, nonce)
- }
- }
- 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, Json: in}
- out.Genesis = mustConvertGenesis(in.GenesisBlockHeader)
- 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)
- return b
- }
- func mustConvertHeader(in btHeader) *types.Header {
- // hex decode these fields
- header := &types.Header{
- //SeedHash: mustConvertBytes(in.SeedHash),
- MixDigest: mustConvertHash(in.MixHash),
- Bloom: mustConvertBloom(in.Bloom),
- ReceiptHash: mustConvertHash(in.ReceiptTrie),
- TxHash: mustConvertHash(in.TransactionsTrie),
- Root: mustConvertHash(in.StateRoot),
- Coinbase: mustConvertAddress(in.Coinbase),
- UncleHash: mustConvertHash(in.UncleHash),
- ParentHash: mustConvertHash(in.ParentHash),
- Extra: mustConvertBytes(in.ExtraData),
- GasUsed: mustConvertBigInt(in.GasUsed, 16),
- GasLimit: mustConvertBigInt(in.GasLimit, 16),
- Difficulty: mustConvertBigInt(in.Difficulty, 16),
- Time: mustConvertUint(in.Timestamp, 16),
- }
- // XXX cheats? :-)
- header.SetNonce(mustConvertUint(in.Nonce, 16))
- return header
- }
- func mustConvertBlock(testBlock btBlock) (*types.Block, error) {
- var b types.Block
- r := bytes.NewReader(mustConvertBytes(testBlock.Rlp))
- err := rlp.Decode(r, &b)
- return &b, err
- }
- func mustConvertBytes(in string) []byte {
- if in == "0x" {
- return []byte{}
- }
- h := unfuckFuckedHex(strings.TrimPrefix(in, "0x"))
- out, err := hex.DecodeString(h)
- if err != nil {
- panic(fmt.Errorf("invalid hex: %q: ", h, err))
- }
- return out
- }
- func mustConvertHash(in string) common.Hash {
- out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
- if err != nil {
- panic(fmt.Errorf("invalid hex: %q", in))
- }
- return common.BytesToHash(out)
- }
- func mustConvertAddress(in string) common.Address {
- out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
- if err != nil {
- panic(fmt.Errorf("invalid hex: %q", in))
- }
- return common.BytesToAddress(out)
- }
- func mustConvertBloom(in string) types.Bloom {
- out, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
- if err != nil {
- panic(fmt.Errorf("invalid hex: %q", in))
- }
- return types.BytesToBloom(out)
- }
- 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, base int) uint64 {
- in = prepInt(base, in)
- out, err := strconv.ParseUint(in, base, 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
- }
- // Nothing to see here, please move along...
- func prepInt(base int, s string) string {
- if base == 16 {
- if strings.HasPrefix(s, "0x") {
- s = s[2:]
- }
- if len(s) == 0 {
- s = "00"
- }
- s = nibbleFix(s)
- }
- return s
- }
- // don't ask
- func unfuckFuckedHex(almostHex string) string {
- return nibbleFix(strings.Replace(almostHex, "v", "", -1))
- }
- func nibbleFix(s string) string {
- if len(s)%2 != 0 {
- s = "0" + s
- }
- return s
- }
|