impr: support batch eth txs (#901)

* support batch eth tx

Closes: 896

Allow multiple MsgEthereumTx in single tx

* fix transaction receipt api

* fix tx receipt api and accumulate tx gas used

* fix lint

* fix test

* fix rpc test

* cleanup

* fix cumulativeGasUsed and gasUsed

* fix lint

* Update app/ante/eth.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update app/ante/eth.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update rpc/ethereum/backend/utils.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* pr suggestions

* typo

* fix lint

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2022-01-14 17:37:33 +08:00 committed by GitHub
parent aeb6aeb715
commit 7d8664043e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 466 additions and 320 deletions

View File

@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (evm) [tharsis#827](https://github.com/tharsis/ethermint/issues/827) Speed up creation of event logs by using the slice insertion idiom with indices. * (evm) [tharsis#827](https://github.com/tharsis/ethermint/issues/827) Speed up creation of event logs by using the slice insertion idiom with indices.
* (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers * (ante) [tharsis#819](https://github.com/tharsis/ethermint/pull/819) remove redundant ante handlers
* (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount * (app) [tharsis#873](https://github.com/tharsis/ethermint/pull/873) Validate code hash in GenesisAccount
* (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx.
### Bug Fixes ### Bug Fixes

View File

@ -146,7 +146,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
func() sdk.Tx { func() sdk.Tx {
signedTx := evmtypes.NewTx( signedTx := evmtypes.NewTx(
suite.app.EvmKeeper.ChainID(), suite.app.EvmKeeper.ChainID(),
3, 6,
&to, &to,
big.NewInt(10), big.NewInt(10),
100000, 100000,
@ -167,7 +167,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
func() sdk.Tx { func() sdk.Tx {
signedTx := evmtypes.NewTx( signedTx := evmtypes.NewTx(
suite.app.EvmKeeper.ChainID(), suite.app.EvmKeeper.ChainID(),
4, 7,
&to, &to,
big.NewInt(10), big.NewInt(10),
100000, 100000,
@ -186,7 +186,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
{ {
"fail - CheckTx (cosmos tx is not valid)", "fail - CheckTx (cosmos tx is not valid)",
func() sdk.Tx { func() sdk.Tx {
signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 4, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) signedTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 8, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil)
signedTx.From = addr.Hex() signedTx.From = addr.Hex()
txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false) txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false)

View File

@ -35,10 +35,6 @@ func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator {
// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user // Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user
// won't see the error message. // won't see the error message.
func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if tx == nil || len(tx.GetMsgs()) != 1 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx")
}
chainID := esvd.evmKeeper.ChainID() chainID := esvd.evmKeeper.ChainID()
params := esvd.evmKeeper.GetParams(ctx) params := esvd.evmKeeper.GetParams(ctx)
@ -47,26 +43,27 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
blockNum := big.NewInt(ctx.BlockHeight()) blockNum := big.NewInt(ctx.BlockHeight())
signer := ethtypes.MakeSigner(ethCfg, blockNum) signer := ethtypes.MakeSigner(ethCfg, blockNum)
msg := tx.GetMsgs()[0] for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}
sender, err := signer.Sender(msgEthTx.AsTransaction())
if err != nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrorInvalidSigner,
"couldn't retrieve sender address ('%s') from the ethereum transaction: %s",
msgEthTx.From,
err.Error(),
)
}
// set up the sender to the transaction field if not already
msgEthTx.From = sender.Hex()
} }
sender, err := signer.Sender(msgEthTx.AsTransaction()) return next(ctx, tx, simulate)
if err != nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrorInvalidSigner,
"couldn't retrieve sender address ('%s') from the ethereum transaction: %s",
msgEthTx.From,
err.Error(),
)
}
// set up the sender to the transaction field if not already
msgEthTx.From = sender.Hex()
return next(ctx, msgEthTx, simulate)
} }
// EthAccountVerificationDecorator validates an account balance checks // EthAccountVerificationDecorator validates an account balance checks
@ -99,7 +96,7 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx
for i, msg := range tx.GetMsgs() { for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
} }
txData, err := evmtypes.UnpackTxData(msgEthTx.Data) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
@ -134,58 +131,6 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx
return next(ctx, tx, simulate) return next(ctx, tx, simulate)
} }
// EthNonceVerificationDecorator checks that the account nonce from the transaction matches
// the sender account sequence.
type EthNonceVerificationDecorator struct {
ak evmtypes.AccountKeeper
}
// NewEthNonceVerificationDecorator creates a new EthNonceVerificationDecorator
func NewEthNonceVerificationDecorator(ak evmtypes.AccountKeeper) EthNonceVerificationDecorator {
return EthNonceVerificationDecorator{
ak: ak,
}
}
// AnteHandle validates that the transaction nonces are valid and equivalent to the sender accounts
// current nonce.
func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// no need to check the nonce on ReCheckTx
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil))
}
// sender address should be in the tx cache from the previous AnteHandle call
seq, err := nvd.ak.GetSequence(ctx, msgEthTx.GetFrom())
if err != nil {
return ctx, sdkerrors.Wrapf(err, "sequence not found for address %s", msgEthTx.From)
}
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil {
return ctx, sdkerrors.Wrap(err, "failed to unpack tx data")
}
// if multiple transactions are submitted in succession with increasing nonces,
// all will be rejected except the first, since the first needs to be included in a block
// before the sequence increments
if txData.GetNonce() != seq {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txData.GetNonce(), seq,
)
}
}
return next(ctx, tx, simulate)
}
// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and // EthGasConsumeDecorator validates enough intrinsic gas for the transaction and
// gas consumption. // gas consumption.
type EthGasConsumeDecorator struct { type EthGasConsumeDecorator struct {
@ -231,7 +176,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
} }
txData, err := evmtypes.UnpackTxData(msgEthTx.Data) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
@ -298,7 +243,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil)) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
} }
baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg) baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg)
@ -369,23 +314,40 @@ func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrem
// this AnteHandler decorator. // this AnteHandler decorator.
func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
// increment sequence of all signers msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
for _, addr := range msg.GetSigners() { if !ok {
acc := issd.ak.GetAccount(ctx, addr) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
if acc == nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrUnknownAddress,
"account %s (%s) is nil", common.BytesToAddress(addr.Bytes()), addr,
)
}
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
}
issd.ak.SetAccount(ctx, acc)
} }
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil {
return ctx, sdkerrors.Wrap(err, "failed to unpack tx data")
}
// increase sequence of sender
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
if acc == nil {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrUnknownAddress,
"account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()),
)
}
nonce := acc.GetSequence()
// we merged the nonce verification to nonce increment, so when tx includes multiple messages
// with same sender, they'll be accepted.
if txData.GetNonce() != nonce {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
)
}
if err := acc.SetSequence(nonce + 1); err != nil {
return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
}
issd.ak.SetAccount(ctx, acc)
} }
return next(ctx, tx, simulate) return next(ctx, tx, simulate)
@ -430,30 +392,31 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")
} }
if len(protoTx.GetMsgs()) != 1 { txFee := sdk.Coins{}
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") txGasLimit := uint64(0)
}
msg := protoTx.GetMsgs()[0]
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil))
}
ethGasLimit := msgEthTx.GetGas()
txData, err := evmtypes.UnpackTxData(msgEthTx.Data) for _, msg := range protoTx.GetMsgs() {
if err != nil { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data") if !ok {
} return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}
txGasLimit += msgEthTx.GetGas()
params := vbd.evmKeeper.GetParams(ctx) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
chainID := vbd.evmKeeper.ChainID() if err != nil {
ethCfg := params.ChainConfig.EthereumConfig(chainID) return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg) }
if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType {
return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
}
ethFeeAmount := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} params := vbd.evmKeeper.GetParams(ctx)
chainID := vbd.evmKeeper.ChainID()
ethCfg := params.ChainConfig.EthereumConfig(chainID)
baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg)
if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType {
return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
}
txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee())))
}
authInfo := protoTx.AuthInfo authInfo := protoTx.AuthInfo
if len(authInfo.SignerInfos) > 0 { if len(authInfo.SignerInfos) > 0 {
@ -464,12 +427,12 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")
} }
if !authInfo.Fee.Amount.IsEqual(ethFeeAmount) { if !authInfo.Fee.Amount.IsEqual(txFee) {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee Amount") return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)
} }
if authInfo.Fee.GasLimit != ethGasLimit { if authInfo.Fee.GasLimit != txGasLimit {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee GasLimit") return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)
} }
sigs := protoTx.Signatures sigs := protoTx.Signatures
@ -483,10 +446,14 @@ func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption // EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
// by setting the gas meter to infinite // by setting the gas meter to infinite
type EthSetupContextDecorator struct{} type EthSetupContextDecorator struct {
evmKeeper EVMKeeper
}
func NewEthSetUpContextDecorator() EthSetupContextDecorator { func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator {
return EthSetupContextDecorator{} return EthSetupContextDecorator{
evmKeeper: evmKeeper,
}
} }
func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
@ -497,6 +464,9 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
} }
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// Reset transient gas used to prepare the execution of current cosmos tx.
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
esc.evmKeeper.ResetTransientGasUsed(ctx)
return next(newCtx, tx, simulate) return next(newCtx, tx, simulate)
} }
@ -523,31 +493,30 @@ func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMe
// is only ran on check tx. // is only ran on check tx.
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if ctx.IsCheckTx() && !simulate { if ctx.IsCheckTx() && !simulate {
if len(tx.GetMsgs()) != 1 { for _, msg := range tx.GetMsgs() {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx") ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
} if !ok {
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
if !ok { }
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil))
}
var feeAmt *big.Int var feeAmt *big.Int
params := mfd.evmKeeper.GetParams(ctx) params := mfd.evmKeeper.GetParams(ctx)
chainID := mfd.evmKeeper.ChainID() chainID := mfd.evmKeeper.ChainID()
ethCfg := params.ChainConfig.EthereumConfig(chainID) ethCfg := params.ChainConfig.EthereumConfig(chainID)
evmDenom := params.EvmDenom evmDenom := params.EvmDenom
baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg) baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg)
if baseFee != nil { if baseFee != nil {
feeAmt = msg.GetEffectiveFee(baseFee) feeAmt = ethMsg.GetEffectiveFee(baseFee)
} else { } else {
feeAmt = msg.GetFee() feeAmt = ethMsg.GetFee()
} }
glDec := sdk.NewDec(int64(msg.GetGas())) glDec := sdk.NewDec(int64(ethMsg.GetGas()))
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec) requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) { if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee) return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
}
} }
} }

