// (c) 2019-2021, Lux Partners Limited.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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.
//
// The go-ethereum library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package state

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"math/big"
	"math/rand"
	"reflect"
	"strings"
	"sync"
	"testing"
	"testing/quick"

	"github.com/luxfi/coreth/core/rawdb"
	"github.com/luxfi/coreth/core/state/snapshot"
	"github.com/luxfi/coreth/core/types"
	"github.com/luxfi/coreth/trie"
	"github.com/luxfi/coreth/trie/triedb/hashdb"
	"github.com/luxfi/coreth/trie/triedb/pathdb"
	"github.com/luxfi/coreth/trie/trienode"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/holiman/uint256"
)

// Tests that updating a state trie does not leak any database writes prior to
// actually committing the state.
func TestUpdateLeaks(t *testing.T) {
	// Create an empty state database
	var (
		db  = rawdb.NewMemoryDatabase()
		tdb = trie.NewDatabase(db, nil)
	)
	state, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(db, tdb), nil)

	// Update it with some accounts
	for i := byte(0); i < 255; i++ {
		addr := common.BytesToAddress([]byte{i})
		state.AddBalance(addr, big.NewInt(int64(11*i)))
		state.SetNonce(addr, uint64(42*i))
		if i%2 == 0 {
			state.SetState(addr, common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i}))
		}
		if i%3 == 0 {
			state.SetCode(addr, []byte{i, i, i, i, i})
		}
	}

	root := state.IntermediateRoot(false)
	if err := tdb.Commit(root, false); err != nil {
		t.Errorf("can not commit trie %v to persistent database", root.Hex())
	}

	// Ensure that no data was leaked into the database
	it := db.NewIterator(nil, nil)
	for it.Next() {
		t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value())
	}
	it.Release()
}

// Tests that no intermediate state of an object is stored into the database,
// only the one right before the commit.
func TestIntermediateLeaks(t *testing.T) {
	// Create two state databases, one transitioning to the final state, the other final from the beginning
	transDb := rawdb.NewMemoryDatabase()
	finalDb := rawdb.NewMemoryDatabase()
	transNdb := trie.NewDatabase(transDb, nil)
	finalNdb := trie.NewDatabase(finalDb, nil)
	transState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(transDb, transNdb), nil)
	finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil)

	modify := func(state *StateDB, addr common.Address, i, tweak byte) {
		state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak)))
		state.SetNonce(addr, uint64(42*i+tweak))
		if i%2 == 0 {
			state.SetState(addr, common.Hash{i, i, i, 0}, common.Hash{})
			state.SetState(addr, common.Hash{i, i, i, tweak}, common.Hash{i, i, i, i, tweak})
		}
		if i%3 == 0 {
			state.SetCode(addr, []byte{i, i, i, i, i, tweak})
		}
	}

	// Modify the transient state.
	for i := byte(0); i < 255; i++ {
		modify(transState, common.Address{i}, i, 0)
	}
	// Write modifications to trie.
	transState.IntermediateRoot(false)

	// Overwrite all the data with new values in the transient database.
	for i := byte(0); i < 255; i++ {
		modify(transState, common.Address{i}, i, 99)
		modify(finalState, common.Address{i}, i, 99)
	}

	// Commit and cross check the databases.
	transRoot, err := transState.Commit(0, false, false)
	if err != nil {
		t.Fatalf("failed to commit transition state: %v", err)
	}
	if err = transNdb.Commit(transRoot, false); err != nil {
		t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
	}

	finalRoot, err := finalState.Commit(0, false, false)
	if err != nil {
		t.Fatalf("failed to commit final state: %v", err)
	}
	if err = finalNdb.Commit(finalRoot, false); err != nil {
		t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
	}

	it := finalDb.NewIterator(nil, nil)
	for it.Next() {
		key, fvalue := it.Key(), it.Value()
		tvalue, err := transDb.Get(key)
		if err != nil {
			t.Errorf("entry missing from the transition database: %x -> %x", key, fvalue)
		}
		if !bytes.Equal(fvalue, tvalue) {
			t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue)
		}
	}
	it.Release()

	it = transDb.NewIterator(nil, nil)
	for it.Next() {
		key, tvalue := it.Key(), it.Value()
		fvalue, err := finalDb.Get(key)
		if err != nil {
			t.Errorf("extra entry in the transition database: %x -> %x", key, it.Value())
		}
		if !bytes.Equal(fvalue, tvalue) {
			t.Errorf("value mismatch at key %x: %x in transition database, %x in final database", key, tvalue, fvalue)
		}
	}
}

