Skip to content

Commit c9cdf14

Browse files
rjl493456442karalabe
authored andcommitted
graphql, internal/ethapi: support overriding accounts in eth_call (#19917)
* graphql, internal/ethapi: extend eth_call This PR offers the third option parameter for eth_call API. Caller can specify a batch of contracts for overriding the original account metadata(nonce, balance, code, state). It has a few advantages: * It's friendly for debugging * It's can make on-chain contract lighter for getting rid of state access functions * core, internal: address comments
1 parent 081642e commit c9cdf14

File tree

4 files changed

+99
-8
lines changed

4 files changed

+99
-8
lines changed

core/state/state_object.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type stateObject struct {
8181

8282
originStorage Storage // Storage cache of original entries to dedup rewrites
8383
dirtyStorage Storage // Storage entries that need to be flushed to disk
84+
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
8485

8586
// Cache flags.
8687
// When an object is marked suicided it will be delete from the trie
@@ -163,6 +164,10 @@ func (s *stateObject) getTrie(db Database) Trie {
163164

164165
// GetState retrieves a value from the account storage trie.
165166
func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
167+
// If the fake storage is set, only lookup the state here(in the debugging mode)
168+
if s.fakeStorage != nil {
169+
return s.fakeStorage[key]
170+
}
166171
// If we have a dirty value for this state entry, return it
167172
value, dirty := s.dirtyStorage[key]
168173
if dirty {
@@ -174,12 +179,16 @@ func (s *stateObject) GetState(db Database, key common.Hash) common.Hash {
174179

175180
// GetCommittedState retrieves a value from the committed account storage trie.
176181
func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
182+
// If the fake storage is set, only lookup the state here(in the debugging mode)
183+
if s.fakeStorage != nil {
184+
return s.fakeStorage[key]
185+
}
177186
// If we have the original value cached, return that
178187
value, cached := s.originStorage[key]
179188
if cached {
180189
return value
181190
}
182-
// Track the amount of time wasted on reading the storge trie
191+
// Track the amount of time wasted on reading the storage trie
183192
if metrics.EnabledExpensive {
184193
defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now())
185194
}
@@ -202,6 +211,11 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
202211

203212
// SetState updates a value in account storage.
204213
func (s *stateObject) SetState(db Database, key, value common.Hash) {
214+
// If the fake storage is set, put the temporary state update here.
215+
if s.fakeStorage != nil {
216+
s.fakeStorage[key] = value
217+
return
218+
}
205219
// If the new value is the same as old, don't set
206220
prev := s.GetState(db, key)
207221
if prev == value {
@@ -216,6 +230,24 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) {
216230
s.setState(key, value)
217231
}
218232

233+
// SetStorage replaces the entire state storage with the given one.
234+
//
235+
// After this function is called, all original state will be ignored and state
236+
// lookup only happens in the fake state storage.
237+
//
238+
// Note this function should only be used for debugging purpose.
239+
func (s *stateObject) SetStorage(storage map[common.Hash]common.Hash) {
240+
// Allocate fake storage if it's nil.
241+
if s.fakeStorage == nil {
242+
s.fakeStorage = make(Storage)
243+
}
244+
for key, value := range storage {
245+
s.fakeStorage[key] = value
246+
}
247+
// Don't bother journal since this function should only be used for
248+
// debugging and the `fake` storage won't be committed to database.
249+
}
250+
219251
func (s *stateObject) setState(key, value common.Hash) {
220252
s.dirtyStorage[key] = value
221253
}

core/state/statedb.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,15 @@ func (self *StateDB) SetState(addr common.Address, key, value common.Hash) {
386386
}
387387
}
388388

389+
// SetStorage replaces the entire storage for the specified account with given
390+
// storage. This function should only be used for debugging.
391+
func (self *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
392+
stateObject := self.GetOrNewStateObject(addr)
393+
if stateObject != nil {
394+
stateObject.SetStorage(storage)
395+
}
396+
}
397+
389398
// Suicide marks the given account as suicided.
390399
// This clears the account balance.
391400
//

graphql/graphql.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ func (b *Block) Call(ctx context.Context, args struct {
817817
return nil, err
818818
}
819819
}
820-
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
820+
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
821821
status := hexutil.Uint64(1)
822822
if failed {
823823
status = 0
@@ -885,7 +885,7 @@ func (p *Pending) Account(ctx context.Context, args struct {
885885
func (p *Pending) Call(ctx context.Context, args struct {
886886
Data ethapi.CallArgs
887887
}) (*CallResult, error) {
888-
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, rpc.PendingBlockNumber, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
888+
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, rpc.PendingBlockNumber, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
889889
status := hexutil.Uint64(1)
890890
if failed {
891891
status = 0

internal/ethapi/api.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,21 @@ type CallArgs struct {
743743
Data *hexutil.Bytes `json:"data"`
744744
}
745745

746-
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
746+
// account indicates the overriding fields of account during the execution of
747+
// a message call.
748+
// Note, state and stateDiff can't be specified at the same time. If state is
749+
// set, message execution will only use the data in the given state. Otherwise
750+
// if statDiff is set, all diff will be applied first and then execute the call
751+
// message.
752+
type account struct {
753+
Nonce *hexutil.Uint64 `json:"nonce"`
754+
Code *hexutil.Bytes `json:"code"`
755+
Balance **hexutil.Big `json:"balance"`
756+
State *map[common.Hash]common.Hash `json:"state"`
757+
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
758+
}
759+
760+
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
747761
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
748762

749763
state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
@@ -761,6 +775,34 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
761775
} else {
762776
addr = *args.From
763777
}
778+
// Override the fields of specified contracts before execution.
779+
for addr, account := range overrides {
780+
// Override account nonce.
781+
if account.Nonce != nil {
782+
state.SetNonce(addr, uint64(*account.Nonce))
783+
}
784+
// Override account(contract) code.
785+
if account.Code != nil {
786+
state.SetCode(addr, *account.Code)
787+
}
788+
// Override account balance.
789+
if account.Balance != nil {
790+
state.SetBalance(addr, (*big.Int)(*account.Balance))
791+
}
792+
if account.State != nil && account.StateDiff != nil {
793+
return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
794+
}
795+
// Replace entire state if caller requires.
796+
if account.State != nil {
797+
state.SetStorage(addr, *account.State)
798+
}
799+
// Apply state diff into specified accounts.
800+
if account.StateDiff != nil {
801+
for key, value := range *account.StateDiff {
802+
state.SetState(addr, key, value)
803+
}
804+
}
805+
}
764806
// Set default gas & gas price if none were set
765807
gas := uint64(math.MaxUint64 / 2)
766808
if args.Gas != nil {
@@ -827,9 +869,17 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
827869
}
828870

829871
// Call executes the given transaction on the state for the given block number.
830-
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
831-
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
832-
result, _, _, err := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
872+
//
873+
// Additionally, the caller can specify a batch of contract for fields overriding.
874+
//
875+
// Note, this function doesn't make and changes in the state/blockchain and is
876+
// useful to execute and retrieve values.
877+
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) {
878+
var accounts map[common.Address]account
879+
if overrides != nil {
880+
accounts = *overrides
881+
}
882+
result, _, _, err := DoCall(ctx, s.b, args, blockNr, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
833883
return (hexutil.Bytes)(result), err
834884
}
835885

@@ -860,7 +910,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
860910
executable := func(gas uint64) bool {
861911
args.Gas = (*hexutil.Uint64)(&gas)
862912

863-
_, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, vm.Config{}, 0, gasCap)
913+
_, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, nil, vm.Config{}, 0, gasCap)
864914
if err != nil || failed {
865915
return false
866916
}

0 commit comments

Comments
 (0)