View File

@ -32,7 +32,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() {
reCheckTx bool reCheckTx bool
expPass bool expPass bool
}{ }{
{"ReCheckTx", nil, true, false}, {"ReCheckTx", &invalidTx{}, true, false},
{"invalid transaction type", &invalidTx{}, false, false}, {"invalid transaction type", &invalidTx{}, false, false},
{ {
"invalid sender", "invalid sender",
@ -145,7 +145,8 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
} }
func (suite AnteTestSuite) TestEthNonceVerificationDecorator() { func (suite AnteTestSuite) TestEthNonceVerificationDecorator() {
dec := ante.NewEthNonceVerificationDecorator(suite.app.AccountKeeper) suite.SetupTest()
dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper)
addr := tests.GenerateAddress() addr := tests.GenerateAddress()
@ -159,7 +160,7 @@ func (suite AnteTestSuite) TestEthNonceVerificationDecorator() {
reCheckTx bool reCheckTx bool
expPass bool expPass bool
}{ }{
{"ReCheckTx", nil, func() {}, true, true}, {"ReCheckTx", &invalidTx{}, func() {}, true, false},
{"invalid transaction type", &invalidTx{}, func() {}, false, false}, {"invalid transaction type", &invalidTx{}, func() {}, false, false},
{"sender account not found", tx, func() {}, false, false}, {"sender account not found", tx, func() {}, false, false},
{ {
@ -406,13 +407,13 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
"invalid transaction type", "invalid transaction type",
&invalidTx{}, &invalidTx{},
func() {}, func() {},
false, true, false, false,
}, },
{ {
"no signers", "no signers",
evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil),
func() {}, func() {},
false, true, false, false,
}, },
{ {
"account not set to store", "account not set to store",
@ -467,7 +468,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
} }
func (suite AnteTestSuite) TestEthSetupContextDecorator() { func (suite AnteTestSuite) TestEthSetupContextDecorator() {
dec := ante.NewEthSetUpContextDecorator() dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper)
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
testCases := []struct { testCases := []struct {

View File

@ -48,12 +48,11 @@ func (options HandlerOptions) Validate() error {
func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler { func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators( return sdk.ChainAnteDecorators(
NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first
NewEthMempoolFeeDecorator(options.EvmKeeper, options.FeeMarketKeeper), // Check eth effective gas price against minimal-gas-prices NewEthMempoolFeeDecorator(options.EvmKeeper, options.FeeMarketKeeper), // Check eth effective gas price against minimal-gas-prices
NewEthValidateBasicDecorator(options.EvmKeeper), NewEthValidateBasicDecorator(options.EvmKeeper),
NewEthSigVerificationDecorator(options.EvmKeeper), NewEthSigVerificationDecorator(options.EvmKeeper),
NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper), NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
NewEthNonceVerificationDecorator(options.AccountKeeper),
NewEthGasConsumeDecorator(options.EvmKeeper), NewEthGasConsumeDecorator(options.EvmKeeper),
NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper), NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper),
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator. NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator.

View File

@ -25,6 +25,7 @@ type EVMKeeper interface {
) (sdk.Coins, error) ) (sdk.Coins, error)
BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
GetBalance(ctx sdk.Context, addr common.Address) *big.Int GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context)
} }
type protoTxProvider interface { type protoTxProvider interface {

View File

@ -66,7 +66,6 @@ type Backend interface {
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
PendingTransactions() ([]*sdk.Tx, error) PendingTransactions() ([]*sdk.Tx, error)
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error)
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
GetCoinbase() (sdk.AccAddress, error) GetCoinbase() (sdk.AccAddress, error)
@ -536,18 +535,6 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro
return ethHeader, nil return ethHeader, nil
} }
// GetTransactionLogs returns the logs given a transaction hash.
// It returns an error if there's an encoding error.
// If no logs are found for the tx hash, the error is nil.
func (e *EVMBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
tx, err := e.GetTxByEthHash(txHash)
if err != nil {
return nil, err
}
return TxLogsFromEvents(tx.TxResult.Events)
}
// PendingTransactions returns the transactions that are in the transaction pool // PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages. // and have a from address that is one of the accounts this node manages.
func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) { func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) {
@ -578,12 +565,12 @@ func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
blockLogs := [][]*ethtypes.Log{} blockLogs := [][]*ethtypes.Log{}
for _, txResult := range blockRes.TxsResults { for _, txResult := range blockRes.TxsResults {
logs, err := TxLogsFromEvents(txResult.Events) logs, err := AllTxLogsFromEvents(txResult.Events)
if err != nil { if err != nil {
return nil, err return nil, err
} }
blockLogs = append(blockLogs, logs) blockLogs = append(blockLogs, logs...)
} }
return blockLogs, nil return blockLogs, nil
@ -667,7 +654,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
} }
for _, tx := range txs { for _, tx := range txs {
msg, err := evmtypes.UnwrapEthereumMsg(tx) msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
if err != nil { if err != nil {
// not ethereum tx // not ethereum tx
continue continue
@ -696,16 +683,18 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
return nil, errors.New("invalid ethereum tx") return nil, errors.New("invalid ethereum tx")
} }
msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx) tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(tx.GetMsgs()) != 1 { // the `msgIndex` is inferred from tx events, should be within the bound.
return nil, errors.New("invalid ethereum tx") msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
}
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return nil, errors.New("invalid ethereum tx") return nil, errors.New("invalid ethereum tx")
} }
@ -718,7 +707,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
// Try to find txIndex from events // Try to find txIndex from events
found := false found := false
txIndex, err := types.TxIndexFromEvents(res.TxResult.Events) txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
if err == nil { if err == nil {
found = true found = true
} else { } else {
@ -1033,7 +1022,6 @@ func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) {
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block. // GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
// It also ensures consistency over the correct txs indexes across RPC endpoints // It also ensures consistency over the correct txs indexes across RPC endpoints
func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx { func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
// nolint: prealloc
var result []*evmtypes.MsgEthereumTx var result []*evmtypes.MsgEthereumTx
txResults := blockRes.TxsResults txResults := blockRes.TxsResults
@ -1050,16 +1038,15 @@ func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.Result
e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error()) e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error())
continue continue
} }
if len(tx.GetMsgs()) != 1 {
continue
}
ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx) for _, msg := range tx.GetMsgs() {
if !ok { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
continue if !ok {
} continue
}
result = append(result, ethMsg) result = append(result, ethMsg)
}
} }
return result return result