// TestCopy tests that copying a StateDB object indeed makes the original and
// the copy independent of each other. This test is a regression test against
// https://github.com/ethereum/go-ethereum/pull/15549.
func TestCopy(t *testing.T) {
	// Create a random state test to copy and modify "independently"
	orig, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)

	for i := byte(0); i < 255; i++ {
		obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
		obj.AddBalance(big.NewInt(int64(i)))
		orig.updateStateObject(obj)
	}
	orig.Finalise(false)

	// Copy the state
	copy := orig.Copy()

	// Copy the copy state
	ccopy := copy.Copy()

	// modify all in memory
	for i := byte(0); i < 255; i++ {
		origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
		copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
		ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))

		origObj.AddBalance(big.NewInt(2 * int64(i)))
		copyObj.AddBalance(big.NewInt(3 * int64(i)))
		ccopyObj.AddBalance(big.NewInt(4 * int64(i)))

		orig.updateStateObject(origObj)
		copy.updateStateObject(copyObj)
		ccopy.updateStateObject(copyObj)
	}

	// Finalise the changes on all concurrently
	finalise := func(wg *sync.WaitGroup, db *StateDB) {
		defer wg.Done()
		db.Finalise(true)
	}

	var wg sync.WaitGroup
	wg.Add(3)
	go finalise(&wg, orig)
	go finalise(&wg, copy)
	go finalise(&wg, ccopy)
	wg.Wait()

	// Verify that the three states have been updated independently
	for i := byte(0); i < 255; i++ {
		origObj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
		copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
		ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i}))

		if want := big.NewInt(3 * int64(i)); origObj.Balance().Cmp(want) != 0 {
			t.Errorf("orig obj %d: balance mismatch: have %v, want %v", i, origObj.Balance(), want)
		}
		if want := big.NewInt(4 * int64(i)); copyObj.Balance().Cmp(want) != 0 {
			t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, copyObj.Balance(), want)
		}
		if want := big.NewInt(5 * int64(i)); ccopyObj.Balance().Cmp(want) != 0 {
			t.Errorf("copy obj %d: balance mismatch: have %v, want %v", i, ccopyObj.Balance(), want)
		}
	}
}

func TestSnapshotRandom(t *testing.T) {
	config := &quick.Config{MaxCount: 1000}
	err := quick.Check((*snapshotTest).run, config)
	if cerr, ok := err.(*quick.CheckError); ok {
		test := cerr.In[0].(*snapshotTest)
		t.Errorf("%v:\n%s", test.err, test)
	} else if err != nil {
		t.Error(err)
	}
}

// A snapshotTest checks that reverting StateDB snapshots properly undoes all changes
// captured by the snapshot. Instances of this test with pseudorandom content are created
// by Generate.
//
// The test works as follows:
//
// A new state is created and all actions are applied to it. Several snapshots are taken
// in between actions. The test then reverts each snapshot. For each snapshot the actions
// leading up to it are replayed on a fresh, empty state. The behaviour of all public
// accessor methods on the reverted state must match the return value of the equivalent
// methods on the replayed state.
type snapshotTest struct {
	addrs     []common.Address // all account addresses
	actions   []testAction     // modifications to the state
	snapshots []int            // actions indexes at which snapshot is taken
	err       error            // failure details are reported through this field
}

type testAction struct {
	name   string
	fn     func(testAction, *StateDB)
	args   []int64
	noAddr bool
}

// newTestAction creates a random action that changes state.
func newTestAction(addr common.Address, r *rand.Rand) testAction {
	actions := []testAction{
		{
			name: "SetBalance",
			fn: func(a testAction, s *StateDB) {
				s.SetBalance(addr, big.NewInt(a.args[0]))
			},
			args: make([]int64, 1),
		},
		{
			name: "AddBalance",
			fn: func(a testAction, s *StateDB) {
				s.AddBalance(addr, big.NewInt(a.args[0]))
			},
			args: make([]int64, 1),
		},
		{
			name: "SetNonce",
			fn: func(a testAction, s *StateDB) {
				s.SetNonce(addr, uint64(a.args[0]))
			},
			args: make([]int64, 1),
		},
		{
			name: "SetState",
			fn: func(a testAction, s *StateDB) {
				var key, val common.Hash
				binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
				binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
				s.SetState(addr, key, val)
			},
			args: make([]int64, 2),
		},
		{
			name: "SetCode",
			fn: func(a testAction, s *StateDB) {
				code := make([]byte, 16)
				binary.BigEndian.PutUint64(code, uint64(a.args[0]))
				binary.BigEndian.PutUint64(code[8:], uint64(a.args[1]))
				s.SetCode(addr, code)
			},
			args: make([]int64, 2),
		},
		{
			name: "CreateAccount",
			fn: func(a testAction, s *StateDB) {
				s.CreateAccount(addr)
			},
		},
		{
			name: "SelfDestruct",
			fn: func(a testAction, s *StateDB) {
				s.SelfDestruct(addr)
			},
		},
		{
			name: "AddRefund",
			fn: func(a testAction, s *StateDB) {
				s.AddRefund(uint64(a.args[0]))
			},
			args:   make([]int64, 1),
			noAddr: true,
		},
		{
			name: "AddLog",
			fn: func(a testAction, s *StateDB) {
				data := make([]byte, 2)
				binary.BigEndian.PutUint16(data, uint16(a.args[0]))
				s.AddLog(addr, nil, data, 0)
			},
			args: make([]int64, 1),
		},
		{
			name: "AddPreimage",
			fn: func(a testAction, s *StateDB) {
				preimage := []byte{1}
				hash := common.BytesToHash(preimage)
				s.AddPreimage(hash, preimage)
			},
			args: make([]int64, 1),
		},
		{
			name: "AddAddressToAccessList",
			fn: func(a testAction, s *StateDB) {
				s.AddAddressToAccessList(addr)
			},
		},
		{
			name: "AddSlotToAccessList",
			fn: func(a testAction, s *StateDB) {
				s.AddSlotToAccessList(addr,
					common.Hash{byte(a.args[0])})
			},
			args: make([]int64, 1),
		},
		{
			name: "SetTransientState",
			fn: func(a testAction, s *StateDB) {
				var key, val common.Hash
				binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
				binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
				s.SetTransientState(addr, key, val)
			},
			args: make([]int64, 2),
		},
	}
	action := actions[r.Intn(len(actions))]
	var nameargs []string
	if !action.noAddr {
		nameargs = append(nameargs, addr.Hex())
	}
	for i := range action.args {
		action.args[i] = rand.Int63n(100)
		nameargs = append(nameargs, fmt.Sprint(action.args[i]))
	}
	action.name += strings.Join(nameargs, ", ")
	return action
}

