Fix for Istanbul sync (EIP-2200) (#271)

* Started adding tx tracing, try to fix GetCommittedState

* Fix lint

* Fix lint

* Fix lint

* Fix the collision tests

* Switch to boltDB with Yield

Yield boltdb transaction regularly to not block memory map resizing

* Added test

* gofmt -w

* Fix lint

* Revert "Switch to boltDB with Yield"

This reverts commit b42650d6e477ecd0f19fd017a29744384c249cac.

* Fix lint
This commit is contained in:
ledgerwatch 2019-12-17 11:56:38 +00:00 committed by GitHub
parent 8ac2bef75b
commit 6d98798700
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 138 additions and 57 deletions

View File

@ -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: &params.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)
}
}

View File

@ -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 {

View File

@ -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
}