@ -2,10 +2,24 @@ package hmyapi
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"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/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.
@ -81,3 +95,98 @@ func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
header , _ := s . b . HeaderByNumber ( context . Background ( ) , rpc . LatestBlockNumber ) // latest header should always be available
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
}