// Generate returns a new snapshot test of the given size. All randomness is
// derived from r.
func (*snapshotTest) Generate(r *rand.Rand, size int) reflect.Value {
	// Generate random actions.
	addrs := make([]common.Address, 50)
	for i := range addrs {
		addrs[i][0] = byte(i)
	}
	actions := make([]testAction, size)
	for i := range actions {
		addr := addrs[r.Intn(len(addrs))]
		actions[i] = newTestAction(addr, r)
	}
	// Generate snapshot indexes.
	nsnapshots := int(math.Sqrt(float64(size)))
	if size > 0 && nsnapshots == 0 {
		nsnapshots = 1
	}
	snapshots := make([]int, nsnapshots)
	snaplen := len(actions) / nsnapshots
	for i := range snapshots {
		// Try to place the snapshots some number of actions apart from each other.
		snapshots[i] = (i * snaplen) + r.Intn(snaplen)
	}
	return reflect.ValueOf(&snapshotTest{addrs, actions, snapshots, nil})
}

func (test *snapshotTest) String() string {
	out := new(bytes.Buffer)
	sindex := 0
	for i, action := range test.actions {
		if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
			fmt.Fprintf(out, "---- snapshot %d ----\n", sindex)
			sindex++
		}
		fmt.Fprintf(out, "%4d: %s\n", i, action.name)
	}
	return out.String()
}

func (test *snapshotTest) run() bool {
	// Run all actions and create snapshots.
	var (
		state, _     = New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
		snapshotRevs = make([]int, len(test.snapshots))
		sindex       = 0
		checkstates  = make([]*StateDB, len(test.snapshots))
	)
	for i, action := range test.actions {
		if len(test.snapshots) > sindex && i == test.snapshots[sindex] {
			snapshotRevs[sindex] = state.Snapshot()
			checkstates[sindex] = state.Copy()
			sindex++
		}
		action.fn(action, state)
	}
	// Revert all snapshots in reverse order. Each revert must yield a state
	// that is equivalent to fresh state with all actions up the snapshot applied.
	for sindex--; sindex >= 0; sindex-- {
		state.RevertToSnapshot(snapshotRevs[sindex])
		if err := test.checkEqual(state, checkstates[sindex]); err != nil {
			test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err)
			return false
		}
	}
	return true
}

func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.Hash) bool) error {
	so := s.getStateObject(addr)
	if so == nil {
		return nil
	}
	tr, err := so.getTrie()
	if err != nil {
		return err
	}
	trieIt, err := tr.NodeIterator(nil)
	if err != nil {
		return err
	}
	it := trie.NewIterator(trieIt)

	for it.Next() {
		key := common.BytesToHash(s.trie.GetKey(it.Key))
		if value, dirty := so.dirtyStorage[key]; dirty {
			if !cb(key, value) {
				return nil
			}
			continue
		}

		if len(it.Value) > 0 {
			_, content, _, err := rlp.Split(it.Value)
			if err != nil {
				return err
			}
			if !cb(key, common.BytesToHash(content)) {
				return nil
			}
		}
	}
	return nil
}

// checkEqual checks that methods of state and checkstate return the same values.
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
	for _, addr := range test.addrs {
		var err error
		checkeq := func(op string, a, b interface{}) bool {
			if err == nil && !reflect.DeepEqual(a, b) {
				err = fmt.Errorf("got %s(%s) == %v, want %v", op, addr.Hex(), a, b)
				return false
			}
			return true
		}
		// Check basic accessor methods.
		checkeq("Exist", state.Exist(addr), checkstate.Exist(addr))
		checkeq("HasSelfdestructed", state.HasSelfDestructed(addr), checkstate.HasSelfDestructed(addr))
		checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr))
		checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr))
		checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr))
		checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr))
		checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
		// Check storage.
		if obj := state.getStateObject(addr); obj != nil {
			forEachStorage(state, addr, func(key, value common.Hash) bool {
				return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
			})
			forEachStorage(checkstate, addr, func(key, value common.Hash) bool {
				return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
			})
		}
		if err != nil {
			return err
		}
	}

	if state.GetRefund() != checkstate.GetRefund() {
		return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
			state.GetRefund(), checkstate.GetRefund())
	}
	if !reflect.DeepEqual(state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{})) {
		return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
			state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
	}
	return nil
}