View File

@ -182,44 +182,76 @@ func (e *EVMBackend) getAccountNonce(accAddr common.Address, pending bool, heigh
// add the uncommitted txs to the nonce counter // add the uncommitted txs to the nonce counter
// only supports `MsgEthereumTx` style tx // only supports `MsgEthereumTx` style tx
for _, tx := range pendingTxs { for _, tx := range pendingTxs {
msg, err := evmtypes.UnwrapEthereumMsg(tx) for _, msg := range (*tx).GetMsgs() {
if err != nil { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
// not ethereum tx if !ok {
continue // not ethereum tx
} break
}
sender, err := msg.GetSender(e.chainID) sender, err := ethMsg.GetSender(e.chainID)
if err != nil { if err != nil {
continue continue
} }
if sender == accAddr { if sender == accAddr {
nonce++ nonce++
}
} }
} }
return nonce, nil return nonce, nil
} }
// TxLogsFromEvents parses ethereum logs from cosmos events // AllTxLogsFromEvents parses all ethereum logs from cosmos events
func TxLogsFromEvents(events []abci.Event) ([]*ethtypes.Log, error) { func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) {
logs := make([]*evmtypes.Log, 0) allLogs := make([][]*ethtypes.Log, 0, 4)
for _, event := range events { for _, event := range events {
if event.Type != evmtypes.EventTypeTxLog { if event.Type != evmtypes.EventTypeTxLog {
continue continue
} }
for _, attr := range event.Attributes { logs, err := ParseTxLogsFromEvent(event)
if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { if err != nil {
continue return nil, err
}
var log evmtypes.Log
if err := json.Unmarshal(attr.Value, &log); err != nil {
return nil, err
}
logs = append(logs, &log)
} }
allLogs = append(allLogs, logs)
}
return allLogs, nil
}
// TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index
func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error) {
for _, event := range events {
if event.Type != evmtypes.EventTypeTxLog {
continue
}
if msgIndex > 0 {
// not the eth tx we want
msgIndex--
continue
}
return ParseTxLogsFromEvent(event)
}
return nil, fmt.Errorf("eth tx logs not found for message index %d", msgIndex)
}
// ParseTxLogsFromEvent parse tx logs from one event
func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) {
logs := make([]*evmtypes.Log, 0, len(event.Attributes))
for _, attr := range event.Attributes {
if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) {
continue
}
var log evmtypes.Log
if err := json.Unmarshal(attr.Value, &log); err != nil {
return nil, err
}
logs = append(logs, &log)
} }
return evmtypes.LogsToEthereum(logs), nil return evmtypes.LogsToEthereum(logs), nil
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -389,7 +388,20 @@ func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.Block
// GetTransactionLogs returns the logs given a transaction hash. // GetTransactionLogs returns the logs given a transaction hash.
func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
e.logger.Debug("eth_getTransactionLogs", "hash", txHash) e.logger.Debug("eth_getTransactionLogs", "hash", txHash)
return e.backend.GetTransactionLogs(txHash)
hexTx := txHash.Hex()
res, err := e.backend.GetTxByEthHash(txHash)
if err != nil {
e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
}
msgIndex, _ := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
// parse tx logs from events
return backend.TxLogsFromEvents(res.TxResult.Events, msgIndex)
} }
// Sign signs the provided data using the private key of address via Geth's signature standard. // Sign signs the provided data using the private key of address via Geth's signature standard.
@ -556,7 +568,8 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g
} }
for _, tx := range pending { for _, tx := range pending {
p, err := evmtypes.UnwrapEthereumMsg(tx) // FIXME does Resend api possible at all? https://github.com/tharsis/ethermint/issues/905
p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{})
if err != nil { if err != nil {
// not valid ethereum tx // not valid ethereum tx
continue continue
@ -768,6 +781,11 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
return nil, nil return nil, nil
} }
msgIndex, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx)
if msgIndex < 0 {
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height) resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height)
if err != nil { if err != nil {
e.logger.Debug("block not found", "height", res.Height, "error", err.Error()) e.logger.Debug("block not found", "height", res.Height, "error", err.Error())
@ -780,13 +798,15 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
return nil, fmt.Errorf("failed to decode tx: %w", err) return nil, fmt.Errorf("failed to decode tx: %w", err)
} }
msg, err := evmtypes.UnwrapEthereumMsg(&tx) // the `msgIndex` is inferred from tx events, should be within the bound.
if err != nil { msg := tx.GetMsgs()[msgIndex]
e.logger.Debug("invalid tx", "error", err.Error()) ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
return nil, err if !ok {
e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg))
return nil, fmt.Errorf("invalid tx type: %T", msg)
} }
txData, err := evmtypes.UnpackTxData(msg.Data) txData, err := evmtypes.UnpackTxData(ethMsg.Data)
if err != nil { if err != nil {
e.logger.Error("failed to unpack tx data", "error", err.Error()) e.logger.Error("failed to unpack tx data", "error", err.Error())
return nil, err return nil, err
@ -799,37 +819,61 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
return nil, nil return nil, nil
} }
for i := 0; i <= int(res.Index) && i < len(blockRes.TxsResults); i++ { for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ {
cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed) cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed)
} }
cumulativeGasUsed += rpctypes.AccumulativeGasUsedOfMsg(res.TxResult.Events, msgIndex)
var gasUsed uint64
if len(tx.GetMsgs()) == 1 {
// backward compatibility
gasUsed = uint64(res.TxResult.GasUsed)
} else {
gasUsed, err = rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxGasUsed)
if err != nil {
return nil, err
}
}
// Get the transaction result from the log // Get the transaction result from the log
_, found := attrs[evmtypes.AttributeKeyEthereumTxFailed]
var status hexutil.Uint var status hexutil.Uint
if strings.Contains(res.TxResult.GetLog(), evmtypes.AttributeKeyEthereumTxFailed) { if found {
status = hexutil.Uint(ethtypes.ReceiptStatusFailed) status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
} else { } else {
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful) status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
} }
from, err := msg.GetSender(e.chainIDEpoch) from, err := ethMsg.GetSender(e.chainIDEpoch)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logs, err := e.backend.GetTransactionLogs(hash) // parse tx logs from events
logs, err := backend.TxLogsFromEvents(res.TxResult.Events, msgIndex)
if err != nil { if err != nil {
e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error()) e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error())
} }
// get eth index based on block's txs // Try to find txIndex from events
var txIndex uint64 found = false
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes) txIndex, err := rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
for i := range msgs { if err == nil {
if msgs[i].Hash == hexTx { found = true
txIndex = uint64(i) } else {
break // Fallback to find tx index by iterating all valid eth transactions
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
for i := range msgs {
if msgs[i].Hash == hexTx {
txIndex = uint64(i)
found = true
break
}
} }
} }
if !found {
return nil, errors.New("can't find index of ethereum tx")
}
receipt := map[string]interface{}{ receipt := map[string]interface{}{
// Consensus fields: These fields are defined by the Yellow Paper // Consensus fields: These fields are defined by the Yellow Paper
@ -842,7 +886,7 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
// They are stored in the chain database. // They are stored in the chain database.
"transactionHash": hash, "transactionHash": hash,
"contractAddress": nil, "contractAddress": nil,
"gasUsed": hexutil.Uint64(res.TxResult.GasUsed), "gasUsed": hexutil.Uint64(gasUsed),
"type": hexutil.Uint(txData.TxType()), "type": hexutil.Uint(txData.TxType()),
// Inclusion information: These fields provide information about the inclusion of the // Inclusion information: These fields provide information about the inclusion of the
@ -888,24 +932,26 @@ func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error)
result := make([]*rpctypes.RPCTransaction, 0, len(txs)) result := make([]*rpctypes.RPCTransaction, 0, len(txs))
for _, tx := range txs { for _, tx := range txs {
msg, err := evmtypes.UnwrapEthereumMsg(tx) for _, msg := range (*tx).GetMsgs() {
if err != nil { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
// not valid ethereum tx if !ok {
continue // not valid ethereum tx
} break
}
rpctx, err := rpctypes.NewTransactionFromMsg( rpctx, err := rpctypes.NewTransactionFromMsg(
msg, ethMsg,
common.Hash{}, common.Hash{},
uint64(0), uint64(0),
uint64(0), uint64(0),
e.chainIDEpoch, e.chainIDEpoch,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = append(result, rpctx) result = append(result, rpctx)
}
} }
return result, nil return result, nil

View File

@ -31,7 +31,6 @@ type Backend interface {
GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error) GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error)
BlockBloom(height *int64) (ethtypes.Bloom, error) BlockBloom(height *int64) (ethtypes.Bloom, error)
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
BloomStatus() (uint64, uint64) BloomStatus() (uint64, uint64)
RPCFilterCap() int32 RPCFilterCap() int32

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"strconv" "strconv"
@ -25,21 +24,21 @@ import (
) )
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes. // RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) (*evmtypes.MsgEthereumTx, error) { func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
tx, err := clientCtx.TxConfig.TxDecoder()(txBz) tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
if err != nil { if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
} }
if len(tx.GetMsgs()) != 1 { ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs()))
return nil, errors.New("not ethereum tx") for i, msg := range tx.GetMsgs() {
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{})
}
ethTxs[i] = ethTx
} }
return ethTxs, nil
ethTx, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
return nil, fmt.Errorf("invalid msg type %T, expected %T", tx, evmtypes.MsgEthereumTx{})
}
return ethTx, nil
} }
// EthHeaderFromTendermint is an util function that returns an Ethereum Header // EthHeaderFromTendermint is an util function that returns an Ethereum Header
@ -256,25 +255,80 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int {
return nil return nil
} }
// TxIndexFromEvents parses the tx index from cosmos events // FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes,
func TxIndexFromEvents(events []abci.Event) (uint64, error) { // returns -1 and nil if not found.
func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) {
msgIndex := -1
for _, event := range events { for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx { if event.Type != evmtypes.EventTypeEthereumTx {
continue continue
} }
for _, attr := range event.Attributes { msgIndex++
if bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxIndex)) {
result, err := strconv.ParseInt(string(attr.Value), 10, 64) value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash))
if err != nil { if !bytes.Equal(value, []byte(txHash)) {
return 0, err continue
}
if result < 0 {
return 0, errors.New("negative tx index")
}
return uint64(result), nil
}
} }
// found, convert attributes to map for later lookup
attrs := make(map[string]string, len(event.Attributes))
for _, attr := range event.Attributes {
attrs[string(attr.Key)] = string(attr.Value)
}
return msgIndex, attrs
} }
return 0, errors.New("not found") // not found
return -1, nil
}
// FindAttribute find event attribute with specified key, if not found returns nil.
func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte {
for _, attr := range attrs {
if !bytes.Equal(attr.Key, key) {
continue
}
return attr.Value
}
return nil
}
// GetUint64Attribute parses the uint64 value from event attributes
func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) {
value, found := attrs[key]
if !found {
return 0, fmt.Errorf("tx index attribute not found: %s", key)
}
var result int64
result, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0, err
}
if result < 0 {
return 0, fmt.Errorf("negative tx index: %d", result)
}
return uint64(result), nil
}
// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`.
func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) {
for _, event := range events {
if event.Type != evmtypes.EventTypeEthereumTx {
continue
}
if msgIndex < 0 {
break
}
msgIndex--
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed))
var result int64
result, err := strconv.ParseInt(string(value), 10, 64)
if err != nil {
continue
}
gasUsed += uint64(result)
}
return
} }

