From 871dfd399be8ee657109112d527645c2c1b3a8f9 Mon Sep 17 00:00:00 2001 From: Gustav Simonsson Date: Tue, 3 Mar 2015 18:41:51 +0100 Subject: [PATCH] Add initial implementation of block tests * Add blocktest cmd and support for block tests files in tests/BlockTests , the launched node does not connect to network, resets state with a genesis block from the test file and starts the RPC API --- cmd/blocktest/flags.go | 41 ++++++ cmd/blocktest/main.go | 320 +++++++++++++++++++++++++++++++++++++++++ core/chain_manager.go | 15 ++ core/error.go | 2 +- 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 cmd/blocktest/flags.go create mode 100644 cmd/blocktest/main.go diff --git a/cmd/blocktest/flags.go b/cmd/blocktest/flags.go new file mode 100644 index 000000000..c811e5b85 --- /dev/null +++ b/cmd/blocktest/flags.go @@ -0,0 +1,41 @@ +/* + This file is part of go-ethereum + + go-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + go-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with go-ethereum. If not, see . +*/ +/** + * @authors + * Gustav Simonsson + */ +package main + +import ( + "flag" + "fmt" + "os" +) + +var ( + TestFile string +) + +func Init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s \n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() + + TestFile = flag.Arg(0) +} diff --git a/cmd/blocktest/main.go b/cmd/blocktest/main.go new file mode 100644 index 000000000..4a05b8bee --- /dev/null +++ b/cmd/blocktest/main.go @@ -0,0 +1,320 @@ +/* + This file is part of go-ethereum + + go-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + go-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with go-ethereum. If not, see . +*/ +/** + * @authors + * Gustav Simonsson + * @date 2015 + * + */ + +package main + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/big" + "path" + "runtime" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + types "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + ClientIdentifier = "Ethereum(G)" + Version = "0.8.6" +) + +type Account struct { + Balance string + Code string + Nonce string + Storage map[string]string +} + +type BlockHeader struct { + Bloom string + Coinbase string + Difficulty string + ExtraData string + GasLimit string + GasUsed string + MixHash string + Nonce string + Number string + ParentHash string + ReceiptTrie string + SeedHash string + StateRoot string + Timestamp string + TransactionsTrie string + UncleHash string +} +type Tx struct { + Data string + GasLimit string + GasPrice string + Nonce string + R string + S string + To string + V string + Value string +} + +type Block struct { + BlockHeader BlockHeader + Rlp string + Transactions []Tx + UncleHeaders []string +} + +type Test struct { + Blocks []Block + GenesisBlockHeader BlockHeader + Pre map[string]Account +} + +var ( + Identifier string + KeyRing string + DiffTool bool + DiffType string + KeyStore string + StartRpc bool + StartWebSockets bool + RpcListenAddress string + RpcPort int + WsPort int + OutboundPort string + ShowGenesis bool + AddPeer string + MaxPeer int + GenAddr bool + BootNodes string + NodeKey *ecdsa.PrivateKey + NAT nat.Interface + SecretFile string + ExportDir string + NonInteractive bool + Datadir string + LogFile string + ConfigFile string + DebugFile string + LogLevel int + LogFormat string + Dump bool + DumpHash string + DumpNumber int + VmType int + ImportChain string + SHH bool + Dial bool + PrintVersion bool + MinerThreads int +) + +// flags specific to cli client +var ( + StartMining bool + StartJsConsole bool + InputFile string +) + +func main() { + init_vars() + + Init() + + if len(TestFile) < 1 { + log.Fatal("Please specify test file") + } + blocks, err := loadBlocksFromTestFile(TestFile) + if err != nil { + panic(err) + } + + runtime.GOMAXPROCS(runtime.NumCPU()) + + defer func() { + logger.Flush() + }() + + utils.HandleInterrupt() + + utils.InitConfig(VmType, ConfigFile, Datadir, "ethblocktest") + + ethereum, err := eth.New(ð.Config{ + Name: p2p.MakeName(ClientIdentifier, Version), + KeyStore: KeyStore, + DataDir: Datadir, + LogFile: LogFile, + LogLevel: LogLevel, + LogFormat: LogFormat, + MaxPeers: MaxPeer, + Port: OutboundPort, + NAT: NAT, + KeyRing: KeyRing, + Shh: true, + Dial: Dial, + BootNodes: BootNodes, + NodeKey: NodeKey, + MinerThreads: MinerThreads, + }) + + utils.StartRpc(ethereum, RpcListenAddress, RpcPort) + utils.StartEthereum(ethereum) + + ethereum.ChainManager().ResetWithGenesisBlock(blocks[0]) + + // fmt.Println("HURR: ", hex.EncodeToString(ethutil.Encode(blocks[0].RlpData()))) + + go ethereum.ChainManager().InsertChain(types.Blocks{blocks[1]}) + fmt.Println("OK! ") + ethereum.WaitForShutdown() +} + +func loadBlocksFromTestFile(filePath string) (blocks types.Blocks, err error) { + fileContent, err := ioutil.ReadFile(filePath) + if err != nil { + return + } + bt := *new(map[string]Test) + err = json.Unmarshal(fileContent, &bt) + if err != nil { + return + } + + // TODO: support multiple blocks; loop over all blocks + gbh := new(types.Header) + + // Let's use slighlty different namings for the same things, because that's awesome. + gbh.ParentHash, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.ParentHash) + gbh.UncleHash, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.UncleHash) + gbh.Coinbase, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.Coinbase) + gbh.Root, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.StateRoot) + gbh.TxHash, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.TransactionsTrie) + gbh.ReceiptHash, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.ReceiptTrie) + gbh.Bloom, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.Bloom) + + gbh.MixDigest, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.MixHash) + gbh.SeedHash, err = hex_decode(bt["SimpleTx"].GenesisBlockHeader.SeedHash) + + d, _ := new(big.Int).SetString(bt["SimpleTx"].GenesisBlockHeader.Difficulty, 10) + gbh.Difficulty = d + + n, _ := new(big.Int).SetString(bt["SimpleTx"].GenesisBlockHeader.Number, 10) + gbh.Number = n + + gl, _ := new(big.Int).SetString(bt["SimpleTx"].GenesisBlockHeader.GasLimit, 10) + gbh.GasLimit = gl + + gu, _ := new(big.Int).SetString(bt["SimpleTx"].GenesisBlockHeader.GasUsed, 10) + gbh.GasUsed = gu + + ts, _ := new(big.Int).SetString(bt["SimpleTx"].GenesisBlockHeader.Timestamp, 0) + gbh.Time = ts.Uint64() + + extra, err := hex_decode(bt["SimpleTx"].GenesisBlockHeader.ExtraData) + gbh.Extra = string(extra) // TODO: change ExtraData to byte array + + nonce, _ := hex_decode(bt["SimpleTx"].GenesisBlockHeader.Nonce) + gbh.Nonce = nonce + + if err != nil { + return + } + + gb := types.NewBlockWithHeader(gbh) + gb.Reward = new(big.Int) + + testBlock := new(types.Block) + + rlpBytes, err := hex_decode(bt["SimpleTx"].Blocks[0].Rlp) + err = rlp.Decode(bytes.NewReader(rlpBytes), &testBlock) + if err != nil { + return + } + + blocks = types.Blocks{ + gb, + testBlock, + } + + return +} + +func init_vars() { + VmType = 0 + Identifier = "" + KeyRing = "" + KeyStore = "db" + RpcListenAddress = "127.0.0.1" + RpcPort = 8545 + WsPort = 40404 + StartRpc = true + StartWebSockets = false + NonInteractive = false + GenAddr = false + SecretFile = "" + ExportDir = "" + LogFile = "" + + timeStr := strconv.FormatInt(time.Now().UnixNano(), 10) + + Datadir = path.Join(ethutil.DefaultDataDir(), timeStr) + ConfigFile = path.Join(ethutil.DefaultDataDir(), timeStr, "conf.ini") + + DebugFile = "" + LogLevel = 5 + LogFormat = "std" + DiffTool = false + DiffType = "all" + ShowGenesis = false + ImportChain = "" + Dump = false + DumpHash = "" + DumpNumber = -1 + StartMining = false + StartJsConsole = false + PrintVersion = false + MinerThreads = runtime.NumCPU() + + Dial = false + OutboundPort = "30303" + BootNodes = "" + MaxPeer = 1 + +} + +func hex_decode(s string) (res []byte, err error) { + return hex.DecodeString(strings.TrimPrefix(s, "0x")) +} diff --git a/core/chain_manager.go b/core/chain_manager.go index f2382cf8d..17bfb1f3e 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -231,6 +231,21 @@ func (bc *ChainManager) Reset() { bc.setTotalDifficulty(ethutil.Big("0")) } +func (bc *ChainManager) ResetWithGenesisBlock(gb *types.Block) { + bc.mu.Lock() + defer bc.mu.Unlock() + + for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) { + bc.db.Delete(block.Hash()) + } + + // Prepare the genesis block + bc.genesisBlock = gb + bc.write(bc.genesisBlock) + bc.insert(bc.genesisBlock) + bc.currentBlock = bc.genesisBlock +} + func (self *ChainManager) Export() []byte { self.mu.RLock() defer self.mu.RUnlock() diff --git a/core/error.go b/core/error.go index fb1eaed84..514cd076b 100644 --- a/core/error.go +++ b/core/error.go @@ -22,7 +22,7 @@ func (err *ParentErr) Error() string { } func ParentError(hash []byte) error { - return &ParentErr{Message: fmt.Sprintf("Block's parent unkown %x", hash)} + return &ParentErr{Message: fmt.Sprintf("Block's parent unknown %x", hash)} } func IsParentErr(err error) bool {