func TestTouchDelete(t *testing.T) {
	s := newStateEnv()
	s.state.GetOrNewStateObject(common.Address{})
	root, _ := s.state.Commit(0, false, false)
	s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap)

	snapshot := s.state.Snapshot()
	s.state.AddBalance(common.Address{}, new(big.Int))

	if len(s.state.journal.dirties) != 1 {
		t.Fatal("expected one dirty state object")
	}
	s.state.RevertToSnapshot(snapshot)
	if len(s.state.journal.dirties) != 0 {
		t.Fatal("expected no dirty state object")
	}
}

// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy.
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
func TestCopyOfCopy(t *testing.T) {
	state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
	addr := common.HexToAddress("aaaa")
	state.SetBalance(addr, big.NewInt(42))

	if got := state.Copy().GetBalance(addr).Uint64(); got != 42 {
		t.Fatalf("1st copy fail, expected 42, got %v", got)
	}
	if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 {
		t.Fatalf("2nd copy fail, expected 42, got %v", got)
	}
}

// Tests a regression where committing a copy lost some internal meta information,
// leading to corrupted subsequent copies.
//
// See https://github.com/ethereum/go-ethereum/issues/20106.
func TestCopyCommitCopy(t *testing.T) {
	tdb := NewDatabase(rawdb.NewMemoryDatabase())
	state, _ := New(types.EmptyRootHash, tdb, nil)

	// Create an account and check if the retrieved balance is correct
	addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
	skey := common.HexToHash("aaa")
	sval := common.HexToHash("bbb")

	state.SetBalance(addr, big.NewInt(42)) // Change the account trie
	state.SetCode(addr, []byte("hello"))   // Change an external metadata
	state.SetState(addr, skey, sval)       // Change the storage trie

	if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
	}
	if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := state.GetState(addr, skey); val != sval {
		t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the non-committed state database and check pre/post commit balance
	copyOne := state.Copy()
	if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42)
	}
	if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("first copy pre-commit code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := copyOne.GetState(addr, skey); val != sval {
		t.Fatalf("first copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the copy and check the balance once more
	copyTwo := copyOne.Copy()
	if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42)
	}
	if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("second copy code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := copyTwo.GetState(addr, skey); val != sval {
		t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval)
	}
	// Commit state, ensure states can be loaded from disk
	root, _ := state.Commit(0, false, false)
	state, _ = New(root, tdb, nil)
	if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
	}
	if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("state post-commit code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := state.GetState(addr, skey); val != sval {
		t.Fatalf("state post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := state.GetCommittedState(addr, skey); val != sval {
		t.Fatalf("state post-commit committed storage slot mismatch: have %x, want %x", val, sval)
	}
}

// Tests a regression where committing a copy lost some internal meta information,
// leading to corrupted subsequent copies.
//
// See https://github.com/ethereum/go-ethereum/issues/20106.
func TestCopyCopyCommitCopy(t *testing.T) {
	state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)

	// Create an account and check if the retrieved balance is correct
	addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
	skey := common.HexToHash("aaa")
	sval := common.HexToHash("bbb")

	state.SetBalance(addr, big.NewInt(42)) // Change the account trie
	state.SetCode(addr, []byte("hello"))   // Change an external metadata
	state.SetState(addr, skey, sval)       // Change the storage trie

	if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
	}
	if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := state.GetState(addr, skey); val != sval {
		t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the non-committed state database and check pre/post commit balance
	copyOne := state.Copy()
	if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42)
	}
	if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("first copy code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := copyOne.GetState(addr, skey); val != sval {
		t.Fatalf("first copy non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the copy and check the balance once more
	copyTwo := copyOne.Copy()
	if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42)
	}
	if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("second copy pre-commit code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := copyTwo.GetState(addr, skey); val != sval {
		t.Fatalf("second copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the copy-copy and check the balance once more
	copyThree := copyTwo.Copy()
	if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42)
	}
	if code := copyThree.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("third copy code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := copyThree.GetState(addr, skey); val != sval {
		t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval)
	}
}

// TestCommitCopy tests the copy from a committed state is not functional.
func TestCommitCopy(t *testing.T) {
	state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)

	// Create an account and check if the retrieved balance is correct
	addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
	skey := common.HexToHash("aaa")
	sval := common.HexToHash("bbb")

	state.SetBalance(addr, big.NewInt(42)) // Change the account trie
	state.SetCode(addr, []byte("hello"))   // Change an external metadata
	state.SetState(addr, skey, sval)       // Change the storage trie

	if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
		t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
	}
	if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
		t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
	}
	if val := state.GetState(addr, skey); val != sval {
		t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
	}
	if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
	}
	// Copy the committed state database, the copied one is not functional.
	state.Commit(0, true, false)
	copied := state.Copy()
	if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 {
		t.Fatalf("unexpected balance: have %v", balance)
	}
	if code := copied.GetCode(addr); code != nil {
		t.Fatalf("unexpected code: have %x", code)
	}
	if val := copied.GetState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("unexpected storage slot: have %x", val)
	}
	if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) {
		t.Fatalf("unexpected storage slot: have %x", val)
	}
	if !errors.Is(copied.Error(), trie.ErrCommitted) {
		t.Fatalf("unexpected state error, %v", copied.Error())
	}
}

// TestDeleteCreateRevert tests a weird state transition corner case that we hit
// while changing the internals of StateDB. The workflow is that a contract is
// self-destructed, then in a follow-up transaction (but same block) it's created
// again and the transaction reverted.
//
// The original StateDB implementation flushed dirty objects to the tries after
// each transaction, so this works ok. The rework accumulated writes in memory
// first, but the journal wiped the entire state object on create-revert.
func TestDeleteCreateRevert(t *testing.T) {
	// Create an initial state with a single contract
	state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)

	addr := common.BytesToAddress([]byte("so"))
	state.SetBalance(addr, big.NewInt(1))

	root, _ := state.Commit(0, false, false)
	state, _ = NewWithSnapshot(root, state.db, state.snap)

	// Simulate self-destructing in one transaction, then create-reverting in another
	state.SelfDestruct(addr)
	state.Finalise(true)

	id := state.Snapshot()
	state.SetBalance(addr, big.NewInt(2))
	state.RevertToSnapshot(id)

	// Commit the entire state and make sure we don't crash and have the correct state
	root, _ = state.Commit(0, true, false)
	state, _ = NewWithSnapshot(root, state.db, state.snap)

	if state.getStateObject(addr) != nil {
		t.Fatalf("self-destructed contract came alive")
	}
}

// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
// the Commit operation fails with an error
// If we are missing trie nodes, we should not continue writing to the trie
func TestMissingTrieNodes(t *testing.T) {
	testMissingTrieNodes(t, rawdb.HashScheme)
	testMissingTrieNodes(t, rawdb.PathScheme)
}

func testMissingTrieNodes(t *testing.T, scheme string) {
	// Create an initial state with a few accounts
	var (
		triedb *trie.Database
		memDb  = rawdb.NewMemoryDatabase()
	)
	if scheme == rawdb.PathScheme {
		triedb = trie.NewDatabase(memDb, &trie.Config{PathDB: &pathdb.Config{
			CleanCacheSize: 0,
			DirtyCacheSize: 0,
		}}) // disable caching
	} else {
		triedb = trie.NewDatabase(memDb, &trie.Config{HashDB: &hashdb.Config{
			CleanCacheSize: 0,
		}}) // disable caching
	}
	db := NewDatabaseWithNodeDB(memDb, triedb)

	var root common.Hash
	state, _ := New(types.EmptyRootHash, db, nil)
	addr := common.BytesToAddress([]byte("so"))
	{
		state.SetBalance(addr, big.NewInt(1))
		state.SetCode(addr, []byte{1, 2, 3})
		a2 := common.BytesToAddress([]byte("another"))
		state.SetBalance(a2, big.NewInt(100))
		state.SetCode(a2, []byte{1, 2, 4})
		root, _ = state.Commit(0, false, false)
		t.Logf("root: %x", root)
		// force-flush
		triedb.Commit(root, false)
	}
	// Create a new state on the old root
	state, _ = New(root, db, nil)
	// Now we clear out the memdb
	it := memDb.NewIterator(nil, nil)
	for it.Next() {
		k := it.Key()
		// Leave the root intact
		if !bytes.Equal(k, root[:]) {
			t.Logf("key: %x", k)
			memDb.Delete(k)
		}
	}
	balance := state.GetBalance(addr)
	// The removed elem should lead to it returning zero balance
	if exp, got := uint64(0), balance.Uint64(); got != exp {
		t.Errorf("expected %d, got %d", exp, got)
	}
	// Modify the state
	state.SetBalance(addr, big.NewInt(2))
	root, err := state.Commit(0, false, false)
	if err == nil {
		t.Fatalf("expected error, got root :%x", root)
	}
}

