forked from cerc-io/plugeth
2045a2bba3
* all: core: split vm.Config into BlockConfig and TxConfig * core: core/vm: reset EVM between tx in block instead of creating new * core/vm: added docs
919 lines
32 KiB
Go
919 lines
32 KiB
Go
// 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 main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/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)
|
|
GetBlockByHash(ctx context.Context, blockHash common.Hash, 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
|
|
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"`
|
|
IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"`
|
|
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.ChainHeaderReader, header *types.Header, seal bool) error {
|
|
return e.inner.VerifyHeader(chain, header, seal)
|
|
}
|
|
|
|
func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainHeaderReader, 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.ChainHeaderReader, header *types.Header) error {
|
|
return e.inner.VerifySeal(chain, header)
|
|
}
|
|
|
|
func (e *NoRewardEngine) Prepare(chain consensus.ChainHeaderReader, 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.ChainHeaderReader, 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.ChainHeaderReader, 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, new(trie.Trie)), nil
|
|
}
|
|
}
|
|
|
|
func (e *NoRewardEngine) Seal(chain consensus.ChainHeaderReader, 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.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
|
return e.inner.CalcDifficulty(chain, time, parent)
|
|
}
|
|
|
|
func (e *NoRewardEngine) APIs(chain consensus.ChainHeaderReader) []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
|
|
istanbulBlock *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)
|
|
}
|
|
if chainParams.Params.IstanbulBlock != nil {
|
|
istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock))
|
|
}
|
|
|
|
genesis := &core.Genesis{
|
|
Config: ¶ms.ChainConfig{
|
|
ChainID: chainId,
|
|
HomesteadBlock: homesteadBlock,
|
|
DAOForkBlock: daoForkBlock,
|
|
DAOForkSupport: true,
|
|
EIP150Block: eip150Block,
|
|
EIP155Block: eip155Block,
|
|
EIP158Block: eip158Block,
|
|
ByzantiumBlock: byzantiumBlock,
|
|
ConstantinopleBlock: constantinopleBlock,
|
|
PetersburgBlock: petersburgBlock,
|
|
IstanbulBlock: istanbulBlock,
|
|
},
|
|
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,
|
|
CachesLockMmap: false,
|
|
DatasetsInMem: 1,
|
|
DatasetsOnDisk: 2,
|
|
DatasetsLockMmap: false,
|
|
}, 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, 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.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.currentNumber())))
|
|
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) currentNumber() uint64 {
|
|
if current := api.blockchain.CurrentBlock(); current != nil {
|
|
return current.NumberU64()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (api *RetestethAPI) mineBlock() error {
|
|
number := api.currentNumber()
|
|
parentHash := rawdb.ReadCanonicalHash(api.ethDb, number)
|
|
parent := rawdb.ReadBlock(api.ethDb, parentHash, number)
|
|
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(number + 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 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)
|
|
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
|
|
}
|
|
fmt.Printf("Imported block %d, head is %d\n", block.NumberU64(), api.currentNumber())
|
|
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
|
|
}
|
|
// When we rewind, the transaction pool should be cleaned out.
|
|
api.txMap = make(map[common.Address]map[uint64]*types.Transaction)
|
|
api.txSenders = make(map[common.Address]struct{})
|
|
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) {
|
|
return api.currentNumber(), 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) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
|
block := api.blockchain.GetBlockByHash(blockHash)
|
|
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(), block.Number().Uint64()))
|
|
return response, err
|
|
}
|
|
return nil, fmt.Errorf("block 0x%x not found", blockHash)
|
|
}
|
|
|
|
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())
|
|
context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil)
|
|
for idx, tx := range block.Transactions() {
|
|
// Assemble the transaction call message and return if the requested offset
|
|
msg, _ := tx.AsMessage(signer)
|
|
txContext := core.NewEVMTxContext(msg)
|
|
// Not yet the searched for transaction, execute on top of the current state
|
|
vmenv := vm.NewEVM(context, txContext, 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("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())
|
|
context := core.NewEVMBlockContext(block.Header(), api.blockchain, nil)
|
|
for idx, tx := range block.Transactions() {
|
|
// Assemble the transaction call message and return if the requested offset
|
|
msg, _ := tx.AsMessage(signer)
|
|
txContext := core.NewEVMTxContext(msg)
|
|
// Not yet the searched for transaction, execute on top of the current state
|
|
vmenv := vm.NewEVM(context, txContext, 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),
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
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 := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPVirtualHostsFlag.Name))
|
|
cors := utils.SplitAndTrim(ctx.GlobalString(utils.HTTPCORSDomainFlag.Name))
|
|
|
|
// register apis and create handler stack
|
|
srv := rpc.NewServer()
|
|
err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false)
|
|
if err != nil {
|
|
utils.Fatalf("Could not register RPC apis: %w", err)
|
|
}
|
|
handler := node.NewHTTPHandlerStack(srv, cors, vhosts)
|
|
|
|
// start http server
|
|
var RetestethHTTPTimeouts = rpc.HTTPTimeouts{
|
|
ReadTimeout: 120 * time.Second,
|
|
WriteTimeout: 120 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.HTTPListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name))
|
|
httpServer, _, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler)
|
|
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() {
|
|
// Don't bother imposing a timeout here.
|
|
httpServer.Shutdown(context.Background())
|
|
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
|
}()
|
|
|
|
abortChan := make(chan os.Signal, 11)
|
|
signal.Notify(abortChan, os.Interrupt)
|
|
|
|
sig := <-abortChan
|
|
log.Info("Exiting...", "signal", sig)
|
|
return nil
|
|
}
|