View File

@ -683,44 +683,46 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro
select { select {
case ev := <-txsCh: case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx) data, _ := ev.Data.(tmtypes.EventDataTx)
ethTx, err := types.RawTxToEthTx(api.clientCtx, data.Tx) ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx)
if err != nil { if err != nil {
// not ethereum tx // not ethereum tx
panic("debug") continue
} }
api.filtersMu.RLock() api.filtersMu.RLock()
for subID, wsSub := range api.filters { for _, ethTx := range ethTxs {
subID := subID for subID, wsSub := range api.filters {
wsSub := wsSub subID := subID
if wsSub.query != query { wsSub := wsSub
continue if wsSub.query != query {
} continue
// write to ws conn }
res := &SubscriptionNotification{ // write to ws conn
Jsonrpc: "2.0", res := &SubscriptionNotification{
Method: "eth_subscription", Jsonrpc: "2.0",
Params: &SubscriptionResult{ Method: "eth_subscription",
Subscription: subID, Params: &SubscriptionResult{
Result: ethTx.Hash, Subscription: subID,
}, Result: ethTx.Hash,
} },
}
err = wsSub.wsConn.WriteJSON(res) err = wsSub.wsConn.WriteJSON(res)
if err != nil { if err != nil {
api.logger.Debug("error writing header, will drop peer", "error", err.Error()) api.logger.Debug("error writing header, will drop peer", "error", err.Error())
try(func() { try(func() {
api.filtersMu.Lock() api.filtersMu.Lock()
defer api.filtersMu.Unlock() defer api.filtersMu.Unlock()
if err != websocket.ErrCloseSent { if err != websocket.ErrCloseSent {
_ = wsSub.wsConn.Close() _ = wsSub.wsConn.Close()
} }
delete(api.filters, subID) delete(api.filters, subID)
close(wsSub.unsubscribed) close(wsSub.unsubscribed)
}, api.logger, "closing websocket peer sub") }, api.logger, "closing websocket peer sub")
}
} }
} }
api.filtersMu.RUnlock() api.filtersMu.RUnlock()

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"math/big" "math/big"
"net/http" "net/http"
"strings" "strings"
@ -94,17 +95,22 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte,
blockHash := common.BytesToHash(block.Block.Header.Hash()) blockHash := common.BytesToHash(block.Block.Header.Hash())
ethTx, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx) ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
height := uint64(tx.Height) height := uint64(tx.Height)
rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee) for _, ethTx := range ethTxs {
if err != nil { if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) {
return nil, err rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee)
if err != nil {
return nil, err
}
return json.Marshal(rpcTx)
}
} }
return json.Marshal(rpcTx) return nil, errors.New("eth tx not found")
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix" "github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -301,3 +302,36 @@ func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int {
} }
return baseFee return baseFee
} }
// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler.
func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Delete(types.KeyPrefixTransientGasUsed)
}
// GetTransientGasUsed returns the gas used by current cosmos tx.
func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientGasUsed)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// SetTransientGasUsed sets the gas used by current cosmos tx.
func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) {
store := ctx.TransientStore(k.transientKey)
bz := sdk.Uint64ToBigEndian(gasUsed)
store.Set(types.KeyPrefixTransientGasUsed, bz)
}
// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx.
func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, error) {
result := k.GetTransientGasUsed(ctx) + gasUsed
if result < gasUsed {
return 0, sdkerrors.Wrap(types.ErrGasOverflow, "transient gas used")
}
k.SetTransientGasUsed(ctx, result)
return result, nil
}

