diff --git a/core/state/database_test.go b/core/state/database_test.go index 811a7b1bdf84e226e30b63b87cb7a99e9bbbc905..3fbe6c941a3366e8faf4100fe284f1ec8eb1d580 100644 --- a/core/state/database_test.go +++ b/core/state/database_test.go @@ -484,3 +484,93 @@ func TestReproduceCrash(t *testing.T) { t.Errorf("Expected empty list of prunables, got:\n %s", prunables) } } +func TestEip2200Gas(t *testing.T) { + // Configure and generate a sample block chain + var ( + db = ethdb.NewMemDatabase() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &core.Genesis{ + Config: ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + EIP150Block: new(big.Int), + EIP155Block: new(big.Int), + EIP158Block: big.NewInt(1), + ByzantiumBlock: big.NewInt(1), + PetersburgBlock: big.NewInt(1), + ConstantinopleBlock: big.NewInt(1), + IstanbulBlock: big.NewInt(1), + }, + Alloc: core.GenesisAlloc{ + address: {Balance: funds}, + }, + } + genesis = gspec.MustCommit(db) + ) + + engine := ethash.NewFaker() + blockchain, err := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil) + if err != nil { + t.Fatal(err) + } + + blockchain.EnableReceipts(true) + + contractBackend := backends.NewSimulatedBackendWithConfig(gspec.Alloc, gspec.Config, gspec.GasLimit) + transactOpts := bind.NewKeyedTransactor(key) + transactOpts.GasLimit = 1000000 + + var contractAddress common.Address + var selfDestruct *contracts.Selfdestruct + + ctx := blockchain.WithContext(context.Background(), big.NewInt(genesis.Number().Int64()+1)) + // Here we generate 1 block with 2 transactions, first creates a contract with some initial values in the + // It activates the SSTORE pricing rules specific to EIP-2200 (istanbul) + blocks, _ := core.GenerateChain(ctx, gspec.Config, genesis, engine, db.MemCopy(), 3, func(i int, block *core.BlockGen) { + var tx *types.Transaction + + switch i { + case 0: + contractAddress, tx, selfDestruct, err = contracts.DeploySelfdestruct(transactOpts, contractBackend) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + + transactOpts.GasPrice = big.NewInt(1) + tx, err = selfDestruct.Change(transactOpts) + if err != nil { + t.Fatal(err) + } + block.AddTx(tx) + } + contractBackend.Commit() + }) + + st, _, _ := blockchain.State() + if !st.Exist(address) { + t.Error("expected account to exist") + } + if st.Exist(contractAddress) { + t.Error("expected contractAddress to not exist before block 0", contractAddress.String()) + } + balanceBefore := st.GetBalance(address) + + // BLOCK 1 + if _, err = blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { + t.Fatal(err) + } + + st, _, _ = blockchain.State() + if !st.Exist(contractAddress) { + t.Error("expected contractAddress to exist at the block 1", contractAddress.String()) + } + balanceAfter := st.GetBalance(address) + gasSpent := big.NewInt(0).Sub(balanceBefore, balanceAfter) + expectedGasSpent := big.NewInt(192245) // In the incorrect version, it is 179645 + if gasSpent.Cmp(expectedGasSpent) != 0 { + t.Errorf("Expected gas spent: %d, got %d", expectedGasSpent, gasSpent) + } +} diff --git a/core/state/state_object.go b/core/state/state_object.go index e88c962c087f8cdad916009bcdeb6094c7e2682f..019fc0e2f1fb3f51831c27881b496b3350409627 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -81,7 +81,6 @@ type stateObject struct { code Code // contract bytecode, which gets set when code is loaded originStorage Storage // Storage cache of original entries to dedup rewrites - pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block blockOriginStorage Storage dirtyStorage Storage // Storage entries that need to be flushed to disk fakeStorage Storage // Fake storage which constructed by caller for debugging purpose. @@ -109,7 +108,6 @@ func newObject(db *IntraBlockState, address common.Address, data, original *acco db: db, address: address, originStorage: make(Storage), - pendingStorage: make(Storage), blockOriginStorage: make(Storage), dirtyStorage: make(Storage), } @@ -168,9 +166,6 @@ func (so *stateObject) GetState(key common.Hash) common.Hash { // GetCommittedState retrieves a value from the committed account storage trie. func (so *stateObject) GetCommittedState(key common.Hash) common.Hash { - if so.created { - return common.Hash{} - } // If we have the original value cached, return that { value, cached := so.originStorage[key] @@ -178,6 +173,9 @@ func (so *stateObject) GetCommittedState(key common.Hash) common.Hash { return value } } + if so.created { + return common.Hash{} + } // Load from DB in case it is missing. enc, err := so.db.stateReader.ReadAccountStorage(so.address, so.data.GetIncarnation(), &key) if err != nil { @@ -231,17 +229,6 @@ func (so *stateObject) setState(key, value common.Hash) { so.dirtyStorage[key] = value } -// finalise moves all dirty storage slots into the pending area to be hashed or -// committed later. It is invoked at the end of every transaction. -func (so *stateObject) finalise() { - for key, value := range so.dirtyStorage { - so.pendingStorage[key] = value - } - if len(so.dirtyStorage) > 0 { - so.dirtyStorage = make(Storage) - } -} - // updateTrie writes cached storage modifications into the object's storage trie. func (so *stateObject) updateTrie(ctx context.Context, stateWriter StateWriter) error { for key, value := range so.dirtyStorage { diff --git a/core/state_processor.go b/core/state_processor.go index 4cf4d81be0b53e7ffea07aed6c141e7757300c0c..0acb49ad5928a867beabd0e94674d52a5b42d5ec 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,9 +17,9 @@ package core import ( - //"os" - //"encoding/json" - //"bytes" + "bytes" + "encoding/json" + "os" "fmt" @@ -41,9 +41,10 @@ import ( // // StateProcessor implements Processor. type StateProcessor struct { - config *params.ChainConfig // Chain configuration options - bc *BlockChain // Canonical block chain - engine consensus.Engine // Consensus engine used for block rewards + config *params.ChainConfig // Chain configuration options + bc *BlockChain // Canonical block chain + engine consensus.Engine // Consensus engine used for block rewards + txTraceHash []byte // Hash of the transaction to trace (or nil if there nothing to trace) } // NewStateProcessor initialises a new StateProcessor. @@ -55,6 +56,11 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen } } +// SetTxTraceHash allows setting the hash of the transaction to trace +func (p *StateProcessor) SetTxTraceHash(txTraceHash common.Hash) { + p.txTraceHash = txTraceHash[:] +} + // StructLogRes stores a structured log emitted by the EVM while replaying a // transaction in debug mode type StructLogRes struct { @@ -69,7 +75,7 @@ type StructLogRes struct { Storage *map[string]string `json:"storage,omitempty"` } -// formatLogs formats EVM returned structured logs for json output +// FormatLogs formats EVM returned structured logs for json output func FormatLogs(logs []vm.StructLog) []StructLogRes { formatted := make([]StructLogRes, len(logs)) for index, trace := range logs { @@ -128,8 +134,39 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.IntraBlockSt // Iterate over and process the individual transactions tds.StartNewBuffer() for i, tx := range block.Transactions() { - statedb.Prepare(tx.Hash(), block.Hash(), i) + txHash := tx.Hash() + statedb.Prepare(txHash, block.Hash(), i) + writeTrace := false + if !cfg.Debug && p.txTraceHash != nil && bytes.Equal(p.txTraceHash, txHash[:]) { + // This code is useful when debugging a certain transaction. If uncommented, together with the code + // at the end of this function, after the execution of transaction with given hash, the file + // structlogs.txt will contain full trace of the transactin in JSON format. This can be compared + // to another trace, obtained from the correct version of the turbo-geth or go-ethereum + cfg.Tracer = vm.NewStructLogger(&vm.LogConfig{}) + cfg.Debug = true + writeTrace = true + } receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, tds.TrieStateWriter(), header, tx, usedGas, cfg) + // This code is useful when debugging a certain transaction. If uncommented, together with the code + // at the end of this function, after the execution of transaction with given hash, the file + // structlogs.txt will contain full trace of the transactin in JSON format. This can be compared + // to another trace, obtained from the correct version of the turbo-geth or go-ethereum + if writeTrace { + w, err1 := os.Create(fmt.Sprintf("txtrace_%x.txt", p.txTraceHash)) + if err1 != nil { + panic(err1) + } + encoder := json.NewEncoder(w) + logs := FormatLogs(cfg.Tracer.(*vm.StructLogger).StructLogs()) + if err2 := encoder.Encode(logs); err2 != nil { + panic(err2) + } + if err2 := w.Close(); err2 != nil { + panic(err2) + } + cfg.Debug = false + cfg.Tracer = nil + } if err != nil { return nil, nil, 0, err } @@ -163,17 +200,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.IntraBlockSt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.IntraBlockState, stateWriter state.StateWriter, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { - /* - // This code is useful when debugging a certain transaction. If uncommented, together with the code - // at the end of this function, after the execution of transaction with given hash, the file - // structlogs.txt will contain full trace of the transactin in JSON format. This can be compared - // to another trace, obtained from the correct version of the turbo-geth or go-ethereum - var h common.Hash = tx.Hash() - if bytes.Equal(h[:], common.FromHex("0x340acfd967a744646ebdcfa2cab9b457a1d42224598d33051047ededdd24caa1")) { - cfg.Tracer = vm.NewStructLogger(&vm.LogConfig{}) - cfg.Debug = true - } - */ msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, err @@ -186,28 +212,6 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo vmenv := vm.NewEVM(context, statedb, config, cfg) // Apply the transaction to the current state (included in the env) _, gas, failed, err := ApplyMessage(vmenv, msg, gp) - /* - // This code is useful when debugging a certain transaction. If uncommented, together with the code - // at the end of this function, after the execution of transaction with given hash, the file - // structlogs.txt will contain full trace of the transactin in JSON format. This can be compared - // to another trace, obtained from the correct version of the turbo-geth or go-ethereum - if cfg.Tracer != nil { - w, err := os.Create("structlogs.txt") - if err != nil { - panic(err) - } - encoder := json.NewEncoder(w) - logs := FormatLogs(cfg.Tracer.(*vm.StructLogger).StructLogs()) - if err := encoder.Encode(logs); err != nil { - panic(err) - } - if err := w.Close(); err != nil { - panic(err) - } - cfg.Debug = false - cfg.Tracer = nil - } - */ if err != nil { return nil, err }