begin eth_Call backend integration
This commit is contained in:
parent
71bc3f8e7b
commit
33a0c8e0e7
145
pkg/eth/api.go
145
pkg/eth/api.go
@ -18,10 +18,18 @@ package eth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/vulcanize/ipld-eth-indexer/pkg/eth"
|
"github.com/vulcanize/ipld-eth-indexer/pkg/eth"
|
||||||
"github.com/vulcanize/ipld-eth-server/pkg/shared"
|
"github.com/vulcanize/ipld-eth-server/pkg/shared"
|
||||||
|
"github.com/vulcanize/priority-queue-go-ethereum/core"
|
||||||
|
"github.com/vulcanize/priority-queue-go-ethereum/core/vm"
|
||||||
|
"github.com/vulcanize/priority-queue-go-ethereum/log"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -195,3 +203,140 @@ func (pea *PublicEthAPI) GetTransactionByHash(ctx context.Context, hash common.H
|
|||||||
// Transaction unknown, return as such
|
// Transaction unknown, return as such
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallArgs represents the arguments for a call.
|
||||||
|
type CallArgs struct {
|
||||||
|
From *common.Address `json:"from"`
|
||||||
|
To *common.Address `json:"to"`
|
||||||
|
Gas *hexutil.Uint64 `json:"gas"`
|
||||||
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Data *hexutil.Bytes `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// account indicates the overriding fields of account during the execution of
|
||||||
|
// a message call.
|
||||||
|
// Note, state and stateDiff can't be specified at the same time. If state is
|
||||||
|
// set, message execution will only use the data in the given state. Otherwise
|
||||||
|
// if statDiff is set, all diff will be applied first and then execute the call
|
||||||
|
// message.
|
||||||
|
type account struct {
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||||
|
Code *hexutil.Bytes `json:"code"`
|
||||||
|
Balance **hexutil.Big `json:"balance"`
|
||||||
|
State *map[common.Hash]common.Hash `json:"state"`
|
||||||
|
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
|
||||||
|
defer func(start time.Time) {
|
||||||
|
logrus.Debugf("Executing EVM call finished %s runtime %s", time.Since(start).String())
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
|
if state == nil || err != nil {
|
||||||
|
return nil, 0, false, err
|
||||||
|
}
|
||||||
|
// Set sender address or use a default if none specified
|
||||||
|
var addr common.Address
|
||||||
|
if args.From == nil {
|
||||||
|
if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
|
||||||
|
if accounts := wallets[0].Accounts(); len(accounts) > 0 {
|
||||||
|
addr = accounts[0].Address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = *args.From
|
||||||
|
}
|
||||||
|
// Override the fields of specified contracts before execution.
|
||||||
|
for addr, account := range overrides {
|
||||||
|
// Override account nonce.
|
||||||
|
if account.Nonce != nil {
|
||||||
|
state.SetNonce(addr, uint64(*account.Nonce))
|
||||||
|
}
|
||||||
|
// Override account(contract) code.
|
||||||
|
if account.Code != nil {
|
||||||
|
state.SetCode(addr, *account.Code)
|
||||||
|
}
|
||||||
|
// Override account balance.
|
||||||
|
if account.Balance != nil {
|
||||||
|
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
||||||
|
}
|
||||||
|
if account.State != nil && account.StateDiff != nil {
|
||||||
|
return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
||||||
|
}
|
||||||
|
// Replace entire state if caller requires.
|
||||||
|
if account.State != nil {
|
||||||
|
state.SetStorage(addr, *account.State)
|
||||||
|
}
|
||||||
|
// Apply state diff into specified accounts.
|
||||||
|
if account.StateDiff != nil {
|
||||||
|
for key, value := range *account.StateDiff {
|
||||||
|
state.SetState(addr, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set default gas & gas price if none were set
|
||||||
|
gas := uint64(math.MaxUint64 / 2)
|
||||||
|
if args.Gas != nil {
|
||||||
|
gas = uint64(*args.Gas)
|
||||||
|
}
|
||||||
|
if globalGasCap != nil && globalGasCap.Uint64() < gas {
|
||||||
|
log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
|
||||||
|
gas = globalGasCap.Uint64()
|
||||||
|
}
|
||||||
|
gasPrice := new(big.Int).SetUint64(defaultGasPrice)
|
||||||
|
if args.GasPrice != nil {
|
||||||
|
gasPrice = args.GasPrice.ToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
value := new(big.Int)
|
||||||
|
if args.Value != nil {
|
||||||
|
value = args.Value.ToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
if args.Data != nil {
|
||||||
|
data = []byte(*args.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new call message
|
||||||
|
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, 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.
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
if timeout > 0 {
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
|
} else {
|
||||||
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
|
}
|
||||||
|
// Make sure the context is cancelled when the call has completed
|
||||||
|
// this makes sure resources are cleaned up.
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Get a new instance of the EVM.
|
||||||
|
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, false, 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() {
|
||||||
|
<-ctx.Done()
|
||||||
|
evm.Cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Setup the gas pool (also for unmetered requests)
|
||||||
|
// and apply the message.
|
||||||
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
|
res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
|
||||||
|
if err := vmError(); err != nil {
|
||||||
|
return nil, 0, false, err
|
||||||
|
}
|
||||||
|
// If the timer caused an abort, return an appropriate error message
|
||||||
|
if evm.Cancelled() {
|
||||||
|
return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
||||||
|
}
|
||||||
|
return res, gas, failed, err
|
||||||
|
}
|
||||||
|
@ -30,10 +30,10 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
eth2 "github.com/vulcanize/ipld-eth-indexer/pkg/eth"
|
eth2 "github.com/vulcanize/ipld-eth-indexer/pkg/eth"
|
||||||
"github.com/vulcanize/ipld-eth-indexer/pkg/eth/mocks"
|
|
||||||
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
|
"github.com/vulcanize/ipld-eth-indexer/pkg/postgres"
|
||||||
|
|
||||||
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
||||||
|
"github.com/vulcanize/ipld-eth-server/pkg/eth/mocks"
|
||||||
"github.com/vulcanize/ipld-eth-server/pkg/shared"
|
"github.com/vulcanize/ipld-eth-server/pkg/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
@ -510,3 +512,52 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateAndHeaderByNumberOrHash returns the statedb and header for the provided block number or hash
|
||||||
|
func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
|
||||||
|
if blockNr, ok := blockNrOrHash.Number(); ok {
|
||||||
|
return b.StateAndHeaderByNumber(ctx, blockNr)
|
||||||
|
}
|
||||||
|
if hash, ok := blockNrOrHash.Hash(); ok {
|
||||||
|
header, err := b.HeaderByHash(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if header == nil {
|
||||||
|
return nil, nil, errors.New("header for hash not found")
|
||||||
|
}
|
||||||
|
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
|
||||||
|
return nil, nil, errors.New("hash is not currently canonical")
|
||||||
|
}
|
||||||
|
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
|
||||||
|
return stateDb, header, err
|
||||||
|
}
|
||||||
|
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
return b.GetHeaderByHash(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backend) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateAndHeaderByNumber returns the statedb and header for a provided block number
|
||||||
|
func (b *Backend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
|
||||||
|
// Pending state is only known by the miner
|
||||||
|
if number == rpc.PendingBlockNumber {
|
||||||
|
block, state := b.eth.miner.Pending()
|
||||||
|
return state, block.Header(), nil
|
||||||
|
}
|
||||||
|
// Otherwise resolve the block number and return its state
|
||||||
|
header, err := b.HeaderByNumber(ctx, number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if header == nil {
|
||||||
|
return nil, nil, errors.New("header not found")
|
||||||
|
}
|
||||||
|
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
|
||||||
|
return stateDb, header, err
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user