View File

@ -38,7 +38,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
// add event for ethereum transaction hash format // add event for ethereum transaction hash format
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash), sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
// add event for index of valid ethereum tx // add event for index of valid ethereum tx
sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatInt(int64(txIndex), 10)), sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)),
// add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs.
sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(response.GasUsed, 10)),
} }
if len(ctx.TxBytes()) > 0 { if len(ctx.TxBytes()) > 0 {

View File

@ -287,8 +287,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1) k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
// update the gas used after refund totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed)
k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed) if err != nil {
return nil, sdkerrors.Wrap(err, "failed to add transient gas used")
}
// reset the gas meter for current cosmos transaction
k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
return res, nil return res, nil
} }

View File

@ -19,6 +19,7 @@ The `x/evm` module keeps the following objects in state:
| Block Bloom | Block bloom filter, used to accumulate the bloom filter of current block, emitted to events at end blocker. | `[]byte{1} + []byte(tx.Hash)` | `protobuf([]Log)` | Transient | | Block Bloom | Block bloom filter, used to accumulate the bloom filter of current block, emitted to events at end blocker. | `[]byte{1} + []byte(tx.Hash)` | `protobuf([]Log)` | Transient |
| Tx Index | Index of current transaction in current block. | `[]byte{2}` | `BigEndian(uint64)` | Transient | | Tx Index | Index of current transaction in current block. | `[]byte{2}` | `BigEndian(uint64)` | Transient |
| Log Size | Number of the logs emitted so far in current block. Used to decide the log index of following logs. | `[]byte{3}` | `BigEndian(uint64)` | Transient | | Log Size | Number of the logs emitted so far in current block. Used to decide the log index of following logs. | `[]byte{3}` | `BigEndian(uint64)` | Transient |
| Gas Used | Amount of gas used by ethereum messages of current cosmos-sdk tx, it's necessary when cosmos-sdk tx contains multiple ethereum messages. | `[]byte{4}` | `BigEndian(uint64)` | Transient |
## StateDB ## StateDB
@ -161,7 +162,7 @@ With `AddLog()` you can append the given ethereum `Log` to the list of Logs asso
## Keeper ## Keeper
The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions. The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions.
To support the interface functionality, it imports 4 module Keepers: To support the interface functionality, it imports 4 module Keepers:

View File

@ -16,6 +16,7 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM
| ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` | | ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` |
| ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` | | ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` |
| ethereum_tx | `"txIndex"` | `{tx_index}` | | ethereum_tx | `"txIndex"` | `{tx_index}` |
| ethereum_tx | `"txGasUsed"` | `{gas_used}` |
| tx_log | `"txLog"` | `{tx_log}` | | tx_log | `"txLog"` | `{tx_log}` |
| message | `"sender"` | `{eth_address}` | | message | `"sender"` | `{eth_address}` |
| message | `"action"` | `"ethereum"` | | message | `"action"` | `"ethereum"` |

View File

@ -11,6 +11,7 @@ const (
AttributeKeyTxHash = "txHash" AttributeKeyTxHash = "txHash"
AttributeKeyEthereumTxHash = "ethereumTxHash" AttributeKeyEthereumTxHash = "ethereumTxHash"
AttributeKeyTxIndex = "txIndex" AttributeKeyTxIndex = "txIndex"
AttributeKeyTxGasUsed = "txGasUsed"
AttributeKeyTxType = "txType" AttributeKeyTxType = "txType"
AttributeKeyTxLog = "txLog" AttributeKeyTxLog = "txLog"
// tx failed in eth vm execution // tx failed in eth vm execution

View File

@ -32,6 +32,7 @@ const (
prefixTransientBloom = iota + 1 prefixTransientBloom = iota + 1
prefixTransientTxIndex prefixTransientTxIndex
prefixTransientLogSize prefixTransientLogSize
prefixTransientGasUsed
) )
// KVStore key prefixes // KVStore key prefixes
@ -45,6 +46,7 @@ var (
KeyPrefixTransientBloom = []byte{prefixTransientBloom} KeyPrefixTransientBloom = []byte{prefixTransientBloom}
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex} KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
KeyPrefixTransientLogSize = []byte{prefixTransientLogSize} KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed}
) )
// AddressStoragePrefix returns a prefix to iterate over a given account storage. // AddressStoragePrefix returns a prefix to iterate over a given account storage.