func TestStateDBAccessList(t *testing.T) {
	// Some helpers
	addr := func(a string) common.Address {
		return common.HexToAddress(a)
	}
	slot := func(a string) common.Hash {
		return common.HexToHash(a)
	}

	memDb := rawdb.NewMemoryDatabase()
	db := NewDatabase(memDb)
	state, _ := New(types.EmptyRootHash, db, nil)
	state.accessList = newAccessList()

	verifyAddrs := func(astrings ...string) {
		t.Helper()
		// convert to common.Address form
		var addresses []common.Address
		var addressMap = make(map[common.Address]struct{})
		for _, astring := range astrings {
			address := addr(astring)
			addresses = append(addresses, address)
			addressMap[address] = struct{}{}
		}
		// Check that the given addresses are in the access list
		for _, address := range addresses {
			if !state.AddressInAccessList(address) {
				t.Fatalf("expected %x to be in access list", address)
			}
		}
		// Check that only the expected addresses are present in the access list
		for address := range state.accessList.addresses {
			if _, exist := addressMap[address]; !exist {
				t.Fatalf("extra address %x in access list", address)
			}
		}
	}
	verifySlots := func(addrString string, slotStrings ...string) {
		if !state.AddressInAccessList(addr(addrString)) {
			t.Fatalf("scope missing address/slots %v", addrString)
		}
		var address = addr(addrString)
		// convert to common.Hash form
		var slots []common.Hash
		var slotMap = make(map[common.Hash]struct{})
		for _, slotString := range slotStrings {
			s := slot(slotString)
			slots = append(slots, s)
			slotMap[s] = struct{}{}
		}
		// Check that the expected items are in the access list
		for i, s := range slots {
			if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent {
				t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString)
			}
		}
		// Check that no extra elements are in the access list
		index := state.accessList.addresses[address]
		if index >= 0 {
			stateSlots := state.accessList.slots[index]
			for s := range stateSlots {
				if _, slotPresent := slotMap[s]; !slotPresent {
					t.Fatalf("scope has extra slot %v (address %v)", s, addrString)
				}
			}
		}
	}

	state.AddAddressToAccessList(addr("aa"))          // 1
	state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3
	state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
	verifyAddrs("aa", "bb")
	verifySlots("bb", "01", "02")

	// Make a copy
	stateCopy1 := state.Copy()
	if exp, got := 4, state.journal.length(); exp != got {
		t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
	}

	// same again, should cause no journal entries
	state.AddSlotToAccessList(addr("bb"), slot("01"))
	state.AddSlotToAccessList(addr("bb"), slot("02"))
	state.AddAddressToAccessList(addr("aa"))
	if exp, got := 4, state.journal.length(); exp != got {
		t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
	}
	// some new ones
	state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
	state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
	state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8
	state.AddAddressToAccessList(addr("cc"))
	if exp, got := 8, state.journal.length(); exp != got {
		t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
	}

	verifyAddrs("aa", "bb", "cc")
	verifySlots("aa", "01")
	verifySlots("bb", "01", "02", "03")
	verifySlots("cc", "01")

	// now start rolling back changes
	state.journal.revert(state, 7)
	if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok {
		t.Fatalf("slot present, expected missing")
	}
	verifyAddrs("aa", "bb", "cc")
	verifySlots("aa", "01")
	verifySlots("bb", "01", "02", "03")

	state.journal.revert(state, 6)
	if state.AddressInAccessList(addr("cc")) {
		t.Fatalf("addr present, expected missing")
	}
	verifyAddrs("aa", "bb")
	verifySlots("aa", "01")
	verifySlots("bb", "01", "02", "03")

	state.journal.revert(state, 5)
	if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok {
		t.Fatalf("slot present, expected missing")
	}
	verifyAddrs("aa", "bb")
	verifySlots("bb", "01", "02", "03")

	state.journal.revert(state, 4)
	if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok {
		t.Fatalf("slot present, expected missing")
	}
	verifyAddrs("aa", "bb")
	verifySlots("bb", "01", "02")

	state.journal.revert(state, 3)
	if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok {
		t.Fatalf("slot present, expected missing")
	}
	verifyAddrs("aa", "bb")
	verifySlots("bb", "01")

	state.journal.revert(state, 2)
	if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok {
		t.Fatalf("slot present, expected missing")
	}
	verifyAddrs("aa", "bb")

	state.journal.revert(state, 1)
	if state.AddressInAccessList(addr("bb")) {
		t.Fatalf("addr present, expected missing")
	}
	verifyAddrs("aa")

	state.journal.revert(state, 0)
	if state.AddressInAccessList(addr("aa")) {
		t.Fatalf("addr present, expected missing")
	}
	if got, exp := len(state.accessList.addresses), 0; got != exp {
		t.Fatalf("expected empty, got %d", got)
	}
	if got, exp := len(state.accessList.slots), 0; got != exp {
		t.Fatalf("expected empty, got %d", got)
	}
	// Check the copy
	// Make a copy
	state = stateCopy1
	verifyAddrs("aa", "bb")
	verifySlots("bb", "01", "02")
	if got, exp := len(state.accessList.addresses), 2; got != exp {
		t.Fatalf("expected empty, got %d", got)
	}
	if got, exp := len(state.accessList.slots), 1; got != exp {
		t.Fatalf("expected empty, got %d", got)
	}
}

func TestMultiCoinOperations(t *testing.T) {
	s := newStateEnv()
	addr := common.Address{1}
	assetID := common.Hash{2}

	s.state.GetOrNewStateObject(addr)
	root, _ := s.state.Commit(0, false, false)
	s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap)

	s.state.AddBalance(addr, new(big.Int))

	balance := s.state.GetBalanceMultiCoin(addr, assetID)
	if balance.Cmp(big.NewInt(0)) != 0 {
		t.Fatal("expected zero multicoin balance")
	}

	s.state.SetBalanceMultiCoin(addr, assetID, big.NewInt(10))
	s.state.SubBalanceMultiCoin(addr, assetID, big.NewInt(5))
	s.state.AddBalanceMultiCoin(addr, assetID, big.NewInt(3))

	balance = s.state.GetBalanceMultiCoin(addr, assetID)
	if balance.Cmp(big.NewInt(8)) != 0 {
		t.Fatal("expected multicoin balance to be 8")
	}
}

func TestMultiCoinSnapshot(t *testing.T) {
	db := rawdb.NewMemoryDatabase()
	sdb := NewDatabase(db)

	// Create empty snapshot.Tree and StateDB
	root := common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
	// Use the root as both the stateRoot and blockHash for this test.
	snapTree := snapshot.NewTestTree(db, root, root)

	addr := common.Address{1}
	assetID1 := common.Hash{1}
	assetID2 := common.Hash{2}

	var stateDB *StateDB
	assertBalances := func(regular, multicoin1, multicoin2 int64) {
		balance := stateDB.GetBalance(addr)
		if balance.Cmp(big.NewInt(regular)) != 0 {
			t.Fatal("incorrect non-multicoin balance")
		}
		balance = stateDB.GetBalanceMultiCoin(addr, assetID1)
		if balance.Cmp(big.NewInt(multicoin1)) != 0 {
			t.Fatal("incorrect multicoin1 balance")
		}
		balance = stateDB.GetBalanceMultiCoin(addr, assetID2)
		if balance.Cmp(big.NewInt(multicoin2)) != 0 {
			t.Fatal("incorrect multicoin2 balance")
		}
	}

	// Create new state
	stateDB, _ = New(root, sdb, snapTree)
	assertBalances(0, 0, 0)

	stateDB.AddBalance(addr, big.NewInt(10))
	assertBalances(10, 0, 0)

	// Commit and get the new root
	root, _ = stateDB.Commit(0, false, false)
	assertBalances(10, 0, 0)

	// Create a new state from the latest root, add a multicoin balance, and
	// commit it to the tree.
	stateDB, _ = New(root, sdb, snapTree)
	stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(10))
	root, _ = stateDB.Commit(0, false, false)
	assertBalances(10, 10, 0)

	// Add more layers than the cap and ensure the balances and layers are correct
	for i := 0; i < 256; i++ {
		stateDB, _ = New(root, sdb, snapTree)
		stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1))
		stateDB.AddBalanceMultiCoin(addr, assetID2, big.NewInt(2))
		root, _ = stateDB.Commit(0, false, false)
	}
	assertBalances(10, 266, 512)

	// Do one more add, including the regular balance which is now in the
	// collapsed snapshot
	stateDB, _ = New(root, sdb, snapTree)
	stateDB.AddBalance(addr, big.NewInt(1))
	stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1))
	root, _ = stateDB.Commit(0, false, false)
	stateDB, _ = New(root, sdb, snapTree)
	assertBalances(11, 267, 512)
}

func TestGenerateMultiCoinAccounts(t *testing.T) {
	var (
		diskdb   = rawdb.NewMemoryDatabase()
		database = NewDatabase(diskdb)

		addr     = common.BytesToAddress([]byte("addr1"))
		addrHash = crypto.Keccak256Hash(addr[:])

		assetID      = common.BytesToHash([]byte("coin1"))
		assetBalance = big.NewInt(10)
	)

	stateDB, err := New(common.Hash{}, database, nil)
	if err != nil {
		t.Fatal(err)
	}
	stateDB.SetBalanceMultiCoin(addr, assetID, assetBalance)
	root, err := stateDB.Commit(0, false, false)
	if err != nil {
		t.Fatal(err)
	}

	triedb := database.TrieDB()
	if err := triedb.Commit(root, true); err != nil {
		t.Fatal(err)
	}
	// Build snapshot from scratch
	snapConfig := snapshot.Config{
		CacheSize:  16,
		AsyncBuild: false,
		NoBuild:    false,
		SkipVerify: true,
	}
	snaps, err := snapshot.New(snapConfig, diskdb, triedb, common.Hash{}, root)
	if err != nil {
		t.Error("Unexpected error while rebuilding snapshot:", err)
	}

	// Get latest snapshot and make sure it has the correct account and storage
	snap := snaps.Snapshot(root)
	snapAccount, err := snap.Account(addrHash)
	if err != nil {
		t.Fatal(err)
	}
	if !snapAccount.IsMultiCoin {
		t.Fatalf("Expected SnapAccount to return IsMultiCoin: true, found: %v", snapAccount.IsMultiCoin)
	}

	NormalizeCoinID(&assetID)
	assetHash := crypto.Keccak256Hash(assetID.Bytes())
	storageBytes, err := snap.Storage(addrHash, assetHash)
	if err != nil {
		t.Fatal(err)
	}

	actualAssetBalance := new(big.Int).SetBytes(storageBytes)
	if actualAssetBalance.Cmp(assetBalance) != 0 {
		t.Fatalf("Expected asset balance: %v, found %v", assetBalance, actualAssetBalance)
	}
}

