R4R: CheckTx AnteHandler for Ethereum Tx Messages (#505)
* Rename Ethereum tx message * Use new tx decoder in the ethermint app * Update ante handler to prevent spam/dos * Update ethereum msg signing/verification logic * Implement secp256k1 key types * Remove pointer from To method * Move sig check to after inartistic gas check * Add comment on chainID parsing * Updated validateIntrinsicGas godoc * Implement Fee method on eth tx msg * Add reference to spec for recoverEthSig * Upgrade TM to v0.27.0
This commit is contained in:
parent
a3619584f8
commit
b821a85a64
26
Gopkg.lock
generated
26
Gopkg.lock
generated
@ -33,7 +33,8 @@
|
|||||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:e70ff0ca07fdc5dfe6cf280917ae4434f069f164a0bf18681e37cb19b01ee151"
|
branch = "develop"
|
||||||
|
digest = "1:83eccb94de6a2aadff1bddb7020e3850131645ff45e9ce9093d1604e407130e7"
|
||||||
name = "github.com/cosmos/cosmos-sdk"
|
name = "github.com/cosmos/cosmos-sdk"
|
||||||
packages = [
|
packages = [
|
||||||
"baseapp",
|
"baseapp",
|
||||||
@ -56,7 +57,7 @@
|
|||||||
"x/stake/types",
|
"x/stake/types",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
|
revision = "ec9c4ea543b5d0f558cf6ad9f1386d26cfe87f28"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
|
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
|
||||||
@ -530,23 +531,23 @@
|
|||||||
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
|
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:bcaa26e82b7707fbdfa6d0506ff1f30158eeb23126f761ac65881ed51339bf9e"
|
digest = "1:02462ad4cc9b135c4ebfb9edccb53e8c705bbebdbf8664799ea641440188387e"
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "github.com/tendermint/go-amino"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12"
|
revision = "dc14acf9ef15f85828bfbc561ed9dd9d2a284885"
|
||||||
version = "v0.14.0"
|
version = "v0.14.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:1e4b8f8f8c428af22d0cc68b1478bef4e144edefc4bb967d4d09aaddbc8cd71e"
|
digest = "1:8a1dc8fc625c867614b48327112718c51c0ca83453c8a043f2f23721e19b353f"
|
||||||
name = "github.com/tendermint/iavl"
|
name = "github.com/tendermint/iavl"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac"
|
revision = "de0740903a67b624d887f9055d4c60175dcfa758"
|
||||||
version = "v0.11.1"
|
version = "v0.12.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:ea9b485e28ee25ef860451187afdec9c38630932e1f999114eb7cab7f8c89966"
|
digest = "1:844c7ba6332e1e6f073d7ac69768fd19f761bdf5964b559bd4b47103a0629144"
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
packages = [
|
packages = [
|
||||||
"abci/client",
|
"abci/client",
|
||||||
@ -609,8 +610,7 @@
|
|||||||
"version",
|
"version",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "22dcc92232cd04ce7381043e09d85dd536ae3b96"
|
revision = "v0.27.0"
|
||||||
version = "v0.26.3"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c"
|
digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c"
|
||||||
@ -784,6 +784,7 @@
|
|||||||
"github.com/ethereum/go-ethereum/core/types",
|
"github.com/ethereum/go-ethereum/core/types",
|
||||||
"github.com/ethereum/go-ethereum/core/vm",
|
"github.com/ethereum/go-ethereum/core/vm",
|
||||||
"github.com/ethereum/go-ethereum/crypto",
|
"github.com/ethereum/go-ethereum/crypto",
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/secp256k1",
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3",
|
"github.com/ethereum/go-ethereum/crypto/sha3",
|
||||||
"github.com/ethereum/go-ethereum/params",
|
"github.com/ethereum/go-ethereum/params",
|
||||||
"github.com/ethereum/go-ethereum/rlp",
|
"github.com/ethereum/go-ethereum/rlp",
|
||||||
@ -792,11 +793,8 @@
|
|||||||
"github.com/pkg/errors",
|
"github.com/pkg/errors",
|
||||||
"github.com/stretchr/testify/require",
|
"github.com/stretchr/testify/require",
|
||||||
"github.com/stretchr/testify/suite",
|
"github.com/stretchr/testify/suite",
|
||||||
"github.com/tendermint/btcd/btcec",
|
|
||||||
"github.com/tendermint/tendermint/abci/types",
|
"github.com/tendermint/tendermint/abci/types",
|
||||||
"github.com/tendermint/tendermint/crypto",
|
"github.com/tendermint/tendermint/crypto",
|
||||||
"github.com/tendermint/tendermint/crypto/ed25519",
|
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1",
|
|
||||||
"github.com/tendermint/tendermint/libs/common",
|
"github.com/tendermint/tendermint/libs/common",
|
||||||
"github.com/tendermint/tendermint/libs/db",
|
"github.com/tendermint/tendermint/libs/db",
|
||||||
"github.com/tendermint/tendermint/libs/log",
|
"github.com/tendermint/tendermint/libs/log",
|
||||||
|
16
Gopkg.toml
16
Gopkg.toml
@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/cosmos/cosmos-sdk"
|
name = "github.com/cosmos/cosmos-sdk"
|
||||||
# TODO: Replace this with 0.27
|
revision = "ec9c4ea543b5d0f558cf6ad9f1386d26cfe87f28"
|
||||||
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
|
# version = "v0.28.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/hashicorp/golang-lru"
|
name = "github.com/hashicorp/golang-lru"
|
||||||
@ -40,20 +40,20 @@
|
|||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "github.com/tendermint/go-amino"
|
||||||
version = "v0.14.0"
|
version = "v0.14.1"
|
||||||
|
|
||||||
[[override]]
|
|
||||||
name = "github.com/tendermint/iavl"
|
|
||||||
version = "=v0.11.1"
|
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
source = "https://github.com/tendermint/crypto"
|
source = "https://github.com/tendermint/crypto"
|
||||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/tendermint/iavl"
|
||||||
|
version = "v0.12.0"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
version = "v0.26.1"
|
revision = "v0.27.0"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
|
296
app/ante.go
296
app/ante.go
@ -1,29 +1,28 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto"
|
||||||
|
"github.com/cosmos/ethermint/types"
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
)
|
|
||||||
|
|
||||||
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 // used for tx simulation
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethcore "github.com/ethereum/go-ethereum/core"
|
||||||
|
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
memoCostPerByte = 1
|
memoCostPerByte sdk.Gas = 3
|
||||||
maxMemoCharacters = 100
|
secp256k1VerifyCost = 21000
|
||||||
secp256k1VerifyCost = 100
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
// NewAnteHandler returns an ante handler responsible for attempting to route an
|
||||||
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
|
|
||||||
copy(dummySecp256k1Pubkey[:], bz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAnteHandler returns an ante handelr responsible for attempting to route an
|
|
||||||
// Ethereum or SDK transaction to an internal ante handler for performing
|
// Ethereum or SDK transaction to an internal ante handler for performing
|
||||||
// transaction-level processing (e.g. fee payment, signature verification) before
|
// transaction-level processing (e.g. fee payment, signature verification) before
|
||||||
// being passed onto it's respective handler.
|
// being passed onto it's respective handler.
|
||||||
@ -35,22 +34,130 @@ func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.Ant
|
|||||||
ctx sdk.Context, tx sdk.Tx, sim bool,
|
ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||||
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
|
||||||
stdTx, ok := tx.(auth.StdTx)
|
switch castTx := tx.(type) {
|
||||||
if !ok {
|
case auth.StdTx:
|
||||||
return ctx, sdk.ErrInternal("transaction type invalid: must be StdTx").Result(), true
|
return sdkAnteHandler(ctx, ak, fck, castTx, sim)
|
||||||
|
|
||||||
|
case *evmtypes.EthereumTxMsg:
|
||||||
|
return ethAnteHandler(ctx, castTx, ak)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ctx, sdk.ErrInternal(fmt.Sprintf("transaction type invalid: %T", tx)).Result(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// SDK Ante Handler
|
||||||
|
|
||||||
|
func sdkAnteHandler(
|
||||||
|
ctx sdk.Context, ak auth.AccountKeeper, fck auth.FeeCollectionKeeper, stdTx auth.StdTx, sim bool,
|
||||||
|
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
|
||||||
|
// Ensure that the provided fees meet a minimum threshold for the validator,
|
||||||
|
// if this is a CheckTx. This is only for local mempool purposes, and thus
|
||||||
|
// is only ran on check tx.
|
||||||
|
if ctx.IsCheckTx() && !sim {
|
||||||
|
res := auth.EnsureSufficientMempoolFees(ctx, stdTx)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return newCtx, res, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx = auth.SetGasMeter(sim, ctx, stdTx)
|
||||||
|
|
||||||
|
// AnteHandlers must have their own defer/recover in order for the BaseApp
|
||||||
|
// to know how much gas was used! This is because the GasMeter is created in
|
||||||
|
// the AnteHandler, but if it panics the context won't be set properly in
|
||||||
|
// runTx's recover call.
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch rType := r.(type) {
|
||||||
|
case sdk.ErrorOutOfGas:
|
||||||
|
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
|
||||||
|
res = sdk.ErrOutOfGas(log).Result()
|
||||||
|
res.GasWanted = stdTx.Fee.Gas
|
||||||
|
res.GasUsed = newCtx.GasMeter().GasConsumed()
|
||||||
|
abort = true
|
||||||
|
default:
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := stdTx.ValidateBasic(); err != nil {
|
||||||
|
return newCtx, err.Result(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo")
|
||||||
|
|
||||||
|
signerAccs, res := auth.GetSignerAccs(newCtx, ak, stdTx.GetSigners())
|
||||||
|
if !res.IsOK() {
|
||||||
|
return newCtx, res, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// the first signer pays the transaction fees
|
||||||
|
if !stdTx.Fee.Amount.IsZero() {
|
||||||
|
signerAccs[0], res = auth.DeductFees(signerAccs[0], stdTx.Fee)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return newCtx, res, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle gas/fee checking and spam prevention. We may need two
|
fck.AddCollectedFees(newCtx, stdTx.Fee.Amount)
|
||||||
// different models for SDK and Ethereum txs. The SDK currently supports a
|
}
|
||||||
// primitive model where a constant gas price is used.
|
|
||||||
//
|
|
||||||
// Ref: #473
|
|
||||||
|
|
||||||
if ethTx, ok := isEthereumTx(stdTx); ethTx != nil && ok {
|
isGenesis := ctx.BlockHeight() == 0
|
||||||
return ethAnteHandler(ctx, ethTx, ak)
|
signBytesList := auth.GetSignBytesList(newCtx.ChainID(), stdTx, signerAccs, isGenesis)
|
||||||
|
stdSigs := stdTx.GetSignatures()
|
||||||
|
|
||||||
|
for i := 0; i < len(stdSigs); i++ {
|
||||||
|
// check signature, return account with incremented nonce
|
||||||
|
signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], sim)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return newCtx, res, true
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.NewAnteHandler(ak, fck)(ctx, stdTx, sim)
|
ak.SetAccount(newCtx, signerAccs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSig verifies the signature and increments the nonce. If the account
|
||||||
|
// doesn't have a pubkey, set it.
|
||||||
|
func processSig(
|
||||||
|
ctx sdk.Context, acc auth.Account, sig auth.StdSignature, signBytes []byte, sim bool,
|
||||||
|
) (updatedAcc auth.Account, res sdk.Result) {
|
||||||
|
|
||||||
|
pubKey, res := auth.ProcessPubKey(acc, sig, sim)
|
||||||
|
if !res.IsOK() {
|
||||||
|
return nil, res
|
||||||
|
}
|
||||||
|
|
||||||
|
err := acc.SetPubKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdk.ErrInternal("failed to set PubKey on signer account").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeSigGas(ctx.GasMeter(), pubKey)
|
||||||
|
if !sim && !pubKey.VerifyBytes(signBytes, sig.Signature) {
|
||||||
|
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = acc.SetSequence(acc.GetSequence() + 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdk.ErrInternal("failed to set account nonce").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc, res
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeSigGas(meter sdk.GasMeter, pubkey tmcrypto.PubKey) {
|
||||||
|
switch pubkey.(type) {
|
||||||
|
case crypto.PubKeySecp256k1:
|
||||||
|
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: secp256k1")
|
||||||
|
default:
|
||||||
|
panic("Unrecognized signature type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,34 +165,133 @@ func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.Ant
|
|||||||
// Ethereum Ante Handler
|
// Ethereum Ante Handler
|
||||||
|
|
||||||
// ethAnteHandler defines an internal ante handler for an Ethereum transaction
|
// ethAnteHandler defines an internal ante handler for an Ethereum transaction
|
||||||
// ethTx that implements the sdk.Msg interface. The Ethereum transaction is a
|
// ethTxMsg. During CheckTx, the transaction is passed through a series of
|
||||||
// single message inside a auth.StdTx.
|
// pre-message execution validation checks such as signature and account
|
||||||
//
|
// verification in addition to minimum fees being checked. Otherwise, during
|
||||||
// For now we simply pass the transaction on as the EVM shares common business
|
// DeliverTx, the transaction is simply passed to the EVM which will also
|
||||||
// logic of an ante handler. Anything not handled by the EVM that should be
|
// perform the same series of checks. The distinction is made in CheckTx to
|
||||||
// prior to transaction processing, should be done here.
|
// prevent spam and DoS attacks.
|
||||||
func ethAnteHandler(
|
func ethAnteHandler(
|
||||||
ctx sdk.Context, ethTx *evmtypes.MsgEthereumTx, ak auth.AccountKeeper,
|
ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg, ak auth.AccountKeeper,
|
||||||
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
|
||||||
|
if ctx.IsCheckTx() {
|
||||||
|
// Only perform pre-message (Ethereum transaction) execution validation
|
||||||
|
// during CheckTx. Otherwise, during DeliverTx the EVM will handle them.
|
||||||
|
if res := validateEthTxCheckTx(ctx, ak, ethTxMsg); !res.IsOK() {
|
||||||
|
return newCtx, res, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ctx, sdk.Result{}, false
|
return ctx, sdk.Result{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
func validateEthTxCheckTx(
|
||||||
// Auxiliary
|
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg,
|
||||||
|
) sdk.Result {
|
||||||
|
|
||||||
// isEthereumTx returns a boolean if a given standard SDK transaction contains
|
// parse the chainID from a string to a base-10 integer
|
||||||
// an Ethereum transaction. If so, the transaction is also returned. A standard
|
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
|
||||||
// SDK transaction contains an Ethereum transaction if it only has a single
|
if !ok {
|
||||||
// message and that embedded message if of type MsgEthereumTx.
|
return types.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", ctx.ChainID())).Result()
|
||||||
func isEthereumTx(tx auth.StdTx) (*evmtypes.MsgEthereumTx, bool) {
|
|
||||||
msgs := tx.GetMsgs()
|
|
||||||
if len(msgs) == 1 {
|
|
||||||
ethTx, ok := msgs[0].(*evmtypes.MsgEthereumTx)
|
|
||||||
if ok {
|
|
||||||
return ethTx, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false
|
// Validate sufficient fees have been provided that meet a minimum threshold
|
||||||
|
// defined by the proposer (for mempool purposes during CheckTx).
|
||||||
|
if res := ensureSufficientMempoolFees(ctx, ethTxMsg); !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate enough intrinsic gas
|
||||||
|
if res := validateIntrinsicGas(ethTxMsg); !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate sender/signature
|
||||||
|
signer, err := ethTxMsg.VerifySig(chainID)
|
||||||
|
if err != nil {
|
||||||
|
return sdk.ErrUnauthorized("signature verification failed").Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate account (nonce and balance checks)
|
||||||
|
if res := validateAccount(ctx, ak, ethTxMsg, signer); !res.IsOK() {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdk.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateIntrinsicGas validates that the Ethereum tx message has enough to
|
||||||
|
// cover intrinsic gas. Intrinsic gas for a transaction is the amount of gas
|
||||||
|
// that the transaction uses before the transaction is executed. The gas is a
|
||||||
|
// constant value of 21000 plus any cost inccured by additional bytes of data
|
||||||
|
// supplied with the transaction.
|
||||||
|
func validateIntrinsicGas(ethTxMsg *evmtypes.EthereumTxMsg) sdk.Result {
|
||||||
|
gas, err := ethcore.IntrinsicGas(ethTxMsg.Data.Payload, ethTxMsg.To() == nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return sdk.ErrInternal(fmt.Sprintf("failed to compute intrinsic gas cost: %s", err)).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ethTxMsg.Data.GasLimit < gas {
|
||||||
|
return sdk.ErrInternal(
|
||||||
|
fmt.Sprintf("intrinsic gas too low; %d < %d", ethTxMsg.Data.GasLimit, gas),
|
||||||
|
).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdk.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateAccount validates the account nonce and that the account has enough
|
||||||
|
// funds to cover the tx cost.
|
||||||
|
func validateAccount(
|
||||||
|
ctx sdk.Context, ak auth.AccountKeeper, ethTxMsg *evmtypes.EthereumTxMsg, signer ethcmn.Address,
|
||||||
|
) sdk.Result {
|
||||||
|
|
||||||
|
acc := ak.GetAccount(ctx, sdk.AccAddress(signer.Bytes()))
|
||||||
|
|
||||||
|
// on InitChain make sure account number == 0
|
||||||
|
if ctx.BlockHeight() == 0 && acc.GetAccountNumber() != 0 {
|
||||||
|
return sdk.ErrInternal(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"invalid account number for height zero; got %d, expected 0", acc.GetAccountNumber(),
|
||||||
|
)).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the transaction nonce is valid (equivalent to the sender account’s
|
||||||
|
// current nonce).
|
||||||
|
seq := acc.GetSequence()
|
||||||
|
if ethTxMsg.Data.AccountNonce != seq {
|
||||||
|
return sdk.ErrInvalidSequence(
|
||||||
|
fmt.Sprintf("nonce too low; got %d, expected %d", ethTxMsg.Data.AccountNonce, seq)).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate sender has enough funds
|
||||||
|
balance := acc.GetCoins().AmountOf(types.DenomDefault)
|
||||||
|
if balance.BigInt().Cmp(ethTxMsg.Cost()) < 0 {
|
||||||
|
return sdk.ErrInsufficientFunds(
|
||||||
|
fmt.Sprintf("insufficient funds: %s < %s", balance, ethTxMsg.Cost()),
|
||||||
|
).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdk.Result{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureSufficientMempoolFees verifies that enough fees have been provided by the
|
||||||
|
// Ethereum transaction that meet the minimum threshold set by the block
|
||||||
|
// proposer.
|
||||||
|
//
|
||||||
|
// NOTE: This should only be ran during a CheckTx mode.
|
||||||
|
func ensureSufficientMempoolFees(ctx sdk.Context, ethTxMsg *evmtypes.EthereumTxMsg) sdk.Result {
|
||||||
|
// fee = GP * GL
|
||||||
|
fee := sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, ethTxMsg.Fee().Int64())}
|
||||||
|
|
||||||
|
// it is assumed that the minimum fees will only include the single valid denom
|
||||||
|
if !ctx.MinimumFees().IsZero() && !fee.IsAllGTE(ctx.MinimumFees()) {
|
||||||
|
// reject the transaction that does not meet the minimum fee
|
||||||
|
return sdk.ErrInsufficientFee(
|
||||||
|
fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinimumFees()),
|
||||||
|
).Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdk.Result{}
|
||||||
}
|
}
|
||||||
|
263
app/ante_test.go
263
app/ante_test.go
@ -5,12 +5,14 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
"github.com/cosmos/ethermint/types"
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func requireValidTx(
|
func requireValidTx(
|
||||||
@ -45,64 +47,80 @@ func requireInvalidTx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidEthTx(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
acc1.SetCoins(newTestCoins())
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc1)
|
||||||
|
|
||||||
|
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
|
||||||
|
acc2.SetCoins(newTestCoins())
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc2)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
requireValidTx(t, input.anteHandler, input.ctx, tx, false)
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidTx(t *testing.T) {
|
func TestValidTx(t *testing.T) {
|
||||||
setup := newTestSetup()
|
input := newTestSetup()
|
||||||
setup.ctx = setup.ctx.WithBlockHeight(1)
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
addr1, priv1 := newTestAddrKey()
|
addr1, priv1 := newTestAddrKey()
|
||||||
addr2, priv2 := newTestAddrKey()
|
addr2, priv2 := newTestAddrKey()
|
||||||
|
|
||||||
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
acc1.SetCoins(newTestCoins())
|
acc1.SetCoins(newTestCoins())
|
||||||
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
input.accKeeper.SetAccount(input.ctx, acc1)
|
||||||
|
|
||||||
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
|
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
|
||||||
acc2.SetCoins(newTestCoins())
|
acc2.SetCoins(newTestCoins())
|
||||||
setup.accKeeper.SetAccount(setup.ctx, acc2)
|
input.accKeeper.SetAccount(input.ctx, acc2)
|
||||||
|
|
||||||
// require a valid SDK tx to pass
|
// require a valid SDK tx to pass
|
||||||
fee := newTestStdFee()
|
fee := newTestStdFee()
|
||||||
msg1 := newTestMsg(addr1, addr2)
|
msg1 := newTestMsg(addr1, addr2)
|
||||||
msgs := []sdk.Msg{msg1}
|
msgs := []sdk.Msg{msg1}
|
||||||
|
|
||||||
privKeys := []crypto.PrivKey{priv1, priv2}
|
privKeys := []tmcrypto.PrivKey{priv1, priv2}
|
||||||
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
|
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
|
requireValidTx(t, input.anteHandler, input.ctx, tx, false)
|
||||||
|
|
||||||
// require accounts to update
|
// require accounts to update
|
||||||
acc1 = setup.accKeeper.GetAccount(setup.ctx, addr1)
|
acc1 = input.accKeeper.GetAccount(input.ctx, addr1)
|
||||||
acc2 = setup.accKeeper.GetAccount(setup.ctx, addr2)
|
acc2 = input.accKeeper.GetAccount(input.ctx, addr2)
|
||||||
require.Equal(t, accSeqs[0]+1, acc1.GetSequence())
|
require.Equal(t, accSeqs[0]+1, acc1.GetSequence())
|
||||||
require.Equal(t, accSeqs[1]+1, acc2.GetSequence())
|
require.Equal(t, accSeqs[1]+1, acc2.GetSequence())
|
||||||
|
|
||||||
// require a valid Ethereum tx to pass
|
|
||||||
to := ethcmn.BytesToAddress(addr2.Bytes())
|
|
||||||
amt := big.NewInt(32)
|
|
||||||
gas := big.NewInt(20)
|
|
||||||
ethMsg := evmtypes.NewMsgEthereumTx(0, to, amt, 20000, gas, []byte("test"))
|
|
||||||
|
|
||||||
tx = newTestEthTx(setup.ctx, ethMsg, priv1)
|
|
||||||
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSDKInvalidSigs(t *testing.T) {
|
func TestSDKInvalidSigs(t *testing.T) {
|
||||||
setup := newTestSetup()
|
input := newTestSetup()
|
||||||
setup.ctx = setup.ctx.WithBlockHeight(1)
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
addr1, priv1 := newTestAddrKey()
|
addr1, priv1 := newTestAddrKey()
|
||||||
addr2, priv2 := newTestAddrKey()
|
addr2, priv2 := newTestAddrKey()
|
||||||
addr3, priv3 := newTestAddrKey()
|
addr3, priv3 := newTestAddrKey()
|
||||||
|
|
||||||
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
acc1.SetCoins(newTestCoins())
|
acc1.SetCoins(newTestCoins())
|
||||||
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
input.accKeeper.SetAccount(input.ctx, acc1)
|
||||||
|
|
||||||
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
|
acc2 := input.accKeeper.NewAccountWithAddress(input.ctx, addr2)
|
||||||
acc2.SetCoins(newTestCoins())
|
acc2.SetCoins(newTestCoins())
|
||||||
setup.accKeeper.SetAccount(setup.ctx, acc2)
|
input.accKeeper.SetAccount(input.ctx, acc2)
|
||||||
|
|
||||||
fee := newTestStdFee()
|
fee := newTestStdFee()
|
||||||
msg1 := newTestMsg(addr1, addr2)
|
msg1 := newTestMsg(addr1, addr2)
|
||||||
@ -110,66 +128,185 @@ func TestSDKInvalidSigs(t *testing.T) {
|
|||||||
// require validation failure with no signers
|
// require validation failure with no signers
|
||||||
msgs := []sdk.Msg{msg1}
|
msgs := []sdk.Msg{msg1}
|
||||||
|
|
||||||
privKeys := []crypto.PrivKey{}
|
privKeys := []tmcrypto.PrivKey{}
|
||||||
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
|
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
|
||||||
// require validation failure with invalid number of signers
|
// require validation failure with invalid number of signers
|
||||||
msgs = []sdk.Msg{msg1}
|
msgs = []sdk.Msg{msg1}
|
||||||
|
|
||||||
privKeys = []crypto.PrivKey{priv1}
|
privKeys = []tmcrypto.PrivKey{priv1}
|
||||||
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence()}
|
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
|
||||||
// require validation failure with an invalid signer
|
// require validation failure with an invalid signer
|
||||||
msg2 := newTestMsg(addr1, addr3)
|
msg2 := newTestMsg(addr1, addr3)
|
||||||
msgs = []sdk.Msg{msg1, msg2}
|
msgs = []sdk.Msg{msg1, msg2}
|
||||||
|
|
||||||
privKeys = []crypto.PrivKey{priv1, priv2, priv3}
|
privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
|
||||||
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
|
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
|
||||||
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence(), 0}
|
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
|
||||||
|
|
||||||
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnknownAddress)
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnknownAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSDKInvalidAcc(t *testing.T) {
|
func TestSDKInvalidAcc(t *testing.T) {
|
||||||
setup := newTestSetup()
|
input := newTestSetup()
|
||||||
setup.ctx = setup.ctx.WithBlockHeight(1)
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
addr1, priv1 := newTestAddrKey()
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
|
||||||
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
acc1 := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
acc1.SetCoins(newTestCoins())
|
acc1.SetCoins(newTestCoins())
|
||||||
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
input.accKeeper.SetAccount(input.ctx, acc1)
|
||||||
|
|
||||||
fee := newTestStdFee()
|
fee := newTestStdFee()
|
||||||
msg1 := newTestMsg(addr1)
|
msg1 := newTestMsg(addr1)
|
||||||
msgs := []sdk.Msg{msg1}
|
msgs := []sdk.Msg{msg1}
|
||||||
privKeys := []crypto.PrivKey{priv1}
|
privKeys := []tmcrypto.PrivKey{priv1}
|
||||||
|
|
||||||
// require validation failure with invalid account number
|
// require validation failure with invalid account number
|
||||||
accNums := []int64{1}
|
accNums := []uint64{1}
|
||||||
accSeqs := []int64{acc1.GetSequence()}
|
accSeqs := []uint64{acc1.GetSequence()}
|
||||||
|
|
||||||
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx := newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
|
||||||
// require validation failure with invalid sequence (nonce)
|
// require validation failure with invalid sequence (nonce)
|
||||||
accNums = []int64{acc1.GetAccountNumber()}
|
accNums = []uint64{acc1.GetAccountNumber()}
|
||||||
accSeqs = []int64{1}
|
accSeqs = []uint64{1}
|
||||||
|
|
||||||
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
tx = newTestSDKTx(input.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSDKGasConsumption(t *testing.T) {
|
func TestEthInvalidSig(t *testing.T) {
|
||||||
// TODO: Test gas consumption and OOG once ante handler implementation stabilizes
|
input := newTestSetup()
|
||||||
t.SkipNow()
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
_, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
ctx := input.ctx.WithChainID("4")
|
||||||
|
requireInvalidTx(t, input.anteHandler, ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEthInvalidNonce(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
acc.SetCoins(newTestCoins())
|
||||||
|
acc.SetSequence(10)
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInvalidSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEthInsufficientBalance(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFunds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEthInvalidIntrinsicGas(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
acc.SetCoins(newTestCoins())
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
gasLimit := uint64(1000)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, gasLimit, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInternal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEthInvalidMempoolFees(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
input.ctx = input.ctx.WithMinimumFees(sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, 500000)})
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
acc.SetCoins(newTestCoins())
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
requireInvalidTx(t, input.anteHandler, input.ctx, tx, false, sdk.CodeInsufficientFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEthInvalidChainID(t *testing.T) {
|
||||||
|
input := newTestSetup()
|
||||||
|
input.ctx = input.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, _ := newTestAddrKey()
|
||||||
|
|
||||||
|
acc := input.accKeeper.NewAccountWithAddress(input.ctx, addr1)
|
||||||
|
acc.SetCoins(newTestCoins())
|
||||||
|
input.accKeeper.SetAccount(input.ctx, acc)
|
||||||
|
|
||||||
|
// require a valid Ethereum tx to pass
|
||||||
|
to := ethcmn.BytesToAddress(addr2.Bytes())
|
||||||
|
amt := big.NewInt(32)
|
||||||
|
gas := big.NewInt(20)
|
||||||
|
ethMsg := evmtypes.NewEthereumTxMsg(0, to, amt, 22000, gas, []byte("test"))
|
||||||
|
|
||||||
|
tx := newTestEthTx(input.ctx, ethMsg, priv1)
|
||||||
|
ctx := input.ctx.WithChainID("bad-chain-id")
|
||||||
|
requireInvalidTx(t, input.anteHandler, ctx, tx, false, types.CodeInvalidChainID)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||||
"github.com/cosmos/cosmos-sdk/x/stake"
|
"github.com/cosmos/cosmos-sdk/x/stake"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto"
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -74,7 +75,7 @@ type (
|
|||||||
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
|
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
|
||||||
cdc := CreateCodec()
|
cdc := CreateCodec()
|
||||||
|
|
||||||
baseApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOpts...)
|
baseApp := bam.NewBaseApp(appName, logger, db, evmtypes.TxDecoder(cdc), baseAppOpts...)
|
||||||
app := &EthermintApp{
|
app := &EthermintApp{
|
||||||
BaseApp: baseApp,
|
BaseApp: baseApp,
|
||||||
cdc: cdc,
|
cdc: cdc,
|
||||||
@ -89,8 +90,8 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
|
|||||||
tParamsKey: storeKeyTransParams,
|
tParamsKey: storeKeyTransParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
|
|
||||||
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey)
|
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey)
|
||||||
|
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
|
||||||
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
|
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
|
||||||
|
|
||||||
// register message handlers
|
// register message handlers
|
||||||
@ -106,7 +107,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.Ba
|
|||||||
app.SetEndBlocker(app.EndBlocker)
|
app.SetEndBlocker(app.EndBlocker)
|
||||||
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
|
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
|
||||||
|
|
||||||
app.MountStoresIAVL(
|
app.MountStores(
|
||||||
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
|
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
|
||||||
app.govKey, app.feeCollKey, app.paramsKey, app.storageKey,
|
app.govKey, app.feeCollKey, app.paramsKey, app.storageKey,
|
||||||
)
|
)
|
||||||
@ -165,6 +166,7 @@ func CreateCodec() *codec.Codec {
|
|||||||
// TODO: Add remaining codec registrations:
|
// TODO: Add remaining codec registrations:
|
||||||
// bank, staking, distribution, slashing, and gov
|
// bank, staking, distribution, slashing, and gov
|
||||||
|
|
||||||
|
crypto.RegisterCodec(cdc)
|
||||||
evmtypes.RegisterCodec(cdc)
|
evmtypes.RegisterCodec(cdc)
|
||||||
auth.RegisterCodec(cdc)
|
auth.RegisterCodec(cdc)
|
||||||
sdk.RegisterCodec(cdc)
|
sdk.RegisterCodec(cdc)
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
|
|
||||||
db := dbm.NewMemDB()
|
|
||||||
capKey := sdk.NewKVStoreKey("capkey")
|
|
||||||
capKey2 := sdk.NewKVStoreKey("capkey2")
|
|
||||||
ms := store.NewCommitMultiStore(db)
|
|
||||||
|
|
||||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
|
||||||
ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db)
|
|
||||||
ms.LoadLatestVersion()
|
|
||||||
|
|
||||||
return ms, capKey, capKey2
|
|
||||||
}
|
|
@ -6,46 +6,61 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
|
||||||
"github.com/cosmos/ethermint/crypto"
|
"github.com/cosmos/ethermint/crypto"
|
||||||
|
"github.com/cosmos/ethermint/types"
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testDenom = "testcoin"
|
|
||||||
|
|
||||||
type testSetup struct {
|
type testSetup struct {
|
||||||
ctx sdk.Context
|
ctx sdk.Context
|
||||||
|
cdc *codec.Codec
|
||||||
accKeeper auth.AccountKeeper
|
accKeeper auth.AccountKeeper
|
||||||
feeKeeper auth.FeeCollectionKeeper
|
feeKeeper auth.FeeCollectionKeeper
|
||||||
anteHandler sdk.AnteHandler
|
anteHandler sdk.AnteHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestSetup() testSetup {
|
func newTestSetup() testSetup {
|
||||||
cdc := codec.New()
|
db := dbm.NewMemDB()
|
||||||
ms, capKey, capKey2 := setupMultiStore()
|
authCapKey := sdk.NewKVStoreKey("authCapKey")
|
||||||
|
feeCapKey := sdk.NewKVStoreKey("feeCapKey")
|
||||||
|
keyParams := sdk.NewKVStoreKey("params")
|
||||||
|
tkeyParams := sdk.NewTransientStoreKey("transient_params")
|
||||||
|
|
||||||
auth.RegisterBaseAccount(cdc)
|
ms := store.NewCommitMultiStore(db)
|
||||||
|
ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(feeCapKey, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.LoadLatestVersion()
|
||||||
|
|
||||||
accKeeper := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
|
cdc := CreateCodec()
|
||||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, capKey2)
|
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
|
||||||
|
|
||||||
|
accKeeper := auth.NewAccountKeeper(cdc, authCapKey, auth.ProtoBaseAccount)
|
||||||
|
feeKeeper := auth.NewFeeCollectionKeeper(cdc, feeCapKey)
|
||||||
anteHandler := NewAnteHandler(accKeeper, feeKeeper)
|
anteHandler := NewAnteHandler(accKeeper, feeKeeper)
|
||||||
|
|
||||||
ctx := sdk.NewContext(
|
ctx := sdk.NewContext(
|
||||||
ms,
|
ms,
|
||||||
abci.Header{ChainID: "3", Time: time.Now().UTC()},
|
abci.Header{ChainID: "3", Time: time.Now().UTC()},
|
||||||
false,
|
true,
|
||||||
log.NewNopLogger(),
|
log.NewNopLogger(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return testSetup{
|
return testSetup{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
cdc: cdc,
|
||||||
accKeeper: accKeeper,
|
accKeeper: accKeeper,
|
||||||
feeKeeper: feeKeeper,
|
feeKeeper: feeKeeper,
|
||||||
anteHandler: anteHandler,
|
anteHandler: anteHandler,
|
||||||
@ -57,55 +72,55 @@ func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTestCoins() sdk.Coins {
|
func newTestCoins() sdk.Coins {
|
||||||
return sdk.Coins{sdk.NewInt64Coin(testDenom, 10000000)}
|
return sdk.Coins{sdk.NewInt64Coin(types.DenomDefault, 500000000)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestStdFee() auth.StdFee {
|
func newTestStdFee() auth.StdFee {
|
||||||
return auth.NewStdFee(5000, sdk.NewInt64Coin(testDenom, 150))
|
return auth.NewStdFee(220000, sdk.NewInt64Coin(types.DenomDefault, 150))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateAddress generates an Ethereum address.
|
// GenerateAddress generates an Ethereum address.
|
||||||
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
|
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
|
||||||
priv := tmsecp256k1.GenPrivKey()
|
privkey, _ := crypto.GenerateKey()
|
||||||
addr := sdk.AccAddress(priv.PubKey().Address())
|
addr := ethcrypto.PubkeyToAddress(privkey.PublicKey)
|
||||||
return addr, priv
|
|
||||||
|
return sdk.AccAddress(addr.Bytes()), privkey
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestSDKTx(
|
func newTestSDKTx(
|
||||||
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
|
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
|
||||||
accNums []int64, seqs []int64, fee auth.StdFee,
|
accNums []uint64, seqs []uint64, fee auth.StdFee,
|
||||||
) sdk.Tx {
|
) sdk.Tx {
|
||||||
|
|
||||||
sigs := make([]auth.StdSignature, len(privs))
|
sigs := make([]auth.StdSignature, len(privs))
|
||||||
for i, priv := range privs {
|
for i, priv := range privs {
|
||||||
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
|
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
|
||||||
|
|
||||||
sig, err := priv.Sign(signBytes)
|
sig, err := priv.Sign(signBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sigs[i] = auth.StdSignature{
|
sigs[i] = auth.StdSignature{
|
||||||
PubKey: priv.PubKey(),
|
PubKey: priv.PubKey(),
|
||||||
Signature: sig,
|
Signature: sig,
|
||||||
AccountNumber: accNums[i],
|
|
||||||
Sequence: seqs[i],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.NewStdTx(msgs, fee, sigs, "")
|
return auth.NewStdTx(msgs, fee, sigs, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx {
|
func newTestEthTx(ctx sdk.Context, msg *evmtypes.EthereumTxMsg, priv tmcrypto.PrivKey) sdk.Tx {
|
||||||
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
|
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
|
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
privKey, err := crypto.PrivKeyToSecp256k1(priv)
|
privkey, ok := priv.(crypto.PrivKeySecp256k1)
|
||||||
if err != nil {
|
if !ok {
|
||||||
panic(fmt.Sprintf("failed to convert private key: %s", err))
|
panic(fmt.Sprintf("invalid private key type: %T", priv))
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Sign(chainID, privKey)
|
msg.Sign(chainID, privkey.ToECDSA())
|
||||||
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
|
return msg
|
||||||
}
|
}
|
||||||
|
16
crypto/codec.go
Normal file
16
crypto/codec.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import "github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
|
var cryptoCodec = codec.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterCodec(cryptoCodec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCodec registers all the necessary types with amino for the given
|
||||||
|
// codec.
|
||||||
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
|
cdc.RegisterConcrete(PubKeySecp256k1{}, "crypto/PubKeySecp256k1", nil)
|
||||||
|
cdc.RegisterConcrete(PrivKeySecp256k1{}, "crypto/PrivKeySecp256k1", nil)
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
secp256k1 "github.com/tendermint/btcd/btcec"
|
|
||||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
|
||||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivKeyToSecp256k1 accepts a Tendermint private key and attempts to convert
|
|
||||||
// it to a SECP256k1 ecdsa.PrivateKey.
|
|
||||||
func PrivKeyToSecp256k1(priv tmcrypto.PrivKey) (*ecdsa.PrivateKey, error) {
|
|
||||||
secp256k1Key, ok := priv.(tmsecp256k1.PrivKeySecp256k1)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid private key type: %T", priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
ecdsaPrivKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), secp256k1Key[:])
|
|
||||||
return ecdsaPrivKey.ToECDSA(), nil
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
secp256k1 "github.com/tendermint/btcd/btcec"
|
|
||||||
tmed25519 "github.com/tendermint/tendermint/crypto/ed25519"
|
|
||||||
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrivKeyToSecp256k1(t *testing.T) {
|
|
||||||
// require valid SECP256k1 key to convert
|
|
||||||
secp256k1PrivKey := tmsecp256k1.GenPrivKey()
|
|
||||||
convertedPriv, err := PrivKeyToSecp256k1(secp256k1PrivKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, secp256k1PrivKey[:], (*secp256k1.PrivateKey)(convertedPriv).Serialize())
|
|
||||||
|
|
||||||
// require invalid ED25519 key not to convert
|
|
||||||
ed25519PrivKey := tmed25519.GenPrivKey()
|
|
||||||
convertedPriv, err = PrivKeyToSecp256k1(ed25519PrivKey)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, convertedPriv)
|
|
||||||
}
|
|
105
crypto/secp256k1.go
Normal file
105
crypto/secp256k1.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
ethsecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
|
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// secp256k1 Private Key
|
||||||
|
|
||||||
|
var _ tmcrypto.PrivKey = PrivKeySecp256k1{}
|
||||||
|
|
||||||
|
// PrivKeySecp256k1 defines a type alias for an ecdsa.PrivateKey that implements
|
||||||
|
// Tendermint's PrivateKey interface.
|
||||||
|
type PrivKeySecp256k1 ecdsa.PrivateKey
|
||||||
|
|
||||||
|
// GenerateKey generates a new random private key. It returns an error upon
|
||||||
|
// failure.
|
||||||
|
func GenerateKey() (PrivKeySecp256k1, error) {
|
||||||
|
priv, err := ethcrypto.GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return PrivKeySecp256k1{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrivKeySecp256k1(*priv), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubKey returns the ECDSA private key's public key.
|
||||||
|
func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey {
|
||||||
|
return PubKeySecp256k1{privkey.PublicKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the raw ECDSA private key bytes.
|
||||||
|
func (privkey PrivKeySecp256k1) Bytes() []byte {
|
||||||
|
return ethcrypto.FromECDSA(privkey.ToECDSA())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign creates a recoverable ECDSA signature on the secp256k1 curve over the
|
||||||
|
// Keccak256 hash of the provided message. The produced signature is 65 bytes
|
||||||
|
// where the last byte contains the recovery ID.
|
||||||
|
func (privkey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) {
|
||||||
|
return ethcrypto.Sign(ethcrypto.Keccak256Hash(msg).Bytes(), privkey.ToECDSA())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if two ECDSA private keys are equal and false otherwise.
|
||||||
|
func (privkey PrivKeySecp256k1) Equals(other tmcrypto.PrivKey) bool {
|
||||||
|
if other, ok := other.(PrivKeySecp256k1); ok {
|
||||||
|
return bytes.Equal(privkey.Bytes(), other.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type.
|
||||||
|
func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey {
|
||||||
|
return (*ecdsa.PrivateKey)(&privkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// secp256k1 Public Key
|
||||||
|
|
||||||
|
var _ tmcrypto.PubKey = (*PubKeySecp256k1)(nil)
|
||||||
|
|
||||||
|
// PubKeySecp256k1 defines a type alias for an ecdsa.PublicKey that implements
|
||||||
|
// Tendermint's PubKey interface.
|
||||||
|
type PubKeySecp256k1 struct {
|
||||||
|
pubkey ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the address of the ECDSA public key.
|
||||||
|
func (key PubKeySecp256k1) Address() tmcrypto.Address {
|
||||||
|
return tmcrypto.Address(ethcrypto.PubkeyToAddress(key.pubkey).Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the raw bytes of the ECDSA public key.
|
||||||
|
func (key PubKeySecp256k1) Bytes() []byte {
|
||||||
|
return ethcrypto.FromECDSAPub(&key.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyBytes verifies that the ECDSA public key created a given signature over
|
||||||
|
// the provided message. It will calculate the Keccak256 hash of the message
|
||||||
|
// prior to verification.
|
||||||
|
func (key PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool {
|
||||||
|
if len(sig) == 65 {
|
||||||
|
// remove recovery ID if contained in the signature
|
||||||
|
sig = sig[:len(sig)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// the signature needs to be in [R || S] format when provided to VerifySignature
|
||||||
|
return ethsecp256k1.VerifySignature(key.Bytes(), ethcrypto.Keccak256Hash(msg).Bytes(), sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if two ECDSA public keys are equal and false otherwise.
|
||||||
|
func (key PubKeySecp256k1) Equals(other tmcrypto.PubKey) bool {
|
||||||
|
if other, ok := other.(PubKeySecp256k1); ok {
|
||||||
|
return bytes.Equal(key.Bytes(), other.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
61
crypto/secp256k1_test.go
Normal file
61
crypto/secp256k1_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
ethsecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrivKeySecp256k1PrivKey(t *testing.T) {
|
||||||
|
// validate type and equality
|
||||||
|
privKey, err := GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, privKey.Equals(privKey))
|
||||||
|
require.Implements(t, (*tmcrypto.PrivKey)(nil), privKey)
|
||||||
|
|
||||||
|
// validate inequality
|
||||||
|
privKey2, err := GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, privKey.Equals(privKey2))
|
||||||
|
|
||||||
|
// validate Ethereum address equality
|
||||||
|
addr := privKey.PubKey().Address()
|
||||||
|
expectedAddr := ethcrypto.PubkeyToAddress(privKey.PublicKey)
|
||||||
|
require.Equal(t, expectedAddr.Bytes(), addr.Bytes())
|
||||||
|
|
||||||
|
// validate we can sign some bytes
|
||||||
|
msg := []byte("hello world")
|
||||||
|
sigHash := ethcrypto.Keccak256Hash(msg)
|
||||||
|
expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey.Bytes())
|
||||||
|
|
||||||
|
sig, err := privKey.Sign(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedSig, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivKeySecp256k1PubKey(t *testing.T) {
|
||||||
|
privKey, err := GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// validate type and equality
|
||||||
|
pubKey := privKey.PubKey().(PubKeySecp256k1)
|
||||||
|
require.Implements(t, (*tmcrypto.PubKey)(nil), pubKey)
|
||||||
|
|
||||||
|
// validate inequality
|
||||||
|
privKey2, err := GenerateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, pubKey.Equals(privKey2.PubKey()))
|
||||||
|
|
||||||
|
// validate signature
|
||||||
|
msg := []byte("hello world")
|
||||||
|
sig, err := privKey.Sign(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fmt.Println("SIG LENGTH:", len(sig))
|
||||||
|
res := pubKey.VerifyBytes(msg, sig)
|
||||||
|
require.True(t, res)
|
||||||
|
}
|
@ -46,6 +46,8 @@ var (
|
|||||||
miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
|
miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
|
||||||
genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
|
genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
|
||||||
|
|
||||||
|
paramsKey = sdk.NewKVStoreKey("params")
|
||||||
|
tParamsKey = sdk.NewTransientStoreKey("transient_params")
|
||||||
accKey = sdk.NewKVStoreKey("acc")
|
accKey = sdk.NewKVStoreKey("acc")
|
||||||
storageKey = sdk.NewKVStoreKey("storage")
|
storageKey = sdk.NewKVStoreKey("storage")
|
||||||
codeKey = sdk.NewKVStoreKey("code")
|
codeKey = sdk.NewKVStoreKey("code")
|
||||||
@ -140,11 +142,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
|
|||||||
ms.Write()
|
ms.Write()
|
||||||
|
|
||||||
// persist multi-store root state
|
// persist multi-store root state
|
||||||
commitID := cms.Commit()
|
cms.Commit()
|
||||||
require.Equal(
|
|
||||||
t, "29EF84DF8CC4648FD15341F15585A434279A9514445FC9F9E884D687185C1012",
|
|
||||||
fmt.Sprintf("%X", commitID.Hash),
|
|
||||||
)
|
|
||||||
|
|
||||||
// verify account mapper state
|
// verify account mapper state
|
||||||
genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes()))
|
genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes()))
|
||||||
@ -172,12 +170,7 @@ func TestImportBlocks(t *testing.T) {
|
|||||||
// create logger, codec and root multi-store
|
// create logger, codec and root multi-store
|
||||||
cdc := newTestCodec()
|
cdc := newTestCodec()
|
||||||
cms := store.NewCommitMultiStore(db)
|
cms := store.NewCommitMultiStore(db)
|
||||||
|
ak := auth.NewAccountKeeper(cdc, accKey, types.ProtoBaseAccount)
|
||||||
ak := auth.NewAccountKeeper(
|
|
||||||
cdc,
|
|
||||||
accKey,
|
|
||||||
types.ProtoBaseAccount,
|
|
||||||
)
|
|
||||||
|
|
||||||
// mount stores
|
// mount stores
|
||||||
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey}
|
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey}
|
||||||
|
@ -9,16 +9,16 @@ const (
|
|||||||
// DefaultCodespace reserves a Codespace for Ethermint.
|
// DefaultCodespace reserves a Codespace for Ethermint.
|
||||||
DefaultCodespace sdk.CodespaceType = "ethermint"
|
DefaultCodespace sdk.CodespaceType = "ethermint"
|
||||||
|
|
||||||
CodeInvalidValue sdk.CodeType = 1
|
CodeInvalidValue sdk.CodeType = 1
|
||||||
CodeInvalidAccountNumber sdk.CodeType = 2
|
CodeInvalidChainID sdk.CodeType = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToDefaultMsg(code sdk.CodeType) string {
|
func codeToDefaultMsg(code sdk.CodeType) string {
|
||||||
switch code {
|
switch code {
|
||||||
case CodeInvalidValue:
|
case CodeInvalidValue:
|
||||||
return "invalid value"
|
return "invalid value"
|
||||||
case CodeInvalidAccountNumber:
|
case CodeInvalidChainID:
|
||||||
return "invalid account number"
|
return "invalid chain ID"
|
||||||
default:
|
default:
|
||||||
return sdk.CodeToDefaultMsg(code)
|
return sdk.CodeToDefaultMsg(code)
|
||||||
}
|
}
|
||||||
@ -30,8 +30,8 @@ func ErrInvalidValue(msg string) sdk.Error {
|
|||||||
return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg)
|
return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrInvalidAccountNumber returns a standardized SDK error resulting from an
|
// ErrInvalidChainID returns a standardized SDK error resulting from an invalid
|
||||||
// invalid account number.
|
// chain ID.
|
||||||
func ErrInvalidAccountNumber(msg string) sdk.Error {
|
func ErrInvalidChainID(msg string) sdk.Error {
|
||||||
return sdk.NewError(DefaultCodespace, CodeInvalidAccountNumber, msg)
|
return sdk.NewError(DefaultCodespace, CodeInvalidChainID, msg)
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,5 @@ func init() {
|
|||||||
|
|
||||||
// Register concrete types and interfaces on the given codec.
|
// Register concrete types and interfaces on the given codec.
|
||||||
func RegisterCodec(cdc *codec.Codec) {
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil)
|
cdc.RegisterConcrete(&EthereumTxMsg{}, "ethermint/MsgEthereumTx", nil)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ type (
|
|||||||
|
|
||||||
nonceChange struct {
|
nonceChange struct {
|
||||||
account *ethcmn.Address
|
account *ethcmn.Address
|
||||||
prev int64
|
prev uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
storageChange struct {
|
storageChange struct {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/ethermint/types"
|
"github.com/cosmos/ethermint/types"
|
||||||
|
|
||||||
@ -17,17 +18,22 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ sdk.Msg = MsgEthereumTx{}
|
var (
|
||||||
|
_ sdk.Msg = EthereumTxMsg{}
|
||||||
|
_ sdk.Tx = EthereumTxMsg{}
|
||||||
|
)
|
||||||
|
|
||||||
|
var big8 = big.NewInt(8)
|
||||||
|
|
||||||
// message type and route constants
|
// message type and route constants
|
||||||
const (
|
const (
|
||||||
TypeMsgEthereumTx = "ethereum_tx"
|
TypeEthereumTxMsg = "ethereum_tx"
|
||||||
RouteMsgEthereumTx = "evm"
|
RouteEthereumTxMsg = "evm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
|
// EthereumTxMsg encapsulates an Ethereum transaction as an SDK message.
|
||||||
type (
|
type (
|
||||||
MsgEthereumTx struct {
|
EthereumTxMsg struct {
|
||||||
Data TxData
|
Data TxData
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
@ -63,28 +69,28 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
|
// NewEthereumTxMsg returns a reference to a new Ethereum transaction message.
|
||||||
func NewMsgEthereumTx(
|
func NewEthereumTxMsg(
|
||||||
nonce uint64, to ethcmn.Address, amount *big.Int,
|
nonce uint64, to ethcmn.Address, amount *big.Int,
|
||||||
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
) *MsgEthereumTx {
|
) *EthereumTxMsg {
|
||||||
|
|
||||||
return newMsgEthereumTx(nonce, &to, amount, gasLimit, gasPrice, payload)
|
return newEthereumTxMsg(nonce, &to, amount, gasLimit, gasPrice, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction
|
// NewEthereumTxMsgContract returns a reference to a new Ethereum transaction
|
||||||
// message designated for contract creation.
|
// message designated for contract creation.
|
||||||
func NewMsgEthereumTxContract(
|
func NewEthereumTxMsgContract(
|
||||||
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
) *MsgEthereumTx {
|
) *EthereumTxMsg {
|
||||||
|
|
||||||
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
|
return newEthereumTxMsg(nonce, nil, amount, gasLimit, gasPrice, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMsgEthereumTx(
|
func newEthereumTxMsg(
|
||||||
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||||
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
) *MsgEthereumTx {
|
) *EthereumTxMsg {
|
||||||
|
|
||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
payload = ethcmn.CopyBytes(payload)
|
payload = ethcmn.CopyBytes(payload)
|
||||||
@ -109,18 +115,18 @@ func newMsgEthereumTx(
|
|||||||
txData.Price.Set(gasPrice)
|
txData.Price.Set(gasPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MsgEthereumTx{Data: txData}
|
return &EthereumTxMsg{Data: txData}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route returns the route value of an MsgEthereumTx.
|
// Route returns the route value of an EthereumTxMsg.
|
||||||
func (msg MsgEthereumTx) Route() string { return RouteMsgEthereumTx }
|
func (msg EthereumTxMsg) Route() string { return RouteEthereumTxMsg }
|
||||||
|
|
||||||
// Type returns the type value of an MsgEthereumTx.
|
// Type returns the type value of an EthereumTxMsg.
|
||||||
func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
|
func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg }
|
||||||
|
|
||||||
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
||||||
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
||||||
func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
|
func (msg EthereumTxMsg) ValidateBasic() sdk.Error {
|
||||||
if msg.Data.Price.Sign() != 1 {
|
if msg.Data.Price.Sign() != 1 {
|
||||||
return types.ErrInvalidValue("price must be positive")
|
return types.ErrInvalidValue("price must be positive")
|
||||||
}
|
}
|
||||||
@ -132,12 +138,27 @@ func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To returns the recipient address of the transaction. It returns nil if the
|
||||||
|
// transaction is a contract creation.
|
||||||
|
func (msg EthereumTxMsg) To() *ethcmn.Address {
|
||||||
|
if msg.Data.Recipient == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg.Data.Recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMsgs returns a single EthereumTxMsg as an sdk.Msg.
|
||||||
|
func (msg EthereumTxMsg) GetMsgs() []sdk.Msg {
|
||||||
|
return []sdk.Msg{msg}
|
||||||
|
}
|
||||||
|
|
||||||
// GetSigners returns the expected signers for an Ethereum transaction message.
|
// GetSigners returns the expected signers for an Ethereum transaction message.
|
||||||
// For such a message, there should exist only a single 'signer'.
|
// For such a message, there should exist only a single 'signer'.
|
||||||
//
|
//
|
||||||
// NOTE: This method cannot be used as a chain ID is needed to recover the signer
|
// NOTE: This method cannot be used as a chain ID is needed to recover the signer
|
||||||
// from the signature. Use 'VerifySig' instead.
|
// from the signature. Use 'VerifySig' instead.
|
||||||
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
|
func (msg EthereumTxMsg) GetSigners() []sdk.AccAddress {
|
||||||
panic("must use 'VerifySig' with a chain ID to get the signer")
|
panic("must use 'VerifySig' with a chain ID to get the signer")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,13 +167,13 @@ func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
|
|||||||
//
|
//
|
||||||
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes
|
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes
|
||||||
// to sign over. Use 'RLPSignBytes' instead.
|
// to sign over. Use 'RLPSignBytes' instead.
|
||||||
func (msg MsgEthereumTx) GetSignBytes() []byte {
|
func (msg EthereumTxMsg) GetSignBytes() []byte {
|
||||||
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
|
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
|
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
|
||||||
// given chainID used for signing.
|
// given chainID used for signing.
|
||||||
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
|
func (msg EthereumTxMsg) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
|
||||||
return rlpHash([]interface{}{
|
return rlpHash([]interface{}{
|
||||||
msg.Data.AccountNonce,
|
msg.Data.AccountNonce,
|
||||||
msg.Data.Price,
|
msg.Data.Price,
|
||||||
@ -165,12 +186,12 @@ func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EncodeRLP implements the rlp.Encoder interface.
|
// EncodeRLP implements the rlp.Encoder interface.
|
||||||
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
|
func (msg *EthereumTxMsg) EncodeRLP(w io.Writer) error {
|
||||||
return rlp.Encode(w, &msg.Data)
|
return rlp.Encode(w, &msg.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeRLP implements the rlp.Decoder interface.
|
// DecodeRLP implements the rlp.Decoder interface.
|
||||||
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
|
func (msg *EthereumTxMsg) DecodeRLP(s *rlp.Stream) error {
|
||||||
_, size, _ := s.Kind()
|
_, size, _ := s.Kind()
|
||||||
|
|
||||||
err := s.Decode(&msg.Data)
|
err := s.Decode(&msg.Data)
|
||||||
@ -182,7 +203,7 @@ func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hash hashes the RLP encoding of a transaction.
|
// Hash hashes the RLP encoding of a transaction.
|
||||||
func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
|
func (msg *EthereumTxMsg) Hash() ethcmn.Hash {
|
||||||
if hash := msg.hash.Load(); hash != nil {
|
if hash := msg.hash.Load(); hash != nil {
|
||||||
return hash.(ethcmn.Hash)
|
return hash.(ethcmn.Hash)
|
||||||
}
|
}
|
||||||
@ -197,7 +218,7 @@ func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
|
|||||||
// takes a private key and chainID to sign an Ethereum transaction according to
|
// takes a private key and chainID to sign an Ethereum transaction according to
|
||||||
// EIP155 standard. It mutates the transaction as it populates the V, R, S
|
// EIP155 standard. It mutates the transaction as it populates the V, R, S
|
||||||
// fields of the Transaction's Signature.
|
// fields of the Transaction's Signature.
|
||||||
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
func (msg *EthereumTxMsg) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
||||||
txHash := msg.RLPSignBytes(chainID)
|
txHash := msg.RLPSignBytes(chainID)
|
||||||
|
|
||||||
sig, err := ethcrypto.Sign(txHash[:], priv)
|
sig, err := ethcrypto.Sign(txHash[:], priv)
|
||||||
@ -230,7 +251,7 @@ func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
|||||||
|
|
||||||
// VerifySig attempts to verify a Transaction's signature for a given chainID.
|
// VerifySig attempts to verify a Transaction's signature for a given chainID.
|
||||||
// A derived address is returned upon success or an error if recovery fails.
|
// A derived address is returned upon success or an error if recovery fails.
|
||||||
func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
func (msg *EthereumTxMsg) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
||||||
signer := ethtypes.NewEIP155Signer(chainID)
|
signer := ethtypes.NewEIP155Signer(chainID)
|
||||||
|
|
||||||
if sc := msg.from.Load(); sc != nil {
|
if sc := msg.from.Load(); sc != nil {
|
||||||
@ -244,43 +265,92 @@ func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
|||||||
|
|
||||||
// do not allow recovery for transactions with an unprotected chainID
|
// do not allow recovery for transactions with an unprotected chainID
|
||||||
if chainID.Sign() == 0 {
|
if chainID.Sign() == 0 {
|
||||||
return ethcmn.Address{}, errors.New("invalid chainID")
|
return ethcmn.Address{}, errors.New("chainID cannot be zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
txHash := msg.RLPSignBytes(chainID)
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||||
sig := recoverEthSig(msg.Data.R, msg.Data.S, msg.Data.V, chainID)
|
V := new(big.Int).Sub(msg.Data.V, chainIDMul)
|
||||||
|
V.Sub(V, big8)
|
||||||
|
|
||||||
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
|
sigHash := msg.RLPSignBytes(chainID)
|
||||||
|
sender, err := recoverEthSig(msg.Data.R, msg.Data.S, V, sigHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ethcmn.Address{}, err
|
return ethcmn.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr ethcmn.Address
|
msg.from.Store(sigCache{signer: signer, from: sender})
|
||||||
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
return sender, nil
|
||||||
|
|
||||||
msg.from.Store(sigCache{signer: signer, from: addr})
|
|
||||||
return addr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recoverEthSig recovers a signature according to the Ethereum specification.
|
// Cost returns amount + gasprice * gaslimit.
|
||||||
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
|
func (msg EthereumTxMsg) Cost() *big.Int {
|
||||||
var v byte
|
total := msg.Fee()
|
||||||
|
total.Add(total, msg.Data.Amount)
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fee returns gasprice * gaslimit.
|
||||||
|
func (msg EthereumTxMsg) Fee() *big.Int {
|
||||||
|
return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Auxiliary
|
||||||
|
|
||||||
|
// TxDecoder returns an sdk.TxDecoder that can decode both auth.StdTx and
|
||||||
|
// EthereumTxMsg transactions.
|
||||||
|
func TxDecoder(cdc *codec.Codec) sdk.TxDecoder {
|
||||||
|
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||||
|
var tx sdk.Tx
|
||||||
|
|
||||||
|
if len(txBytes) == 0 {
|
||||||
|
return nil, sdk.ErrTxDecode("txBytes are empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recoverEthSig recovers a signature according to the Ethereum specification and
|
||||||
|
// returns the sender or an error.
|
||||||
|
//
|
||||||
|
// Ref: Ethereum Yellow Paper (BYZANTIUM VERSION 69351d5) Appendix F
|
||||||
|
func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, error) {
|
||||||
|
if Vb.BitLen() > 8 {
|
||||||
|
return ethcmn.Address{}, errors.New("invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
V := byte(Vb.Uint64() - 27)
|
||||||
|
if !ethcrypto.ValidateSignatureValues(V, R, S, true) {
|
||||||
|
return ethcmn.Address{}, errors.New("invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode the signature in uncompressed format
|
||||||
r, s := R.Bytes(), S.Bytes()
|
r, s := R.Bytes(), S.Bytes()
|
||||||
sig := make([]byte, 65)
|
sig := make([]byte, 65)
|
||||||
|
|
||||||
copy(sig[32-len(r):32], r)
|
copy(sig[32-len(r):32], r)
|
||||||
copy(sig[64-len(s):64], s)
|
copy(sig[64-len(s):64], s)
|
||||||
|
sig[64] = V
|
||||||
|
|
||||||
if chainID.Sign() == 0 {
|
// recover the public key from the signature
|
||||||
v = byte(Vb.Uint64() - 27)
|
pub, err := ethcrypto.Ecrecover(sigHash[:], sig)
|
||||||
} else {
|
if err != nil {
|
||||||
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
return ethcmn.Address{}, err
|
||||||
V := new(big.Int).Sub(Vb, chainIDMul)
|
|
||||||
|
|
||||||
v = byte(V.Uint64() - 35)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sig[64] = v
|
if len(pub) == 0 || pub[0] != 4 {
|
||||||
return sig
|
return ethcmn.Address{}, errors.New("invalid public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr ethcmn.Address
|
||||||
|
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto"
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -15,17 +15,17 @@ import (
|
|||||||
func TestMsgEthereumTx(t *testing.T) {
|
func TestMsgEthereumTx(t *testing.T) {
|
||||||
addr := GenerateEthAddress()
|
addr := GenerateEthAddress()
|
||||||
|
|
||||||
msg1 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg1 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
require.NotNil(t, msg1)
|
require.NotNil(t, msg1)
|
||||||
require.Equal(t, *msg1.Data.Recipient, addr)
|
require.Equal(t, *msg1.Data.Recipient, addr)
|
||||||
|
|
||||||
msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
|
msg2 := NewEthereumTxMsgContract(0, nil, 100000, nil, []byte("test"))
|
||||||
require.NotNil(t, msg2)
|
require.NotNil(t, msg2)
|
||||||
require.Nil(t, msg2.Data.Recipient)
|
require.Nil(t, msg2.Data.Recipient)
|
||||||
|
|
||||||
msg3 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg3 := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
require.Equal(t, msg3.Route(), RouteMsgEthereumTx)
|
require.Equal(t, msg3.Route(), RouteEthereumTxMsg)
|
||||||
require.Equal(t, msg3.Type(), TypeMsgEthereumTx)
|
require.Equal(t, msg3.Type(), TypeEthereumTxMsg)
|
||||||
require.Panics(t, func() { msg3.GetSigners() })
|
require.Panics(t, func() { msg3.GetSigners() })
|
||||||
require.Panics(t, func() { msg3.GetSignBytes() })
|
require.Panics(t, func() { msg3.GetSignBytes() })
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ func TestMsgEthereumTxValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
msg := NewMsgEthereumTx(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
|
msg := NewEthereumTxMsg(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
|
||||||
|
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||||
@ -60,14 +60,14 @@ func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
|
|||||||
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
chainID := big.NewInt(3)
|
chainID := big.NewInt(3)
|
||||||
|
|
||||||
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
hash := msg.RLPSignBytes(chainID)
|
hash := msg.RLPSignBytes(chainID)
|
||||||
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
|
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMsgEthereumTxRLPEncode(t *testing.T) {
|
func TestMsgEthereumTxRLPEncode(t *testing.T) {
|
||||||
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
raw, err := rlp.EncodeToBytes(msg)
|
raw, err := rlp.EncodeToBytes(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -75,11 +75,11 @@ func TestMsgEthereumTxRLPEncode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMsgEthereumTxRLPDecode(t *testing.T) {
|
func TestMsgEthereumTxRLPDecode(t *testing.T) {
|
||||||
var msg MsgEthereumTx
|
var msg EthereumTxMsg
|
||||||
|
|
||||||
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
|
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
|
||||||
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
expectedMsg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
expectedMsg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
err := rlp.Decode(bytes.NewReader(raw), &msg)
|
err := rlp.Decode(bytes.NewReader(raw), &msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -88,34 +88,46 @@ func TestMsgEthereumTxRLPDecode(t *testing.T) {
|
|||||||
|
|
||||||
func TestMsgEthereumTxHash(t *testing.T) {
|
func TestMsgEthereumTxHash(t *testing.T) {
|
||||||
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
hash := msg.Hash()
|
hash := msg.Hash()
|
||||||
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
|
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMsgEthereumTxSig(t *testing.T) {
|
func TestMsgEthereumTxSig(t *testing.T) {
|
||||||
priv, _ := ethcrypto.GenerateKey()
|
|
||||||
addr := PrivKeyToEthAddress(priv)
|
|
||||||
|
|
||||||
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
|
||||||
chainID := big.NewInt(3)
|
chainID := big.NewInt(3)
|
||||||
|
|
||||||
msg.Sign(chainID, priv)
|
priv1, _ := crypto.GenerateKey()
|
||||||
|
priv2, _ := crypto.GenerateKey()
|
||||||
|
addr1 := ethcmn.BytesToAddress(priv1.PubKey().Address().Bytes())
|
||||||
|
addr2 := ethcmn.BytesToAddress(priv2.PubKey().Address().Bytes())
|
||||||
|
|
||||||
resultAddr, err := msg.VerifySig(chainID)
|
// require valid signature passes validation
|
||||||
|
msg := NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test"))
|
||||||
|
msg.Sign(chainID, priv1.ToECDSA())
|
||||||
|
|
||||||
|
signer, err := msg.VerifySig(chainID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, addr, resultAddr)
|
require.Equal(t, addr1, signer)
|
||||||
|
require.NotEqual(t, addr2, signer)
|
||||||
|
|
||||||
|
// require invalid chain ID fail validation
|
||||||
|
msg = NewEthereumTxMsg(0, addr1, nil, 100000, nil, []byte("test"))
|
||||||
|
msg.Sign(chainID, priv1.ToECDSA())
|
||||||
|
|
||||||
|
signer, err = msg.VerifySig(big.NewInt(4))
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, ethcmn.Address{}, signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMsgEthereumTxAmino(t *testing.T) {
|
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||||
addr := GenerateEthAddress()
|
addr := GenerateEthAddress()
|
||||||
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
raw, err := msgCodec.MarshalBinaryBare(msg)
|
raw, err := msgCodec.MarshalBinaryBare(msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var msg2 MsgEthereumTx
|
var msg2 EthereumTxMsg
|
||||||
|
|
||||||
err = msgCodec.UnmarshalBinaryBare(raw, &msg2)
|
err = msgCodec.UnmarshalBinaryBare(raw, &msg2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -176,10 +176,10 @@ func (so *stateObject) SetNonce(nonce uint64) {
|
|||||||
prev: so.account.Sequence,
|
prev: so.account.Sequence,
|
||||||
})
|
})
|
||||||
|
|
||||||
so.setNonce(int64(nonce))
|
so.setNonce(nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (so *stateObject) setNonce(nonce int64) {
|
func (so *stateObject) setNonce(nonce uint64) {
|
||||||
so.account.Sequence = nonce
|
so.account.Sequence = nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ func (so *stateObject) CodeHash() []byte {
|
|||||||
|
|
||||||
// Nonce returns the state object's current nonce (sequence number).
|
// Nonce returns the state object's current nonce (sequence number).
|
||||||
func (so *stateObject) Nonce() uint64 {
|
func (so *stateObject) Nonce() uint64 {
|
||||||
return uint64(so.account.Sequence)
|
return so.account.Sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code returns the contract code associated with this object, if any.
|
// Code returns the contract code associated with this object, if any.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto"
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
@ -12,19 +12,14 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
|
|
||||||
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
|
|
||||||
return ethcrypto.PubkeyToAddress(p.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateAddress generates an Ethereum address.
|
// GenerateAddress generates an Ethereum address.
|
||||||
func GenerateEthAddress() ethcmn.Address {
|
func GenerateEthAddress() ethcmn.Address {
|
||||||
priv, err := ethcrypto.GenerateKey()
|
priv, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return PrivKeyToEthAddress(priv)
|
return ethcrypto.PubkeyToAddress(priv.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSigner attempts to validate a signer for a given slice of bytes over
|
// ValidateSigner attempts to validate a signer for a given slice of bytes over
|
||||||
|
Loading…
Reference in New Issue
Block a user