Implements eth_call (#127)

* Fixed tx receipt error on failed transaction

* Add returnData to failed transaction for logs bloom

* Added simulate call option, without returning evm data

* Added encoding and decoding of data from EVM execution for usability

* Remove unused context parameter

* Fix function comment and remove unnecessary logging on eth_call
This commit is contained in:
Austin Abell
2019-10-22 11:40:34 -05:00
committed by Dustin Brickwood
parent 475919274e
commit 2b4d2bea82
7 changed files with 190 additions and 30 deletions
+4 -2
View File
@@ -20,8 +20,8 @@ func NewHandler(keeper Keeper) sdk.Handler {
switch msg := msg.(type) {
case types.EthereumTxMsg:
return handleETHTxMsg(ctx, keeper, msg)
case types.EmintMsg:
return handleEmintMsg(ctx, keeper, msg)
case *types.EmintMsg:
return handleEmintMsg(ctx, keeper, *msg)
default:
errMsg := fmt.Sprintf("Unrecognized ethermint Msg type: %v", msg.Type())
return sdk.ErrUnknownRequest(errMsg).Result()
@@ -67,6 +67,7 @@ func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk
Csdb: keeper.csdb.WithContext(ctx),
ChainID: intChainID,
THash: &ethHash,
Simulate: ctx.IsCheckTx(),
}
// Prepare db for logs
keeper.csdb.Prepare(ethHash, common.Hash{}, keeper.txCount.get())
@@ -97,6 +98,7 @@ func handleEmintMsg(ctx sdk.Context, keeper Keeper, msg types.EmintMsg) sdk.Resu
Payload: msg.Payload,
Csdb: keeper.csdb.WithContext(ctx),
ChainID: intChainID,
Simulate: ctx.IsCheckTx(),
}
if msg.Recipient != nil {
+1 -1
View File
@@ -391,7 +391,7 @@ func GenerateFromArgs(args args.SendTxArgs, ctx context.CLIContext) (msg *Ethere
if args.GasPrice == nil {
// Set default gas price
// TODO: Change to min gas price from context once available through server/daemon
gasPrice = big.NewInt(20)
gasPrice = big.NewInt(types.DefaultGasPrice)
}
if args.Nonce == nil {
+15 -10
View File
@@ -25,6 +25,7 @@ type StateTransition struct {
Csdb *CommitStateDB
ChainID *big.Int
THash *common.Hash
Simulate bool
}
// TransitionCSDB performs an evm state transition from a transaction
@@ -60,6 +61,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (sdk.Result, *big.Int)
)
var (
ret []byte
leftOverGas uint64
addr common.Address
vmerr error
@@ -67,11 +69,11 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (sdk.Result, *big.Int)
)
if contractCreation {
_, addr, leftOverGas, vmerr = vmenv.Create(senderRef, st.Payload, gasLimit, st.Amount)
ret, addr, leftOverGas, vmerr = vmenv.Create(senderRef, st.Payload, gasLimit, st.Amount)
} else {
// Increment the nonce for the next transaction
st.Csdb.SetNonce(st.Sender, st.Csdb.GetNonce(st.Sender)+1)
_, leftOverGas, vmerr = vmenv.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
ret, leftOverGas, vmerr = vmenv.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
}
// Generate bloom filter to be saved in tx receipt data
@@ -83,8 +85,8 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (sdk.Result, *big.Int)
bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
}
// TODO: coniditionally add either/ both of these to return data
returnData := append(addr.Bytes(), bloomFilter.Bytes()...)
// Encode all necessary data into slice of bytes to return in sdk result
returnData := EncodeReturnData(addr, bloomFilter, ret)
// handle errors
if vmerr != nil {
@@ -105,12 +107,15 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (sdk.Result, *big.Int)
}
func (st *StateTransition) checkNonce() sdk.Result {
// Make sure this transaction's nonce is correct.
nonce := st.Csdb.GetNonce(st.Sender)
if nonce < st.AccountNonce {
return emint.ErrInvalidNonce("nonce too high").Result()
} else if nonce > st.AccountNonce {
return emint.ErrInvalidNonce("nonce too low").Result()
// If simulated transaction, don't verify nonce
if !st.Simulate {
// Make sure this transaction's nonce is correct.
nonce := st.Csdb.GetNonce(st.Sender)
if nonce < st.AccountNonce {
return emint.ErrInvalidNonce("nonce too high").Result()
} else if nonce > st.AccountNonce {
return emint.ErrInvalidNonce("nonce too low").Result()
}
}
return sdk.Result{}
+27
View File
@@ -5,6 +5,7 @@ import (
"github.com/cosmos/ethermint/crypto"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
@@ -12,6 +13,11 @@ import (
"golang.org/x/crypto/sha3"
)
const (
bloomIdx = ethcmn.AddressLength
returnIdx = bloomIdx + ethtypes.BloomByteLength
)
// GenerateEthAddress generates an Ethereum address.
func GenerateEthAddress() ethcmn.Address {
priv, err := crypto.GenerateKey()
@@ -46,3 +52,24 @@ func rlpHash(x interface{}) (hash ethcmn.Hash) {
return hash
}
// EncodeReturnData takes all of the necessary data from the EVM execution
// and returns the data as a byte slice
func EncodeReturnData(addr ethcmn.Address, bloom ethtypes.Bloom, evmRet []byte) []byte {
// Append address, bloom, evm return bytes in that order
returnData := append(addr.Bytes(), bloom.Bytes()...)
return append(returnData, evmRet...)
}
// DecodeReturnData decodes the byte slice of values to their respective types
func DecodeReturnData(bytes []byte) (addr ethcmn.Address, bloom ethtypes.Bloom, ret []byte, err error) {
if len(bytes) >= returnIdx {
addr = ethcmn.BytesToAddress(bytes[:bloomIdx])
bloom = ethtypes.BytesToBloom(bytes[bloomIdx:returnIdx])
ret = bytes[returnIdx:]
} else {
err = fmt.Errorf("Invalid format for encoded data, message must be an EVM state transition")
}
return
}
+24
View File
@@ -0,0 +1,24 @@
package types
import (
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
func TestEvmDataEncoding(t *testing.T) {
addr := ethcmn.HexToAddress("0x12345")
bloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
ret := []byte{0x5, 0x8}
encoded := EncodeReturnData(addr, bloom, ret)
decAddr, decBloom, decRet, err := DecodeReturnData(encoded)
require.NoError(t, err)
require.Equal(t, addr, decAddr)
require.Equal(t, bloom, decBloom)
require.Equal(t, ret, decRet)
}