Merge pull request #1221 from lzl124631x/ricl-call

add hmy_call
pull/1216/head
Richard Liu 5 years ago committed by GitHub
commit 0f33f51c6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      core/blockchain.go
  2. 5
      core/vm/evm.go
  3. 20
      hmy/api_backend.go
  4. 4
      hmy/backend.go
  5. 6
      internal/hmyapi/backend.go
  6. 109
      internal/hmyapi/blockchain.go
  7. 10
      internal/hmyapi/types.go

@ -1844,3 +1844,8 @@ func (bc *BlockChain) StoreEpochBlockNumber(
func (bc *BlockChain) ChainDB() ethdb.Database { func (bc *BlockChain) ChainDB() ethdb.Database {
return bc.db return bc.db
} }
// GetVMConfig returns the block chain VM config.
func (bc *BlockChain) GetVMConfig() *vm.Config {
return &bc.vmConfig
}

@ -169,6 +169,11 @@ func (evm *EVM) Cancel() {
atomic.StoreInt32(&evm.abort, 1) atomic.StoreInt32(&evm.abort, 1)
} }
// Cancelled returns true if Cancel has been called
func (evm *EVM) Cancelled() bool {
return atomic.LoadInt32(&evm.abort) == 1
}
// Interpreter returns the current interpreter // Interpreter returns the current interpreter
func (evm *EVM) Interpreter() Interpreter { func (evm *EVM) Interpreter() Interpreter {
return evm.interpreter return evm.interpreter

@ -3,9 +3,11 @@ package hmy
import ( import (
"context" "context"
"errors" "errors"
"math/big"
"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/common/math"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
@ -16,6 +18,7 @@ import (
"github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
) )
// APIBackend An implementation of internal/hmyapi/Backend. Full client. // APIBackend An implementation of internal/hmyapi/Backend. Full client.
@ -206,3 +209,20 @@ func (b *APIBackend) GetBalance(address common.Address) (*hexutil.Big, error) {
func (b *APIBackend) NetVersion() uint64 { func (b *APIBackend) NetVersion() uint64 {
return b.hmy.NetVersion() return b.hmy.NetVersion()
} }
// GetEVM returns a new EVM entity
func (b *APIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.DB, header *types.Header) (*vm.EVM, func() error, error) {
// TODO(ricl): The code is borrowed from [go-ethereum](https://github.com/ethereum/go-ethereum/blob/40cdcf8c47ff094775aca08fd5d94051f9cf1dbb/les/api_backend.go#L114)
// [question](https://ethereum.stackexchange.com/q/72977/54923)
// Might need to reconsider the SetBalance behavior
state.SetBalance(msg.From(), math.MaxBig256)
vmError := func() error { return nil }
context := core.NewEVMContext(msg, header, b.hmy.BlockChain(), nil)
return vm.NewEVM(context, state, b.hmy.blockchain.Config(), *b.hmy.blockchain.GetVMConfig()), vmError, nil
}
// RPCGasCap returns the gas cap of rpc
func (b *APIBackend) RPCGasCap() *big.Int {
return b.hmy.RPCGasCap // TODO(ricl): should be hmy.config.RPCGasCap
}

@ -33,6 +33,10 @@ type Harmony struct {
// aka network version, which is used to identify which network we are using // aka network version, which is used to identify which network we are using
networkID uint64 networkID uint64
// TODO(ricl): put this into config object
// TODO(ricl): this is never set. Will result in nil pointer bug
// RPCGasCap is the global gas cap for eth-call variants.
RPCGasCap *big.Int `toml:",omitempty"`
} }
// NodeAPI is the list of functions from node used to call rpc apis. // NodeAPI is the list of functions from node used to call rpc apis.

@ -2,6 +2,7 @@ package hmyapi
import ( import (
"context" "context"
"math/big"
"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"
@ -13,6 +14,7 @@ import (
"github.com/harmony-one/harmony/core" "github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/state" "github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types" "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
) )
// Backend interface provides the common API services (that are provided by // Backend interface provides the common API services (that are provided by
@ -30,7 +32,7 @@ type Backend interface {
EventMux() *event.TypeMux EventMux() *event.TypeMux
AccountManager() *accounts.Manager AccountManager() *accounts.Manager
// ExtRPCEnabled() bool // ExtRPCEnabled() bool
// RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection RPCGasCap() *big.Int // global gas cap for hmy_call over rpc: DoS protection
// BlockChain API // BlockChain API
// SetHead(number uint64) // SetHead(number uint64)
@ -40,7 +42,7 @@ type Backend interface {
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
// GetTd(blockHash common.Hash) *big.Int // GetTd(blockHash common.Hash) *big.Int
// GetEVM(ctx context.Context, msg core.Message, state *state.DB, header *types.Header) (*vm.EVM, func() error, error) GetEVM(ctx context.Context, msg core.Message, state *state.DB, header *types.Header) (*vm.EVM, func() error, error)
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription

@ -2,10 +2,24 @@ package hmyapi
import ( import (
"context" "context"
"fmt"
"math/big"
"time"
"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/common/math"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/harmony-one/harmony/core"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/internal/utils"
)
const (
defaultGasPrice = params.GWei
) )
// PublicBlockChainAPI provides an API to access the Harmony blockchain. // PublicBlockChainAPI provides an API to access the Harmony blockchain.
@ -81,3 +95,98 @@ func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available header, _ := s.b.HeaderByNumber(context.Background(), rpc.LatestBlockNumber) // latest header should always be available
return hexutil.Uint64(header.Number.Uint64()) return hexutil.Uint64(header.Number.Uint64())
} }
// 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) {
result, _, _, err := doCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
return (hexutil.Bytes)(result), err
}
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) {
defer func(start time.Time) {
utils.GetLogInstance().Debug("Executing EVM call finished", "runtime", time.Since(start))
}(time.Now())
state, header, err := b.StateAndHeaderByNumber(ctx, blockNr)
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 {
// TODO(ricl): this logic was borrowed from [go-ethereum](https://github.com/ethereum/go-ethereum/blob/f578d41ee6b3087f8021fd561a0b5665aea3dba6/internal/ethapi/api.go#L738)
// [question](https://ethereum.stackexchange.com/questions/72979/why-does-the-docall-function-use-the-first-account-by-default)
// Might need to reconsider the logic
if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
if accounts := wallets[0].Accounts(); len(accounts) > 0 {
addr = accounts[0].Address
}
}
} else {
addr = *args.From
}
// 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 {
utils.GetLogInstance().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
}

@ -158,3 +158,13 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti
} }
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
} }
// 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"`
}

Loading…
Cancel
Save