core/state: remove notion of fake storage (#24916)

This PR removes the notion of fakeStorage from the state objects, and instead, for any state modifications that are needed, it simply makes the changes.
This commit is contained in:
Martin Holst Swende 2023-01-10 08:24:30 -05:00 committed by GitHub
parent 4ada314fff
commit 7a489623ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 35 deletions

View File

@ -84,7 +84,6 @@ type stateObject struct {
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
// Cache flags. // Cache flags.
// When an object is marked suicided it will be delete from the trie // When an object is marked suicided it will be delete from the trie
@ -173,10 +172,6 @@ func (s *stateObject) getTrie(db Database) (Trie, error) {
// GetState retrieves a value from the account storage trie. // GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash { func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
// If we have a dirty value for this state entry, return it // If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key] value, dirty := s.dirtyStorage[key]
if dirty { if dirty {
@ -188,10 +183,6 @@ func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
// GetCommittedState retrieves a value from the committed account storage trie. // GetCommittedState retrieves a value from the committed account storage trie.
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash { func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
// If the fake storage is set, only lookup the state here(in the debugging mode)
if s.fakeStorage != nil {
return s.fakeStorage[key]
}
// If we have a pending write or clean cached, return that // If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending { if value, pending := s.pendingStorage[key]; pending {
return value return value
@ -251,11 +242,6 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
// SetState updates a value in account storage. // SetState updates a value in account storage.
func (s *stateObject) SetState(db Database, key, value common.Hash) { func (s *stateObject) SetState(db Database, key, value common.Hash) {
// If the fake storage is set, put the temporary state update here.
if s.fakeStorage != nil {
s.fakeStorage[key] = value
return
}
// If the new value is the same as old, don't set // If the new value is the same as old, don't set
prev := s.GetState(db, key) prev := s.GetState(db, key)
if prev == value { if prev == value {
@ -270,24 +256,6 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) {
s.setState(key, value) s.setState(key, value)
} }
// SetStorage replaces the entire state storage with the given one.
//
// After this function is called, all original state will be ignored and state
// lookup only happens in the fake state storage.
//
// Note this function should only be used for debugging purpose.
func (s *stateObject) SetStorage(storage map[common.Hash]common.Hash) {
// Allocate fake storage if it's nil.
if s.fakeStorage == nil {
s.fakeStorage = make(Storage)
}
for key, value := range storage {
s.fakeStorage[key] = value
}
// Don't bother journal since this function should only be used for
// debugging and the `fake` storage won't be committed to database.
}
func (s *stateObject) setState(key, value common.Hash) { func (s *stateObject) setState(key, value common.Hash) {
s.dirtyStorage[key] = value s.dirtyStorage[key] = value
} }

View File

@ -442,9 +442,15 @@ func (s *StateDB) SetState(addr common.Address, key, value common.Hash) {
// SetStorage replaces the entire storage for the specified account with given // SetStorage replaces the entire storage for the specified account with given
// storage. This function should only be used for debugging. // storage. This function should only be used for debugging.
func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
// SetStorage needs to wipe existing storage. We achieve this by pretending
// that the account self-destructed earlier in this block, by flagging
// it in stateObjectsDestruct. The effect of doing so is that storage lookups
// will not hit disk, since it is assumed that the disk-data is belonging
// to a previous incarnation of the object.
s.stateObjectsDestruct[addr] = struct{}{}
stateObject := s.GetOrNewStateObject(addr) stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil { for k, v := range storage {
stateObject.SetStorage(storage) stateObject.SetState(s.db, k, v)
} }
} }

View File

@ -454,12 +454,21 @@ func TestTracingWithOverrides(t *testing.T) {
t.Parallel() t.Parallel()
// Initialize test accounts // Initialize test accounts
accounts := newAccounts(3) accounts := newAccounts(3)
storageAccount := common.Address{0x13, 37}
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.TestChainConfig, Config: params.TestChainConfig,
Alloc: core.GenesisAlloc{ Alloc: core.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)},
accounts[2].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)},
// An account with existing storage
storageAccount: {
Balance: new(big.Int),
Storage: map[common.Hash]common.Hash{
common.HexToHash("0x03"): common.HexToHash("0x33"),
common.HexToHash("0x04"): common.HexToHash("0x44"),
},
},
}, },
} }
genBlocks := 10 genBlocks := 10
@ -579,6 +588,164 @@ func TestTracingWithOverrides(t *testing.T) {
}, },
want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`, want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
}, },
/*
pragma solidity =0.8.12;
contract Test {
uint private x;
function test2() external {
x = 1337;
revert();
}
function test() external returns (uint) {
x = 1;
try this.test2() {} catch (bytes memory) {}
return x;
}
}
*/
{ // First with only code override, not storage override
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[2].addr,
Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), //
},
config: &TraceCallConfig{
StateOverrides: &ethapi.StateOverride{
randomAccounts[2].addr: ethapi.OverrideAccount{
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060325760003560e01c806366e41cb7146037578063f8a8fd6d14603f575b600080fd5b603d6057565b005b60456062565b60405190815260200160405180910390f35b610539600090815580fd5b60006001600081905550306001600160a01b03166366e41cb76040518163ffffffff1660e01b8152600401600060405180830381600087803b15801560a657600080fd5b505af192505050801560b6575060015b60e9573d80801560e1576040519150601f19603f3d011682016040523d82523d6000602084013e60e6565b606091505b50505b506000549056fea26469706673582212205ce45de745a5308f713cb2f448589177ba5a442d1a2eff945afaa8915961b4d064736f6c634300080c0033")),
},
},
},
want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`,
},
{ // Same again, this time with storage override
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[2].addr,
Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), //
},
config: &TraceCallConfig{
StateOverrides: &ethapi.StateOverride{
randomAccounts[2].addr: ethapi.OverrideAccount{
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060325760003560e01c806366e41cb7146037578063f8a8fd6d14603f575b600080fd5b603d6057565b005b60456062565b60405190815260200160405180910390f35b610539600090815580fd5b60006001600081905550306001600160a01b03166366e41cb76040518163ffffffff1660e01b8152600401600060405180830381600087803b15801560a657600080fd5b505af192505050801560b6575060015b60e9573d80801560e1576040519150601f19603f3d011682016040523d82523d6000602084013e60e6565b606091505b50505b506000549056fea26469706673582212205ce45de745a5308f713cb2f448589177ba5a442d1a2eff945afaa8915961b4d064736f6c634300080c0033")),
State: newStates([]common.Hash{{}}, []common.Hash{{}}),
},
},
},
//want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`,
want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`,
},
{ // No state override
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &storageAccount,
Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), //
},
config: &TraceCallConfig{
StateOverrides: &ethapi.StateOverride{
storageAccount: ethapi.OverrideAccount{
Code: newRPCBytes([]byte{
// SLOAD(3) + SLOAD(4) (which is 0x77)
byte(vm.PUSH1), 0x04,
byte(vm.SLOAD),
byte(vm.PUSH1), 0x03,
byte(vm.SLOAD),
byte(vm.ADD),
// 0x77 -> MSTORE(0)
byte(vm.PUSH1), 0x00,
byte(vm.MSTORE),
// RETURN (0, 32)
byte(vm.PUSH1), 32,
byte(vm.PUSH1), 00,
byte(vm.RETURN),
}),
},
},
},
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000077"}`,
},
{ // Full state override
// The original storage is
// 3: 0x33
// 4: 0x44
// With a full override, where we set 3:0x11, the slot 4 should be
// removed. So SLOT(3)+SLOT(4) should be 0x11.
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &storageAccount,
Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), //
},
config: &TraceCallConfig{
StateOverrides: &ethapi.StateOverride{
storageAccount: ethapi.OverrideAccount{
Code: newRPCBytes([]byte{
// SLOAD(3) + SLOAD(4) (which is now 0x11 + 0x00)
byte(vm.PUSH1), 0x04,
byte(vm.SLOAD),
byte(vm.PUSH1), 0x03,
byte(vm.SLOAD),
byte(vm.ADD),
// 0x11 -> MSTORE(0)
byte(vm.PUSH1), 0x00,
byte(vm.MSTORE),
// RETURN (0, 32)
byte(vm.PUSH1), 32,
byte(vm.PUSH1), 00,
byte(vm.RETURN),
}),
State: newStates(
[]common.Hash{common.HexToHash("0x03")},
[]common.Hash{common.HexToHash("0x11")}),
},
},
},
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000011"}`,
},
{ // Partial state override
// The original storage is
// 3: 0x33
// 4: 0x44
// With a partial override, where we set 3:0x11, the slot 4 as before.
// So SLOT(3)+SLOT(4) should be 0x55.
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &storageAccount,
Data: newRPCBytes(common.Hex2Bytes("f8a8fd6d")), //
},
config: &TraceCallConfig{
StateOverrides: &ethapi.StateOverride{
storageAccount: ethapi.OverrideAccount{
Code: newRPCBytes([]byte{
// SLOAD(3) + SLOAD(4) (which is now 0x11 + 0x44)
byte(vm.PUSH1), 0x04,
byte(vm.SLOAD),
byte(vm.PUSH1), 0x03,
byte(vm.SLOAD),
byte(vm.ADD),
// 0x55 -> MSTORE(0)
byte(vm.PUSH1), 0x00,
byte(vm.MSTORE),
// RETURN (0, 32)
byte(vm.PUSH1), 32,
byte(vm.PUSH1), 00,
byte(vm.RETURN),
}),
StateDiff: &map[common.Hash]common.Hash{
common.HexToHash("0x03"): common.HexToHash("0x11"),
},
},
},
},
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000055"}`,
},
} }
for i, tc := range testSuite { for i, tc := range testSuite {
result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config) result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config)
@ -605,7 +772,8 @@ func TestTracingWithOverrides(t *testing.T) {
json.Unmarshal(resBytes, &have) json.Unmarshal(resBytes, &have)
json.Unmarshal([]byte(tc.want), &want) json.Unmarshal([]byte(tc.want), &want)
if !reflect.DeepEqual(have, want) { if !reflect.DeepEqual(have, want) {
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(resBytes), want) t.Logf("result: %v\n", string(resBytes))
t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, have, want)
} }
} }
} }

View File

@ -909,6 +909,10 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
} }
} }
} }
// Now finalize the changes. Finalize is normally performed between transactions.
// By using finalize, the overrides are semantically behaving as
// if they were created in a transaction just before the tracing occur.
state.Finalise(false)
return nil return nil
} }