aboutsummaryrefslogtreecommitdiff
path: root/cmd/geth/retesteth.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/geth/retesteth.go')
-rw-r--r--cmd/geth/retesteth.go891
1 files changed, 891 insertions, 0 deletions
diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go
new file mode 100644
index 0000000..e3f163b
--- /dev/null
+++ b/cmd/geth/retesteth.go
@@ -0,0 +1,891 @@
+// Copyright 2019 The go-ethereum Authors
+// 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 <http://www.gnu.org/licenses/>.
+
+package geth
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "math/big"
+ "os"
+ "os/signal"
+ "strings"
+ "time"
+
+ "github.com/Determinant/coreth/cmd/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/consensus/misc"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/trie"
+
+ cli "gopkg.in/urfave/cli.v1"
+)
+
+var (
+ rpcPortFlag = cli.IntFlag{
+ Name: "rpcport",
+ Usage: "HTTP-RPC server listening port",
+ Value: node.DefaultHTTPPort,
+ }
+ retestethCommand = cli.Command{
+ Action: utils.MigrateFlags(retesteth),
+ Name: "retesteth",
+ Usage: "Launches geth in retesteth mode",
+ ArgsUsage: "",
+ Flags: []cli.Flag{rpcPortFlag},
+ Category: "MISCELLANEOUS COMMANDS",
+ Description: `Launches geth in retesteth mode (no database, no network, only retesteth RPC interface)`,
+ }
+)
+
+type RetestethTestAPI interface {
+ SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error)
+ MineBlocks(ctx context.Context, number uint64) (bool, error)
+ ModifyTimestamp(ctx context.Context, interval uint64) (bool, error)
+ ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error)
+ RewindToBlock(ctx context.Context, number uint64) (bool, error)
+ GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error)
+}
+
+type RetestethEthAPI interface {
+ SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error)
+ BlockNumber(ctx context.Context) (uint64, error)
+ GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error)
+ GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error)
+ GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error)
+ GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error)
+}
+
+type RetestethDebugAPI interface {
+ AccountRange(ctx context.Context,
+ blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
+ addressHash *math.HexOrDecimal256, maxResults uint64,
+ ) (AccountRangeResult, error)
+ StorageRangeAt(ctx context.Context,
+ blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
+ address common.Address,
+ begin *math.HexOrDecimal256, maxResults uint64,
+ ) (StorageRangeResult, error)
+}
+
+type RetestWeb3API interface {
+ ClientVersion(ctx context.Context) (string, error)
+}
+
+type RetestethAPI struct {
+ ethDb ethdb.Database
+ db state.Database
+ chainConfig *params.ChainConfig
+ author common.Address
+ extraData []byte
+ genesisHash common.Hash
+ engine *NoRewardEngine
+ blockchain *core.BlockChain
+ blockNumber uint64
+ txMap map[common.Address]map[uint64]*types.Transaction // Sender -> Nonce -> Transaction
+ txSenders map[common.Address]struct{} // Set of transaction senders
+ blockInterval uint64
+}
+
+type ChainParams struct {
+ SealEngine string `json:"sealEngine"`
+ Params CParamsParams `json:"params"`
+ Genesis CParamsGenesis `json:"genesis"`
+ Accounts map[common.Address]CParamsAccount `json:"accounts"`
+}
+
+type CParamsParams struct {
+ AccountStartNonce math.HexOrDecimal64 `json:"accountStartNonce"`
+ HomesteadForkBlock *math.HexOrDecimal64 `json:"homesteadForkBlock"`
+ EIP150ForkBlock *math.HexOrDecimal64 `json:"EIP150ForkBlock"`
+ EIP158ForkBlock *math.HexOrDecimal64 `json:"EIP158ForkBlock"`
+ DaoHardforkBlock *math.HexOrDecimal64 `json:"daoHardforkBlock"`
+ ByzantiumForkBlock *math.HexOrDecimal64 `json:"byzantiumForkBlock"`
+ ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"`
+ ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"`
+ ChainID *math.HexOrDecimal256 `json:"chainID"`
+ MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"`
+ TieBreakingGas bool `json:"tieBreakingGas"`
+ MinGasLimit math.HexOrDecimal64 `json:"minGasLimit"`
+ MaxGasLimit math.HexOrDecimal64 `json:"maxGasLimit"`
+ GasLimitBoundDivisor math.HexOrDecimal64 `json:"gasLimitBoundDivisor"`
+ MinimumDifficulty math.HexOrDecimal256 `json:"minimumDifficulty"`
+ DifficultyBoundDivisor math.HexOrDecimal256 `json:"difficultyBoundDivisor"`
+ DurationLimit math.HexOrDecimal256 `json:"durationLimit"`
+ BlockReward math.HexOrDecimal256 `json:"blockReward"`
+ NetworkID math.HexOrDecimal256 `json:"networkID"`
+}
+
+type CParamsGenesis struct {
+ Nonce math.HexOrDecimal64 `json:"nonce"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
+ MixHash *math.HexOrDecimal256 `json:"mixHash"`
+ Author common.Address `json:"author"`
+ Timestamp math.HexOrDecimal64 `json:"timestamp"`
+ ParentHash common.Hash `json:"parentHash"`
+ ExtraData hexutil.Bytes `json:"extraData"`
+ GasLimit math.HexOrDecimal64 `json:"gasLimit"`
+}
+
+type CParamsAccount struct {
+ Balance *math.HexOrDecimal256 `json:"balance"`
+ Precompiled *CPAccountPrecompiled `json:"precompiled"`
+ Code hexutil.Bytes `json:"code"`
+ Storage map[string]string `json:"storage"`
+ Nonce *math.HexOrDecimal64 `json:"nonce"`
+}
+
+type CPAccountPrecompiled struct {
+ Name string `json:"name"`
+ StartingBlock math.HexOrDecimal64 `json:"startingBlock"`
+ Linear *CPAPrecompiledLinear `json:"linear"`
+}
+
+type CPAPrecompiledLinear struct {
+ Base uint64 `json:"base"`
+ Word uint64 `json:"word"`
+}
+
+type AccountRangeResult struct {
+ AddressMap map[common.Hash]common.Address `json:"addressMap"`
+ NextKey common.Hash `json:"nextKey"`
+}
+
+type StorageRangeResult struct {
+ Complete bool `json:"complete"`
+ Storage map[common.Hash]SRItem `json:"storage"`
+}
+
+type SRItem struct {
+ Key string `json:"key"`
+ Value string `json:"value"`
+}
+
+type NoRewardEngine struct {
+ inner consensus.Engine
+ rewardsOn bool
+}
+
+func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) {
+ return e.inner.Author(header)
+}
+
+func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+ return e.inner.VerifyHeader(chain, header, seal)
+}
+
+func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+ return e.inner.VerifyHeaders(chain, headers, seals)
+}
+
+func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
+ return e.inner.VerifyUncles(chain, block)
+}
+
+func (e *NoRewardEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
+ return e.inner.VerifySeal(chain, header)
+}
+
+func (e *NoRewardEngine) Prepare(chain consensus.ChainReader, header *types.Header) error {
+ return e.inner.Prepare(chain, header)
+}
+
+func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
+ // Simply touch miner and uncle coinbase accounts
+ reward := big.NewInt(0)
+ for _, uncle := range uncles {
+ state.AddBalance(uncle.Coinbase, reward)
+ }
+ state.AddBalance(header.Coinbase, reward)
+}
+
+func (e *NoRewardEngine) Finalize(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
+ uncles []*types.Header) {
+ if e.rewardsOn {
+ e.inner.Finalize(chain, header, statedb, txs, uncles)
+ } else {
+ e.accumulateRewards(chain.Config(), statedb, header, uncles)
+ header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+ }
+}
+
+func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
+ uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
+ if e.rewardsOn {
+ return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts)
+ } else {
+ e.accumulateRewards(chain.Config(), statedb, header, uncles)
+ header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+
+ // Header seems complete, assemble into a block and return
+ return types.NewBlock(header, txs, uncles, receipts), nil
+ }
+}
+
+func (e *NoRewardEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+ return e.inner.Seal(chain, block, results, stop)
+}
+
+func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash {
+ return e.inner.SealHash(header)
+}
+
+func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+ return e.inner.CalcDifficulty(chain, time, parent)
+}
+
+func (e *NoRewardEngine) APIs(chain consensus.ChainReader) []rpc.API {
+ return e.inner.APIs(chain)
+}
+
+func (e *NoRewardEngine) Close() error {
+ return e.inner.Close()
+}
+
+func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) {
+ // Clean up
+ if api.blockchain != nil {
+ api.blockchain.Stop()
+ }
+ if api.engine != nil {
+ api.engine.Close()
+ }
+ if api.ethDb != nil {
+ api.ethDb.Close()
+ }
+ ethDb := rawdb.NewMemoryDatabase()
+ accounts := make(core.GenesisAlloc)
+ for address, account := range chainParams.Accounts {
+ balance := big.NewInt(0)
+ if account.Balance != nil {
+ balance.Set((*big.Int)(account.Balance))
+ }
+ var nonce uint64
+ if account.Nonce != nil {
+ nonce = uint64(*account.Nonce)
+ }
+ if account.Precompiled == nil || account.Balance != nil {
+ storage := make(map[common.Hash]common.Hash)
+ for k, v := range account.Storage {
+ storage[common.HexToHash(k)] = common.HexToHash(v)
+ }
+ accounts[address] = core.GenesisAccount{
+ Balance: balance,
+ Code: account.Code,
+ Nonce: nonce,
+ Storage: storage,
+ }
+ }
+ }
+ chainId := big.NewInt(1)
+ if chainParams.Params.ChainID != nil {
+ chainId.Set((*big.Int)(chainParams.Params.ChainID))
+ }
+ var (
+ homesteadBlock *big.Int
+ daoForkBlock *big.Int
+ eip150Block *big.Int
+ eip155Block *big.Int
+ eip158Block *big.Int
+ byzantiumBlock *big.Int
+ constantinopleBlock *big.Int
+ petersburgBlock *big.Int
+ )
+ if chainParams.Params.HomesteadForkBlock != nil {
+ homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock))
+ }
+ if chainParams.Params.DaoHardforkBlock != nil {
+ daoForkBlock = big.NewInt(int64(*chainParams.Params.DaoHardforkBlock))
+ }
+ if chainParams.Params.EIP150ForkBlock != nil {
+ eip150Block = big.NewInt(int64(*chainParams.Params.EIP150ForkBlock))
+ }
+ if chainParams.Params.EIP158ForkBlock != nil {
+ eip158Block = big.NewInt(int64(*chainParams.Params.EIP158ForkBlock))
+ eip155Block = eip158Block
+ }
+ if chainParams.Params.ByzantiumForkBlock != nil {
+ byzantiumBlock = big.NewInt(int64(*chainParams.Params.ByzantiumForkBlock))
+ }
+ if chainParams.Params.ConstantinopleForkBlock != nil {
+ constantinopleBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleForkBlock))
+ }
+ if chainParams.Params.ConstantinopleFixForkBlock != nil {
+ petersburgBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleFixForkBlock))
+ }
+ if constantinopleBlock != nil && petersburgBlock == nil {
+ petersburgBlock = big.NewInt(100000000000)
+ }
+ genesis := &core.Genesis{
+ Config: &params.ChainConfig{
+ ChainID: chainId,
+ HomesteadBlock: homesteadBlock,
+ DAOForkBlock: daoForkBlock,
+ DAOForkSupport: false,
+ EIP150Block: eip150Block,
+ EIP155Block: eip155Block,
+ EIP158Block: eip158Block,
+ ByzantiumBlock: byzantiumBlock,
+ ConstantinopleBlock: constantinopleBlock,
+ PetersburgBlock: petersburgBlock,
+ },
+ Nonce: uint64(chainParams.Genesis.Nonce),
+ Timestamp: uint64(chainParams.Genesis.Timestamp),
+ ExtraData: chainParams.Genesis.ExtraData,
+ GasLimit: uint64(chainParams.Genesis.GasLimit),
+ Difficulty: big.NewInt(0).Set((*big.Int)(chainParams.Genesis.Difficulty)),
+ Mixhash: common.BigToHash((*big.Int)(chainParams.Genesis.MixHash)),
+ Coinbase: chainParams.Genesis.Author,
+ ParentHash: chainParams.Genesis.ParentHash,
+ Alloc: accounts,
+ }
+ chainConfig, genesisHash, err := core.SetupGenesisBlock(ethDb, genesis)
+ if err != nil {
+ return false, err
+ }
+ fmt.Printf("Chain config: %v\n", chainConfig)
+
+ var inner consensus.Engine
+ switch chainParams.SealEngine {
+ case "NoProof", "NoReward":
+ inner = ethash.NewFaker()
+ case "Ethash":
+ inner = ethash.New(ethash.Config{
+ CacheDir: "ethash",
+ CachesInMem: 2,
+ CachesOnDisk: 3,
+ DatasetsInMem: 1,
+ DatasetsOnDisk: 2,
+ }, nil, false)
+ default:
+ return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine)
+ }
+ engine := &NoRewardEngine{inner: inner, rewardsOn: chainParams.SealEngine != "NoReward"}
+
+ blockchain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil)
+ if err != nil {
+ return false, err
+ }
+
+ api.chainConfig = chainConfig
+ api.genesisHash = genesisHash
+ api.author = chainParams.Genesis.Author
+ api.extraData = chainParams.Genesis.ExtraData
+ api.ethDb = ethDb
+ api.engine = engine
+ api.blockchain = blockchain
+ api.db = state.NewDatabase(api.ethDb)
+ api.blockNumber = 0
+ api.txMap = make(map[common.Address]map[uint64]*types.Transaction)
+ api.txSenders = make(map[common.Address]struct{})
+ api.blockInterval = 0
+ return true, nil
+}
+
+func (api *RetestethAPI) SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) {
+ tx := new(types.Transaction)
+ if err := rlp.DecodeBytes(rawTx, tx); err != nil {
+ // Return nil is not by mistake - some tests include sending transaction where gasLimit overflows uint64
+ return common.Hash{}, nil
+ }
+ signer := types.MakeSigner(api.chainConfig, big.NewInt(int64(api.blockNumber)))
+ sender, err := types.Sender(signer, tx)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ if nonceMap, ok := api.txMap[sender]; ok {
+ nonceMap[tx.Nonce()] = tx
+ } else {
+ nonceMap = make(map[uint64]*types.Transaction)
+ nonceMap[tx.Nonce()] = tx
+ api.txMap[sender] = nonceMap
+ }
+ api.txSenders[sender] = struct{}{}
+ return tx.Hash(), nil
+}
+
+func (api *RetestethAPI) MineBlocks(ctx context.Context, number uint64) (bool, error) {
+ for i := 0; i < int(number); i++ {
+ if err := api.mineBlock(); err != nil {
+ return false, err
+ }
+ }
+ fmt.Printf("Mined %d blocks\n", number)
+ return true, nil
+}
+
+func (api *RetestethAPI) mineBlock() error {
+ parentHash := rawdb.ReadCanonicalHash(api.ethDb, api.blockNumber)
+ parent := rawdb.ReadBlock(api.ethDb, parentHash, api.blockNumber)
+ var timestamp uint64
+ if api.blockInterval == 0 {
+ timestamp = uint64(time.Now().Unix())
+ } else {
+ timestamp = parent.Time() + api.blockInterval
+ }
+ gasLimit := core.CalcGasLimit(parent, 9223372036854775807, 9223372036854775807)
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: big.NewInt(int64(api.blockNumber + 1)),
+ GasLimit: gasLimit,
+ Extra: api.extraData,
+ Time: timestamp,
+ }
+ header.Coinbase = api.author
+ if api.engine != nil {
+ api.engine.Prepare(api.blockchain, header)
+ }
+ // If we are care about TheDAO hard-fork check whether to override the extra-data or not
+ if daoBlock := api.chainConfig.DAOForkBlock; daoBlock != nil {
+ // Check whether the block is among the fork extra-override range
+ limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
+ if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 {
+ // Depending whether we support or oppose the fork, override differently
+ if api.chainConfig.DAOForkSupport {
+ header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
+ } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {
+ header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data
+ }
+ }
+ }
+ statedb, err := api.blockchain.StateAt(parent.Root())
+ if err != nil {
+ return err
+ }
+ if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 {
+ misc.ApplyDAOHardFork(statedb)
+ }
+ gasPool := new(core.GasPool).AddGas(header.GasLimit)
+ txCount := 0
+ var txs []*types.Transaction
+ var receipts []*types.Receipt
+ var coalescedLogs []*types.Log
+ var blockFull = gasPool.Gas() < params.TxGas
+ for address := range api.txSenders {
+ if blockFull {
+ break
+ }
+ m := api.txMap[address]
+ for nonce := statedb.GetNonce(address); ; nonce++ {
+ if tx, ok := m[nonce]; ok {
+ // Try to apply transactions to the state
+ statedb.Prepare(tx.Hash(), common.Hash{}, txCount)
+ snap := statedb.Snapshot()
+
+ receipt, _, err := core.ApplyTransaction(
+ api.chainConfig,
+ api.blockchain,
+ &api.author,
+ gasPool,
+ statedb,
+ header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(),
+ )
+ if err != nil {
+ statedb.RevertToSnapshot(snap)
+ break
+ }
+ txs = append(txs, tx)
+ receipts = append(receipts, receipt)
+ coalescedLogs = append(coalescedLogs, receipt.Logs...)
+ delete(m, nonce)
+ if len(m) == 0 {
+ // Last tx for the sender
+ delete(api.txMap, address)
+ delete(api.txSenders, address)
+ }
+ txCount++
+ if gasPool.Gas() < params.TxGas {
+ blockFull = true
+ break
+ }
+ } else {
+ break // Gap in the nonces
+ }
+ }
+ }
+ block, err := api.engine.FinalizeAndAssemble(api.blockchain, header, statedb, txs, []*types.Header{}, receipts)
+ if err != nil {
+ return err
+ }
+ return api.importBlock(block)
+}
+
+func (api *RetestethAPI) importBlock(block *types.Block) error {
+ if _, err := api.blockchain.InsertChain([]*types.Block{block}); err != nil {
+ return err
+ }
+ api.blockNumber = block.NumberU64()
+ fmt.Printf("Imported block %d\n", block.NumberU64())
+ return nil
+}
+
+func (api *RetestethAPI) ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) {
+ api.blockInterval = interval
+ return true, nil
+}
+
+func (api *RetestethAPI) ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) {
+ block := new(types.Block)
+ if err := rlp.DecodeBytes(rawBlock, block); err != nil {
+ return common.Hash{}, err
+ }
+ fmt.Printf("Importing block %d with parent hash: %x, genesisHash: %x\n", block.NumberU64(), block.ParentHash(), api.genesisHash)
+ if err := api.importBlock(block); err != nil {
+ return common.Hash{}, err
+ }
+ return block.Hash(), nil
+}
+
+func (api *RetestethAPI) RewindToBlock(ctx context.Context, newHead uint64) (bool, error) {
+ if err := api.blockchain.SetHead(newHead); err != nil {
+ return false, err
+ }
+ api.blockNumber = newHead
+ return true, nil
+}
+
+var emptyListHash common.Hash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
+
+func (api *RetestethAPI) GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) {
+ receipt, _, _, _ := rawdb.ReadReceipt(api.ethDb, txHash, api.chainConfig)
+ if receipt == nil {
+ return emptyListHash, nil
+ } else {
+ if logListRlp, err := rlp.EncodeToBytes(receipt.Logs); err != nil {
+ return common.Hash{}, err
+ } else {
+ return common.BytesToHash(crypto.Keccak256(logListRlp)), nil
+ }
+ }
+}
+
+func (api *RetestethAPI) BlockNumber(ctx context.Context) (uint64, error) {
+ //fmt.Printf("BlockNumber, response: %d\n", api.blockNumber)
+ return api.blockNumber, nil
+}
+
+func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) {
+ block := api.blockchain.GetBlockByNumber(uint64(blockNr))
+ if block != nil {
+ response, err := RPCMarshalBlock(block, true, fullTx)
+ if err != nil {
+ return nil, err
+ }
+ response["author"] = response["miner"]
+ response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), uint64(blockNr)))
+ return response, err
+ }
+ return nil, fmt.Errorf("block %d not found", blockNr)
+}
+
+func (api *RetestethAPI) AccountRange(ctx context.Context,
+ blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
+ addressHash *math.HexOrDecimal256, maxResults uint64,
+) (AccountRangeResult, error) {
+ var (
+ header *types.Header
+ block *types.Block
+ )
+ if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 {
+ blockHash := common.BigToHash((*big.Int)(blockHashOrNumber))
+ header = api.blockchain.GetHeaderByHash(blockHash)
+ block = api.blockchain.GetBlockByHash(blockHash)
+ //fmt.Printf("Account range: %x, txIndex %d, start: %x, maxResults: %d\n", blockHash, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults)
+ } else {
+ blockNumber := (*big.Int)(blockHashOrNumber).Uint64()
+ header = api.blockchain.GetHeaderByNumber(blockNumber)
+ block = api.blockchain.GetBlockByNumber(blockNumber)
+ //fmt.Printf("Account range: %d, txIndex %d, start: %x, maxResults: %d\n", blockNumber, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults)
+ }
+ parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash)
+ var root common.Hash
+ var statedb *state.StateDB
+ var err error
+ if parentHeader == nil || int(txIndex) >= len(block.Transactions()) {
+ root = header.Root
+ statedb, err = api.blockchain.StateAt(root)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ } else {
+ root = parentHeader.Root
+ statedb, err = api.blockchain.StateAt(root)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ // Recompute transactions up to the target index.
+ signer := types.MakeSigner(api.blockchain.Config(), block.Number())
+ for idx, tx := range block.Transactions() {
+ // Assemble the transaction call message and return if the requested offset
+ msg, _ := tx.AsMessage(signer)
+ context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
+ // Not yet the searched for transaction, execute on top of the current state
+ vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
+ if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+ return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+ }
+ // Ensure any modifications are committed to the state
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
+ if idx == int(txIndex) {
+ // This is to make sure root can be opened by OpenTrie
+ root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number()))
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ break
+ }
+ }
+ }
+ accountTrie, err := statedb.Database().OpenTrie(root)
+ if err != nil {
+ return AccountRangeResult{}, err
+ }
+ it := trie.NewIterator(accountTrie.NodeIterator(common.BigToHash((*big.Int)(addressHash)).Bytes()))
+ result := AccountRangeResult{AddressMap: make(map[common.Hash]common.Address)}
+ for i := 0; /*i < int(maxResults) && */ it.Next(); i++ {
+ if preimage := accountTrie.GetKey(it.Key); preimage != nil {
+ result.AddressMap[common.BytesToHash(it.Key)] = common.BytesToAddress(preimage)
+ //fmt.Printf("%x: %x\n", it.Key, preimage)
+ } else {
+ //fmt.Printf("could not find preimage for %x\n", it.Key)
+ }
+ }
+ //fmt.Printf("Number of entries returned: %d\n", len(result.AddressMap))
+ // Add the 'next key' so clients can continue downloading.
+ if it.Next() {
+ next := common.BytesToHash(it.Key)
+ result.NextKey = next
+ }
+ return result, nil
+}
+
+func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) {
+ //fmt.Printf("GetBalance %x, block %d\n", address, blockNr)
+ header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
+ statedb, err := api.blockchain.StateAt(header.Root)
+ if err != nil {
+ return nil, err
+ }
+ return (*math.HexOrDecimal256)(statedb.GetBalance(address)), nil
+}
+
+func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) {
+ header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
+ statedb, err := api.blockchain.StateAt(header.Root)
+ if err != nil {
+ return nil, err
+ }
+ return statedb.GetCode(address), nil
+}
+
+func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) {
+ header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
+ statedb, err := api.blockchain.StateAt(header.Root)
+ if err != nil {
+ return 0, err
+ }
+ return statedb.GetNonce(address), nil
+}
+
+func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
+ blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
+ address common.Address,
+ begin *math.HexOrDecimal256, maxResults uint64,
+) (StorageRangeResult, error) {
+ var (
+ header *types.Header
+ block *types.Block
+ )
+ if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 {
+ blockHash := common.BigToHash((*big.Int)(blockHashOrNumber))
+ header = api.blockchain.GetHeaderByHash(blockHash)
+ block = api.blockchain.GetBlockByHash(blockHash)
+ //fmt.Printf("Storage range: %x, txIndex %d, addr: %x, start: %x, maxResults: %d\n",
+ // blockHash, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults)
+ } else {
+ blockNumber := (*big.Int)(blockHashOrNumber).Uint64()
+ header = api.blockchain.GetHeaderByNumber(blockNumber)
+ block = api.blockchain.GetBlockByNumber(blockNumber)
+ //fmt.Printf("Storage range: %d, txIndex %d, addr: %x, start: %x, maxResults: %d\n",
+ // blockNumber, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults)
+ }
+ parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash)
+ var root common.Hash
+ var statedb *state.StateDB
+ var err error
+ if parentHeader == nil || int(txIndex) >= len(block.Transactions()) {
+ root = header.Root
+ statedb, err = api.blockchain.StateAt(root)
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ } else {
+ root = parentHeader.Root
+ statedb, err = api.blockchain.StateAt(root)
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ // Recompute transactions up to the target index.
+ signer := types.MakeSigner(api.blockchain.Config(), block.Number())
+ for idx, tx := range block.Transactions() {
+ // Assemble the transaction call message and return if the requested offset
+ msg, _ := tx.AsMessage(signer)
+ context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
+ // Not yet the searched for transaction, execute on top of the current state
+ vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
+ if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+ return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+ }
+ // Ensure any modifications are committed to the state
+ // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+ _ = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
+ if idx == int(txIndex) {
+ // This is to make sure root can be opened by OpenTrie
+ _, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number()))
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ }
+ }
+ }
+ storageTrie := statedb.StorageTrie(address)
+ it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes()))
+ result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)}
+ for i := 0; /*i < int(maxResults) && */ it.Next(); i++ {
+ if preimage := storageTrie.GetKey(it.Key); preimage != nil {
+ key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage))
+ v, _, err := rlp.SplitString(it.Value)
+ if err != nil {
+ return StorageRangeResult{}, err
+ }
+ value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v))
+ ks, _ := key.MarshalText()
+ vs, _ := value.MarshalText()
+ if len(ks)%2 != 0 {
+ ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...)
+ }
+ if len(vs)%2 != 0 {
+ vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...)
+ }
+ result.Storage[common.BytesToHash(it.Key)] = SRItem{
+ Key: string(ks),
+ Value: string(vs),
+ }
+ //fmt.Printf("Key: %s, Value: %s\n", ks, vs)
+ } else {
+ //fmt.Printf("Did not find preimage for %x\n", it.Key)
+ }
+ }
+ if it.Next() {
+ result.Complete = false
+ } else {
+ result.Complete = true
+ }
+ return result, nil
+}
+
+func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) {
+ return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil
+}
+
+// splitAndTrim splits input separated by a comma
+// and trims excessive white space from the substrings.
+func splitAndTrim(input string) []string {
+ result := strings.Split(input, ",")
+ for i, r := range result {
+ result[i] = strings.TrimSpace(r)
+ }
+ return result
+}
+
+func retesteth(ctx *cli.Context) error {
+ log.Info("Welcome to retesteth!")
+ // register signer API with server
+ var (
+ extapiURL string
+ )
+ apiImpl := &RetestethAPI{}
+ var testApi RetestethTestAPI = apiImpl
+ var ethApi RetestethEthAPI = apiImpl
+ var debugApi RetestethDebugAPI = apiImpl
+ var web3Api RetestWeb3API = apiImpl
+ rpcAPI := []rpc.API{
+ {
+ Namespace: "test",
+ Public: true,
+ Service: testApi,
+ Version: "1.0",
+ },
+ {
+ Namespace: "eth",
+ Public: true,
+ Service: ethApi,
+ Version: "1.0",
+ },
+ {
+ Namespace: "debug",
+ Public: true,
+ Service: debugApi,
+ Version: "1.0",
+ },
+ {
+ Namespace: "web3",
+ Public: true,
+ Service: web3Api,
+ Version: "1.0",
+ },
+ }
+ vhosts := splitAndTrim(ctx.GlobalString(utils.RPCVirtualHostsFlag.Name))
+ cors := splitAndTrim(ctx.GlobalString(utils.RPCCORSDomainFlag.Name))
+
+ // start http server
+ httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.RPCListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name))
+ listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"test", "eth", "debug", "web3"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
+ if err != nil {
+ utils.Fatalf("Could not start RPC api: %v", err)
+ }
+ extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
+ log.Info("HTTP endpoint opened", "url", extapiURL)
+
+ defer func() {
+ listener.Close()
+ log.Info("HTTP endpoint closed", "url", httpEndpoint)
+ }()
+
+ abortChan := make(chan os.Signal)
+ signal.Notify(abortChan, os.Interrupt)
+
+ sig := <-abortChan
+ log.Info("Exiting...", "signal", sig)
+ return nil
+}