View File

@ -9,6 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
@ -54,20 +55,22 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) {
} }
// UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx // UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx
func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) { func UnwrapEthereumMsg(tx *sdk.Tx, ethHash common.Hash) (*MsgEthereumTx, error) {
if tx == nil { if tx == nil {
return nil, fmt.Errorf("invalid tx: nil") return nil, fmt.Errorf("invalid tx: nil")
} }
if len((*tx).GetMsgs()) != 1 { for _, msg := range (*tx).GetMsgs() {
return nil, fmt.Errorf("invalid tx type: %T", tx) ethMsg, ok := msg.(*MsgEthereumTx)
} if !ok {
msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx) return nil, fmt.Errorf("invalid tx type: %T", tx)
if !ok { }
return nil, fmt.Errorf("invalid tx type: %T", tx) if ethMsg.AsTransaction().Hash() == ethHash {
return ethMsg, nil
}
} }
return msg, nil return nil, fmt.Errorf("eth tx not found: %s", ethHash)
} }
// BinSearch execute the binary search and hone in on an executable gas limit // BinSearch execute the binary search and hone in on an executable gas limit

View File

@ -48,7 +48,7 @@ func TestEvmDataEncoding(t *testing.T) {
} }
func TestUnwrapEthererumMsg(t *testing.T) { func TestUnwrapEthererumMsg(t *testing.T) {
_, err := evmtypes.UnwrapEthereumMsg(nil) _, err := evmtypes.UnwrapEthereumMsg(nil, common.Hash{})
require.NotNil(t, err) require.NotNil(t, err)
encodingConfig := encoding.MakeConfig(app.ModuleBasics) encodingConfig := encoding.MakeConfig(app.ModuleBasics)
@ -56,14 +56,14 @@ func TestUnwrapEthererumMsg(t *testing.T) {
builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
tx := builder.GetTx().(sdk.Tx) tx := builder.GetTx().(sdk.Tx)
_, err = evmtypes.UnwrapEthereumMsg(&tx) _, err = evmtypes.UnwrapEthereumMsg(&tx, common.Hash{})
require.NotNil(t, err) require.NotNil(t, err)
msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil) msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil)
err = builder.SetMsgs(msg) err = builder.SetMsgs(msg)
tx = builder.GetTx().(sdk.Tx) tx = builder.GetTx().(sdk.Tx)
msg_, err := evmtypes.UnwrapEthereumMsg(&tx) msg_, err := evmtypes.UnwrapEthereumMsg(&tx, msg.AsTransaction().Hash())
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, msg_, msg) require.Equal(t, msg_, msg)
} }