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:
parent
aeb6aeb715
commit
7d8664043e
@ -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.
|
||||
* (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
|
||||
* (evm) [tharsis#901](https://github.com/tharsis/ethermint/pull/901) Support multiple MsgEthereumTx in single tx.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
@ -146,7 +146,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
func() sdk.Tx {
|
||||
signedTx := evmtypes.NewTx(
|
||||
suite.app.EvmKeeper.ChainID(),
|
||||
3,
|
||||
6,
|
||||
&to,
|
||||
big.NewInt(10),
|
||||
100000,
|
||||
@ -167,7 +167,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
func() sdk.Tx {
|
||||
signedTx := evmtypes.NewTx(
|
||||
suite.app.EvmKeeper.ChainID(),
|
||||
4,
|
||||
7,
|
||||
&to,
|
||||
big.NewInt(10),
|
||||
100000,
|
||||
@ -186,7 +186,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
{
|
||||
"fail - CheckTx (cosmos tx is not valid)",
|
||||
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()
|
||||
|
||||
txBuilder := suite.CreateTestTxBuilder(signedTx, privKey, 1, false)
|
||||
|
255
app/ante/eth.go
255
app/ante/eth.go
@ -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
|
||||
// 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) {
|
||||
if tx == nil || len(tx.GetMsgs()) != 1 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx")
|
||||
}
|
||||
|
||||
chainID := esvd.evmKeeper.ChainID()
|
||||
|
||||
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())
|
||||
signer := ethtypes.MakeSigner(ethCfg, blockNum)
|
||||
|
||||
msg := tx.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))
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
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())
|
||||
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)
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
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)
|
||||
@ -134,58 +131,6 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx
|
||||
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 account’s
|
||||
// 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
|
||||
// gas consumption.
|
||||
type EthGasConsumeDecorator struct {
|
||||
@ -231,7 +176,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
|
||||
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))
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
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() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
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)
|
||||
@ -369,23 +314,40 @@ func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrem
|
||||
// this AnteHandler decorator.
|
||||
func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
// increment sequence of all signers
|
||||
for _, addr := range msg.GetSigners() {
|
||||
acc := issd.ak.GetAccount(ctx, addr)
|
||||
|
||||
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)
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
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)
|
||||
@ -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")
|
||||
}
|
||||
|
||||
if len(protoTx.GetMsgs()) != 1 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx")
|
||||
}
|
||||
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()
|
||||
txFee := sdk.Coins{}
|
||||
txGasLimit := uint64(0)
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
|
||||
}
|
||||
for _, msg := range protoTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
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)
|
||||
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")
|
||||
}
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
|
||||
}
|
||||
|
||||
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
|
||||
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")
|
||||
}
|
||||
|
||||
if !authInfo.Fee.Amount.IsEqual(ethFeeAmount) {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee Amount")
|
||||
if !authInfo.Fee.Amount.IsEqual(txFee) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)
|
||||
}
|
||||
|
||||
if authInfo.Fee.GasLimit != ethGasLimit {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid eth tx AuthInfo Fee GasLimit")
|
||||
if authInfo.Fee.GasLimit != txGasLimit {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)
|
||||
}
|
||||
|
||||
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
|
||||
// by setting the gas meter to infinite
|
||||
type EthSetupContextDecorator struct{}
|
||||
type EthSetupContextDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
func NewEthSetUpContextDecorator() EthSetupContextDecorator {
|
||||
return EthSetupContextDecorator{}
|
||||
func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) 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) {
|
||||
@ -497,6 +464,9 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -523,31 +493,30 @@ func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMe
|
||||
// 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) {
|
||||
if ctx.IsCheckTx() && !simulate {
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx")
|
||||
}
|
||||
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
var feeAmt *big.Int
|
||||
var feeAmt *big.Int
|
||||
|
||||
params := mfd.evmKeeper.GetParams(ctx)
|
||||
chainID := mfd.evmKeeper.ChainID()
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
evmDenom := params.EvmDenom
|
||||
baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
if baseFee != nil {
|
||||
feeAmt = msg.GetEffectiveFee(baseFee)
|
||||
} else {
|
||||
feeAmt = msg.GetFee()
|
||||
}
|
||||
params := mfd.evmKeeper.GetParams(ctx)
|
||||
chainID := mfd.evmKeeper.ChainID()
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
evmDenom := params.EvmDenom
|
||||
baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
if baseFee != nil {
|
||||
feeAmt = ethMsg.GetEffectiveFee(baseFee)
|
||||
} else {
|
||||
feeAmt = ethMsg.GetFee()
|
||||
}
|
||||
|
||||
glDec := sdk.NewDec(int64(msg.GetGas()))
|
||||
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
|
||||
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
|
||||
glDec := sdk.NewDec(int64(ethMsg.GetGas()))
|
||||
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
|
||||
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ func (suite AnteTestSuite) TestEthSigVerificationDecorator() {
|
||||
reCheckTx bool
|
||||
expPass bool
|
||||
}{
|
||||
{"ReCheckTx", nil, true, false},
|
||||
{"ReCheckTx", &invalidTx{}, true, false},
|
||||
{"invalid transaction type", &invalidTx{}, false, false},
|
||||
{
|
||||
"invalid sender",
|
||||
@ -145,7 +145,8 @@ func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
|
||||
}
|
||||
|
||||
func (suite AnteTestSuite) TestEthNonceVerificationDecorator() {
|
||||
dec := ante.NewEthNonceVerificationDecorator(suite.app.AccountKeeper)
|
||||
suite.SetupTest()
|
||||
dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper)
|
||||
|
||||
addr := tests.GenerateAddress()
|
||||
|
||||
@ -159,7 +160,7 @@ func (suite AnteTestSuite) TestEthNonceVerificationDecorator() {
|
||||
reCheckTx bool
|
||||
expPass bool
|
||||
}{
|
||||
{"ReCheckTx", nil, func() {}, true, true},
|
||||
{"ReCheckTx", &invalidTx{}, func() {}, true, false},
|
||||
{"invalid transaction type", &invalidTx{}, func() {}, false, false},
|
||||
{"sender account not found", tx, func() {}, false, false},
|
||||
{
|
||||
@ -406,13 +407,13 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
"invalid transaction type",
|
||||
&invalidTx{},
|
||||
func() {},
|
||||
false, true,
|
||||
false, false,
|
||||
},
|
||||
{
|
||||
"no signers",
|
||||
evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil),
|
||||
func() {},
|
||||
false, true,
|
||||
false, false,
|
||||
},
|
||||
{
|
||||
"account not set to store",
|
||||
@ -467,7 +468,7 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
testCases := []struct {
|
||||
|
@ -48,12 +48,11 @@ func (options HandlerOptions) Validate() error {
|
||||
|
||||
func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
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
|
||||
NewEthValidateBasicDecorator(options.EvmKeeper),
|
||||
NewEthSigVerificationDecorator(options.EvmKeeper),
|
||||
NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
|
||||
NewEthNonceVerificationDecorator(options.AccountKeeper),
|
||||
NewEthGasConsumeDecorator(options.EvmKeeper),
|
||||
NewCanTransferDecorator(options.EvmKeeper, options.FeeMarketKeeper),
|
||||
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator.
|
||||
|
@ -25,6 +25,7 @@ type EVMKeeper interface {
|
||||
) (sdk.Coins, error)
|
||||
BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
|
||||
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
|
||||
ResetTransientGasUsed(ctx sdk.Context)
|
||||
}
|
||||
|
||||
type protoTxProvider interface {
|
||||
|
@ -66,7 +66,6 @@ type Backend interface {
|
||||
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
|
||||
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
||||
PendingTransactions() ([]*sdk.Tx, error)
|
||||
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
|
||||
GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error)
|
||||
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
|
||||
GetCoinbase() (sdk.AccAddress, error)
|
||||
@ -536,18 +535,6 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro
|
||||
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
|
||||
// and have a from address that is one of the accounts this node manages.
|
||||
func (e *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) {
|
||||
@ -578,12 +565,12 @@ func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
|
||||
|
||||
blockLogs := [][]*ethtypes.Log{}
|
||||
for _, txResult := range blockRes.TxsResults {
|
||||
logs, err := TxLogsFromEvents(txResult.Events)
|
||||
logs, err := AllTxLogsFromEvents(txResult.Events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockLogs = append(blockLogs, logs)
|
||||
blockLogs = append(blockLogs, logs...)
|
||||
}
|
||||
|
||||
return blockLogs, nil
|
||||
@ -667,7 +654,7 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
|
||||
}
|
||||
|
||||
for _, tx := range txs {
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(tx)
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
|
||||
if err != nil {
|
||||
// not ethereum tx
|
||||
continue
|
||||
@ -696,16 +683,18 @@ func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransac
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
return nil, errors.New("invalid ethereum tx")
|
||||
}
|
||||
|
||||
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
|
||||
// the `msgIndex` is inferred from tx events, should be within the bound.
|
||||
msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
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
|
||||
found := false
|
||||
txIndex, err := types.TxIndexFromEvents(res.TxResult.Events)
|
||||
txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
|
||||
if err == nil {
|
||||
found = true
|
||||
} else {
|
||||
@ -1033,7 +1022,6 @@ func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) {
|
||||
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
|
||||
// It also ensures consistency over the correct txs indexes across RPC endpoints
|
||||
func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
|
||||
// nolint: prealloc
|
||||
var result []*evmtypes.MsgEthereumTx
|
||||
|
||||
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())
|
||||
continue
|
||||
}
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ethMsg)
|
||||
result = append(result, ethMsg)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -182,44 +182,76 @@ func (e *EVMBackend) getAccountNonce(accAddr common.Address, pending bool, heigh
|
||||
// add the uncommitted txs to the nonce counter
|
||||
// only supports `MsgEthereumTx` style tx
|
||||
for _, tx := range pendingTxs {
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(tx)
|
||||
if err != nil {
|
||||
// not ethereum tx
|
||||
continue
|
||||
}
|
||||
for _, msg := range (*tx).GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
// not ethereum tx
|
||||
break
|
||||
}
|
||||
|
||||
sender, err := msg.GetSender(e.chainID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if sender == accAddr {
|
||||
nonce++
|
||||
sender, err := ethMsg.GetSender(e.chainID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if sender == accAddr {
|
||||
nonce++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// TxLogsFromEvents parses ethereum logs from cosmos events
|
||||
func TxLogsFromEvents(events []abci.Event) ([]*ethtypes.Log, error) {
|
||||
logs := make([]*evmtypes.Log, 0)
|
||||
// AllTxLogsFromEvents parses all ethereum logs from cosmos events
|
||||
func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) {
|
||||
allLogs := make([][]*ethtypes.Log, 0, 4)
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeTxLog {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
logs, err := ParseTxLogsFromEvent(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"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.
|
||||
func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
|
||||
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.
|
||||
@ -556,7 +568,8 @@ func (e *PublicAPI) Resend(ctx context.Context, args evmtypes.TransactionArgs, g
|
||||
}
|
||||
|
||||
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 {
|
||||
// not valid ethereum tx
|
||||
continue
|
||||
@ -768,6 +781,11 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(&tx)
|
||||
if err != nil {
|
||||
e.logger.Debug("invalid tx", "error", err.Error())
|
||||
return nil, err
|
||||
// the `msgIndex` is inferred from tx events, should be within the bound.
|
||||
msg := tx.GetMsgs()[msgIndex]
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
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 {
|
||||
e.logger.Error("failed to unpack tx data", "error", err.Error())
|
||||
return nil, err
|
||||
@ -799,37 +819,61 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
|
||||
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 += 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
|
||||
_, found := attrs[evmtypes.AttributeKeyEthereumTxFailed]
|
||||
var status hexutil.Uint
|
||||
if strings.Contains(res.TxResult.GetLog(), evmtypes.AttributeKeyEthereumTxFailed) {
|
||||
if found {
|
||||
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
|
||||
} else {
|
||||
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
|
||||
}
|
||||
|
||||
from, err := msg.GetSender(e.chainIDEpoch)
|
||||
from, err := ethMsg.GetSender(e.chainIDEpoch)
|
||||
if err != nil {
|
||||
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 {
|
||||
e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error())
|
||||
}
|
||||
|
||||
// get eth index based on block's txs
|
||||
var txIndex uint64
|
||||
msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
|
||||
for i := range msgs {
|
||||
if msgs[i].Hash == hexTx {
|
||||
txIndex = uint64(i)
|
||||
break
|
||||
// Try to find txIndex from events
|
||||
found = false
|
||||
txIndex, err := rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
|
||||
if err == nil {
|
||||
found = true
|
||||
} else {
|
||||
// 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{}{
|
||||
// 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.
|
||||
"transactionHash": hash,
|
||||
"contractAddress": nil,
|
||||
"gasUsed": hexutil.Uint64(res.TxResult.GasUsed),
|
||||
"gasUsed": hexutil.Uint64(gasUsed),
|
||||
"type": hexutil.Uint(txData.TxType()),
|
||||
|
||||
// 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))
|
||||
for _, tx := range txs {
|
||||
msg, err := evmtypes.UnwrapEthereumMsg(tx)
|
||||
if err != nil {
|
||||
// not valid ethereum tx
|
||||
continue
|
||||
}
|
||||
for _, msg := range (*tx).GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
// not valid ethereum tx
|
||||
break
|
||||
}
|
||||
|
||||
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||||
msg,
|
||||
common.Hash{},
|
||||
uint64(0),
|
||||
uint64(0),
|
||||
e.chainIDEpoch,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||||
ethMsg,
|
||||
common.Hash{},
|
||||
uint64(0),
|
||||
uint64(0),
|
||||
e.chainIDEpoch,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, rpctx)
|
||||
result = append(result, rpctx)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
@ -31,7 +31,6 @@ type Backend interface {
|
||||
GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error)
|
||||
BlockBloom(height *int64) (ethtypes.Bloom, error)
|
||||
|
||||
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
|
||||
BloomStatus() (uint64, uint64)
|
||||
|
||||
RPCFilterCap() int32
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
@ -25,21 +24,21 @@ import (
|
||||
)
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
if len(tx.GetMsgs()) != 1 {
|
||||
return nil, errors.New("not ethereum tx")
|
||||
ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs()))
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
return ethTxs, nil
|
||||
}
|
||||
|
||||
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
||||
@ -256,25 +255,80 @@ func BaseFeeFromEvents(events []abci.Event) *big.Int {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxIndexFromEvents parses the tx index from cosmos events
|
||||
func TxIndexFromEvents(events []abci.Event) (uint64, error) {
|
||||
// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes,
|
||||
// returns -1 and nil if not found.
|
||||
func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) {
|
||||
msgIndex := -1
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, attr := range event.Attributes {
|
||||
if bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxIndex)) {
|
||||
result, err := strconv.ParseInt(string(attr.Value), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result < 0 {
|
||||
return 0, errors.New("negative tx index")
|
||||
}
|
||||
return uint64(result), nil
|
||||
}
|
||||
msgIndex++
|
||||
|
||||
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash))
|
||||
if !bytes.Equal(value, []byte(txHash)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -683,44 +683,46 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro
|
||||
select {
|
||||
case ev := <-txsCh:
|
||||
data, _ := ev.Data.(tmtypes.EventDataTx)
|
||||
ethTx, err := types.RawTxToEthTx(api.clientCtx, data.Tx)
|
||||
ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx)
|
||||
if err != nil {
|
||||
// not ethereum tx
|
||||
panic("debug")
|
||||
continue
|
||||
}
|
||||
|
||||
api.filtersMu.RLock()
|
||||
for subID, wsSub := range api.filters {
|
||||
subID := subID
|
||||
wsSub := wsSub
|
||||
if wsSub.query != query {
|
||||
continue
|
||||
}
|
||||
// write to ws conn
|
||||
res := &SubscriptionNotification{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "eth_subscription",
|
||||
Params: &SubscriptionResult{
|
||||
Subscription: subID,
|
||||
Result: ethTx.Hash,
|
||||
},
|
||||
}
|
||||
for _, ethTx := range ethTxs {
|
||||
for subID, wsSub := range api.filters {
|
||||
subID := subID
|
||||
wsSub := wsSub
|
||||
if wsSub.query != query {
|
||||
continue
|
||||
}
|
||||
// write to ws conn
|
||||
res := &SubscriptionNotification{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "eth_subscription",
|
||||
Params: &SubscriptionResult{
|
||||
Subscription: subID,
|
||||
Result: ethTx.Hash,
|
||||
},
|
||||
}
|
||||
|
||||
err = wsSub.wsConn.WriteJSON(res)
|
||||
if err != nil {
|
||||
api.logger.Debug("error writing header, will drop peer", "error", err.Error())
|
||||
err = wsSub.wsConn.WriteJSON(res)
|
||||
if err != nil {
|
||||
api.logger.Debug("error writing header, will drop peer", "error", err.Error())
|
||||
|
||||
try(func() {
|
||||
api.filtersMu.Lock()
|
||||
defer api.filtersMu.Unlock()
|
||||
try(func() {
|
||||
api.filtersMu.Lock()
|
||||
defer api.filtersMu.Unlock()
|
||||
|
||||
if err != websocket.ErrCloseSent {
|
||||
_ = wsSub.wsConn.Close()
|
||||
}
|
||||
if err != websocket.ErrCloseSent {
|
||||
_ = wsSub.wsConn.Close()
|
||||
}
|
||||
|
||||
delete(api.filters, subID)
|
||||
close(wsSub.unsubscribed)
|
||||
}, api.logger, "closing websocket peer sub")
|
||||
delete(api.filters, subID)
|
||||
close(wsSub.unsubscribed)
|
||||
}, api.logger, "closing websocket peer sub")
|
||||
}
|
||||
}
|
||||
}
|
||||
api.filtersMu.RUnlock()
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -94,17 +95,22 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte,
|
||||
|
||||
blockHash := common.BytesToHash(block.Block.Header.Hash())
|
||||
|
||||
ethTx, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
|
||||
ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
height := uint64(tx.Height)
|
||||
|
||||
rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, ethTx := range ethTxs {
|
||||
if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) {
|
||||
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")
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@ -301,3 +302,36 @@ func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
|
||||
// add event for ethereum transaction hash format
|
||||
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
|
||||
// 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 {
|
||||
|
@ -287,8 +287,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
|
||||
|
||||
k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
|
||||
|
||||
// update the gas used after refund
|
||||
k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed)
|
||||
totalGasUsed, err := k.AddTransientGasUsed(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
|
||||
}
|
||||
|
||||
|
@ -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 |
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
@ -161,7 +162,7 @@ With `AddLog()` you can append the given ethereum `Log` to the list of Logs asso
|
||||
|
||||
## 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:
|
||||
|
||||
|
@ -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 | `"ethereumTxHash"` | `{hex_hash}` |
|
||||
| ethereum_tx | `"txIndex"` | `{tx_index}` |
|
||||
| ethereum_tx | `"txGasUsed"` | `{gas_used}` |
|
||||
| tx_log | `"txLog"` | `{tx_log}` |
|
||||
| message | `"sender"` | `{eth_address}` |
|
||||
| message | `"action"` | `"ethereum"` |
|
||||
|
@ -11,6 +11,7 @@ const (
|
||||
AttributeKeyTxHash = "txHash"
|
||||
AttributeKeyEthereumTxHash = "ethereumTxHash"
|
||||
AttributeKeyTxIndex = "txIndex"
|
||||
AttributeKeyTxGasUsed = "txGasUsed"
|
||||
AttributeKeyTxType = "txType"
|
||||
AttributeKeyTxLog = "txLog"
|
||||
// tx failed in eth vm execution
|
||||
|
@ -32,6 +32,7 @@ const (
|
||||
prefixTransientBloom = iota + 1
|
||||
prefixTransientTxIndex
|
||||
prefixTransientLogSize
|
||||
prefixTransientGasUsed
|
||||
)
|
||||
|
||||
// KVStore key prefixes
|
||||
@ -45,6 +46,7 @@ var (
|
||||
KeyPrefixTransientBloom = []byte{prefixTransientBloom}
|
||||
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
|
||||
KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
|
||||
KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed}
|
||||
)
|
||||
|
||||
// AddressStoragePrefix returns a prefix to iterate over a given account storage.
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
@ -54,20 +55,22 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("invalid tx: nil")
|
||||
}
|
||||
|
||||
if len((*tx).GetMsgs()) != 1 {
|
||||
return nil, fmt.Errorf("invalid tx type: %T", tx)
|
||||
}
|
||||
msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid tx type: %T", tx)
|
||||
for _, msg := range (*tx).GetMsgs() {
|
||||
ethMsg, ok := msg.(*MsgEthereumTx)
|
||||
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
|
||||
|
@ -48,7 +48,7 @@ func TestEvmDataEncoding(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnwrapEthererumMsg(t *testing.T) {
|
||||
_, err := evmtypes.UnwrapEthereumMsg(nil)
|
||||
_, err := evmtypes.UnwrapEthereumMsg(nil, common.Hash{})
|
||||
require.NotNil(t, err)
|
||||
|
||||
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
||||
@ -56,14 +56,14 @@ func TestUnwrapEthererumMsg(t *testing.T) {
|
||||
builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
|
||||
|
||||
tx := builder.GetTx().(sdk.Tx)
|
||||
_, err = evmtypes.UnwrapEthereumMsg(&tx)
|
||||
_, err = evmtypes.UnwrapEthereumMsg(&tx, common.Hash{})
|
||||
require.NotNil(t, err)
|
||||
|
||||
msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil)
|
||||
err = builder.SetMsgs(msg)
|
||||
|
||||
tx = builder.GetTx().(sdk.Tx)
|
||||
msg_, err := evmtypes.UnwrapEthereumMsg(&tx)
|
||||
msg_, err := evmtypes.UnwrapEthereumMsg(&tx, msg.AsTransaction().Hash())
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, msg_, msg)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user