// Tests that account and storage tries are flushed in the correct order and that
// no data loss occurs.
func TestFlushOrderDataLoss(t *testing.T) {
	// Create a state trie with many accounts and slots
	var (
		memdb    = rawdb.NewMemoryDatabase()
		triedb   = trie.NewDatabase(memdb, nil)
		statedb  = NewDatabaseWithNodeDB(memdb, triedb)
		state, _ = New(types.EmptyRootHash, statedb, nil)
	)
	for a := byte(0); a < 10; a++ {
		state.CreateAccount(common.Address{a})
		for s := byte(0); s < 10; s++ {
			state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
		}
	}
	root, err := state.Commit(0, false, false)
	if err != nil {
		t.Fatalf("failed to commit state trie: %v", err)
	}
	triedb.Reference(root, common.Hash{})
	if err := triedb.Cap(1024); err != nil {
		t.Fatalf("failed to cap trie dirty cache: %v", err)
	}
	if err := triedb.Commit(root, false); err != nil {
		t.Fatalf("failed to commit state trie: %v", err)
	}
	// Reopen the state trie from flushed disk and verify it
	state, err = New(root, NewDatabase(memdb), nil)
	if err != nil {
		t.Fatalf("failed to reopen state trie: %v", err)
	}
	for a := byte(0); a < 10; a++ {
		for s := byte(0); s < 10; s++ {
			if have := state.GetState(common.Address{a}, common.Hash{a, s}); have != (common.Hash{a, s}) {
				t.Errorf("account %d: slot %d: state mismatch: have %x, want %x", a, s, have, common.Hash{a, s})
			}
		}
	}
}

func TestStateDBTransientStorage(t *testing.T) {
	memDb := rawdb.NewMemoryDatabase()
	db := NewDatabase(memDb)
	state, _ := New(types.EmptyRootHash, db, nil)

	key := common.Hash{0x01}
	value := common.Hash{0x02}
	addr := common.Address{}

	state.SetTransientState(addr, key, value)
	if exp, got := 1, state.journal.length(); exp != got {
		t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
	}
	// the retrieved value should equal what was set
	if got := state.GetTransientState(addr, key); got != value {
		t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
	}

	// revert the transient state being set and then check that the
	// value is now the empty hash
	state.journal.revert(state, 0)
	if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
		t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
	}

	// set transient state and then copy the statedb and ensure that
	// the transient state is copied
	state.SetTransientState(addr, key, value)
	cpy := state.Copy()
	if got := cpy.GetTransientState(addr, key); got != value {
		t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
	}
}

func TestResetObject(t *testing.T) {
	var (
		disk     = rawdb.NewMemoryDatabase()
		tdb      = trie.NewDatabase(disk, nil)
		db       = NewDatabaseWithNodeDB(disk, tdb)
		snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, common.Hash{}, types.EmptyRootHash)
		state, _ = New(types.EmptyRootHash, db, snaps)
		addr     = common.HexToAddress("0x1")
		slotA    = common.HexToHash("0x1")
		slotB    = common.HexToHash("0x2")
	)
	// Initialize account with balance and storage in first transaction.
	state.SetBalance(addr, big.NewInt(1))
	state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
	state.IntermediateRoot(true)

	// Reset account and mutate balance and storages
	state.CreateAccount(addr)
	state.SetBalance(addr, big.NewInt(2))
	state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false)

	// Ensure the original account is wiped properly
	snap := snaps.Snapshot(root)
	slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
	if len(slot) != 0 {
		t.Fatalf("Unexpected storage slot")
	}
	slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
	if !bytes.Equal(slot, []byte{0x2}) {
		t.Fatalf("Unexpected storage slot value %v", slot)
	}
}

func TestDeleteStorage(t *testing.T) {
	var (
		disk     = rawdb.NewMemoryDatabase()
		tdb      = trie.NewDatabase(disk, nil)
		db       = NewDatabaseWithNodeDB(disk, tdb)
		snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, common.Hash{}, types.EmptyRootHash)
		state, _ = New(types.EmptyRootHash, db, snaps)
		addr     = common.HexToAddress("0x1")
	)
	// Initialize account and populate storage
	state.SetBalance(addr, big.NewInt(1))
	state.CreateAccount(addr)
	for i := 0; i < 1000; i++ {
		slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32())
		value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32())
		state.SetState(addr, slot, value)
	}
	root, _ := state.CommitWithSnap(0, true, snaps, common.Hash{}, common.Hash{}, false)
	// Init phase done, create two states, one with snap and one without
	fastState, _ := New(root, db, snaps)
	slowState, _ := New(root, db, nil)

	obj := fastState.GetOrNewStateObject(addr)
	storageRoot := obj.data.Root

	_, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
	if err != nil {
		t.Fatal(err)
	}

	_, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
	if err != nil {
		t.Fatal(err)
	}
	check := func(set *trienode.NodeSet) string {
		var a []string
		set.ForEachWithOrder(func(path string, n *trienode.Node) {
			if n.Hash != (common.Hash{}) {
				t.Fatal("delete should have empty hashes")
			}
			if len(n.Blob) != 0 {
				t.Fatal("delete should have have empty blobs")
			}
			a = append(a, fmt.Sprintf("%x", path))
		})
		return strings.Join(a, ",")
	}
	slowRes := check(slowNodes)
	fastRes := check(fastNodes)
	if slowRes != fastRes {
		t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
	}
}
