From ea363998d08e960b08cda0bdc27e5725ff55243e Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 26 Jan 2017 10:02:40 +0100 Subject: [PATCH] core/vm, internal/ethapi: implemented eth_injectCode_ The EVM gained a new method called which allows one to call using the caller as context and runs with the given code as if it were a delegate call. The only difference is that does not take an address to get the code *from*. This functions as code injection tool. --- core/vm/evm.go | 31 ++++++++++++++++++++ internal/ethapi/api.go | 57 +++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 6 ++++ 3 files changed, 94 insertions(+) diff --git a/core/vm/evm.go b/core/vm/evm.go index 0c5d998c29..7a288b4cf6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -292,6 +292,37 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I return ret, contractAddr, contract.Gas, err } +// InjectCode executes the given code in the context of caller, i.e. code injection. +func (evm *EVM) InjectCall(caller ContractRef, code, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { + if evm.vmConfig.NoRecursion && evm.depth > 0 { + return nil, gas, nil + } + + // Depth check execution. Fail if we're trying to execute above the + // limit. + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, ErrDepth + } + + var ( + snapshot = evm.StateDB.Snapshot() + to = evm.StateDB.GetAccount(caller.Address()) + ) + + // Iinitialise a new contract and make initialise the delegate values + contract := NewContract(caller, to, new(big.Int), gas).AsDelegate() + contract.SetCallCode(nil, crypto.Sha3Hash(code), code) + + ret, err = evm.interpreter.Run(contract, input) + if err != nil { + contract.UseGas(contract.Gas) + + evm.StateDB.RevertToSnapshot(snapshot) + } + + return ret, contract.Gas, err +} + // ChainConfig returns the evmironment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4c8e784c5d..c4dffc1663 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -570,6 +570,63 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr return res, gas, err } +type InjectCodeArgs struct { + CallArgs + Code hexutil.Bytes `json:"code"` +} + +func (s *PublicBlockChainAPI) InjectCall(ctx context.Context, args InjectCodeArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { + statedb, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) + if statedb == nil || err != nil { + return nil, err + } + + if args.To == nil { + return nil, errors.New("requires 'to' be set") + } + + // Set sender address or use a default if none specified + addr := args.From + if addr == (common.Address{}) { + accounts := s.b.AccountManager().Accounts() + if len(accounts) > 0 { + addr = accounts[0].Address + } + } + + gas, gasPrice := args.Gas.ToInt(), args.GasPrice.ToInt() + // Create new call message + msg := types.NewMessage(addr, args.To, 0, args.Value.ToInt(), gas, gasPrice, args.Data, false) + + // Setup context so it may be cancelled the call has completed + // or, in case of unmetered gas, setup a context with a timeout. + ctx, cancel := context.WithTimeout(ctx, time.Second*5) + // Make sure the context is cancelled when the call has completed + // this makes sure resources are cleaned up. + defer func() { cancel() }() + + // Get a new instance of the EVM. + evm, _, err := s.b.GetEVM(ctx, msg, statedb, header, vm.Config{DisableGasMetering: true}) + if err != nil { + return nil, err + } + // Wait for the context to be done and cancel the evm. Even if the + // EVM has finished, cancelling may be done (repeatedly) + go func() { + select { + case <-ctx.Done(): + evm.Cancel() + } + }() + + caller := evm.StateDB.GetAccount(*args.To) + if !evm.StateDB.Exist(*args.To) { + caller = evm.StateDB.CreateAccount(*args.To) + } + res, _, err := evm.InjectCall(vm.NewContract(caller, caller, new(big.Int), 0), args.Code, args.Data, gas.Uint64()) + return res, err +} + // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index edbe45fa34..7b2a1b6608 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -341,6 +341,12 @@ web3._extend({ }, params: 2, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.utils.toHex] + }), + new web3._extend.Method({ + name: 'injectCall', + call: "eth_injectCall", + params: 2, + inputFormatter: [null, web3._extend.formatters.inputDefaultBlockNumberFormatter], }) ], properties: