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.
|
* (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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
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
|
// 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 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
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -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"` |
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user