TX Routing Refactor (#496)
This commit is contained in:
parent
167d43ce38
commit
a3619584f8
89
Gopkg.lock
generated
89
Gopkg.lock
generated
@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:495c7006c2f48b705f0d89fd8449a2ae70622bb748788d9d17caafa65a6769f9"
|
digest = "1:8038f3159385d2017d12ce7d8ccf25e59554a265d76d8c31f8c80acb589da6c6"
|
||||||
name = "github.com/aristanetworks/goarista"
|
name = "github.com/aristanetworks/goarista"
|
||||||
packages = ["monotime"]
|
packages = ["monotime"]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "33151c4543a79b013e8e6799ef45b2ba88c3cd1c"
|
revision = "5bb443fba8e05f4a819301a63af91fe3cbadcc17"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -33,7 +33,7 @@
|
|||||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:912c494215c339688331953ba09cb9698a4797fe647d93a4e0a117c9c7b960a2"
|
digest = "1:e70ff0ca07fdc5dfe6cf280917ae4434f069f164a0bf18681e37cb19b01ee151"
|
||||||
name = "github.com/cosmos/cosmos-sdk"
|
name = "github.com/cosmos/cosmos-sdk"
|
||||||
packages = [
|
packages = [
|
||||||
"baseapp",
|
"baseapp",
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"x/stake/types",
|
"x/stake/types",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
|
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
|
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
|
||||||
@ -74,13 +74,6 @@
|
|||||||
revision = "cbaa98ba5575e67703b32b4b19f73c91f3c4159e"
|
revision = "cbaa98ba5575e67703b32b4b19f73c91f3c4159e"
|
||||||
version = "v1.7.1"
|
version = "v1.7.1"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
|
|
||||||
name = "github.com/ebuchman/fail-test"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "T"
|
|
||||||
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7"
|
digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7"
|
||||||
@ -378,14 +371,16 @@
|
|||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:f4f3858737fd9db5cf3ef8019c918a798a987d4d11f7e531c54dfe70d4708642"
|
digest = "1:98aa8bc119587e8bddd558bf2921a645ea6c0ff3195760142113d4dc7cab509f"
|
||||||
name = "github.com/prometheus/client_golang"
|
name = "github.com/prometheus/client_golang"
|
||||||
packages = [
|
packages = [
|
||||||
"prometheus",
|
"prometheus",
|
||||||
|
"prometheus/internal",
|
||||||
"prometheus/promhttp",
|
"prometheus/promhttp",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632"
|
revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3"
|
||||||
|
version = "v0.9.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -397,7 +392,7 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:2d9b03513fadf4adf193b3570f5ef65ee57b658d9f11e901a06d17baf2bdc88b"
|
digest = "1:95442856f5c1df4ff0be91b5a320ee717dd539d4091a3574aeb96bfbd407aa41"
|
||||||
name = "github.com/prometheus/common"
|
name = "github.com/prometheus/common"
|
||||||
packages = [
|
packages = [
|
||||||
"expfmt",
|
"expfmt",
|
||||||
@ -405,7 +400,7 @@
|
|||||||
"model",
|
"model",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6"
|
revision = "0b1957f9d949dfa3084171a6ec5642b38055276a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
@ -455,12 +450,12 @@
|
|||||||
version = "v1.1.2"
|
version = "v1.1.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
|
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
|
||||||
name = "github.com/spf13/cast"
|
name = "github.com/spf13/cast"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
|
||||||
version = "v1.2.0"
|
version = "v1.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:52565bd966162d1f4579757f66ce6a7ca9054e7f6b662f0c7c96e4dd228fd017"
|
digest = "1:52565bd966162d1f4579757f66ce6a7ca9054e7f6b662f0c7c96e4dd228fd017"
|
||||||
@ -508,7 +503,7 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:ea4a45f31f55c7a42ba3063baa646ac94eb7ee9afe60c1fd2c8b396930222620"
|
digest = "1:fa0605d74039818b662892c950cfd9938ab81ebb5f8e2479ceb4734cdae21df3"
|
||||||
name = "github.com/syndtr/goleveldb"
|
name = "github.com/syndtr/goleveldb"
|
||||||
packages = [
|
packages = [
|
||||||
"leveldb",
|
"leveldb",
|
||||||
@ -525,7 +520,7 @@
|
|||||||
"leveldb/util",
|
"leveldb/util",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43"
|
revision = "f9080354173f192dfc8821931eacf9cfd6819253"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:71ffd1fca92b4972ecd588cf13d9929d4f444659788e9128d055a9126498d41d"
|
digest = "1:71ffd1fca92b4972ecd588cf13d9929d4f444659788e9128d055a9126498d41d"
|
||||||
@ -535,35 +530,23 @@
|
|||||||
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
|
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:bcaa26e82b7707fbdfa6d0506ff1f30158eeb23126f761ac65881ed51339bf9e"
|
||||||
digest = "1:2b15c0442dc80b581ce7028b2e43029d2f3f985da43cb1d55f7bcdeca785bda0"
|
|
||||||
name = "github.com/tendermint/ed25519"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"edwards25519",
|
|
||||||
"extra25519",
|
|
||||||
]
|
|
||||||
pruneopts = "T"
|
|
||||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:25c97d29878b5f821bb17a379469f0f923426188241bf2aa81c18728cdc6927c"
|
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "github.com/tendermint/go-amino"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee"
|
revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12"
|
||||||
version = "v0.12.0"
|
version = "v0.14.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:2ecd824e1615a8becefea26637fe24576f3800260f5dc91ffe44b37bdbd27878"
|
digest = "1:1e4b8f8f8c428af22d0cc68b1478bef4e144edefc4bb967d4d09aaddbc8cd71e"
|
||||||
name = "github.com/tendermint/iavl"
|
name = "github.com/tendermint/iavl"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d"
|
revision = "fa74114f764f9827c4ad5573f990ed25bf8c4bac"
|
||||||
version = "v0.11.0"
|
version = "v0.11.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:b8bd45120cbea639592420b1d5363f102d819ea89d6239f4dae2a0814c76a6d2"
|
digest = "1:ea9b485e28ee25ef860451187afdec9c38630932e1f999114eb7cab7f8c89966"
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
packages = [
|
packages = [
|
||||||
"abci/client",
|
"abci/client",
|
||||||
@ -592,8 +575,8 @@
|
|||||||
"libs/clist",
|
"libs/clist",
|
||||||
"libs/common",
|
"libs/common",
|
||||||
"libs/db",
|
"libs/db",
|
||||||
"libs/errors",
|
|
||||||
"libs/events",
|
"libs/events",
|
||||||
|
"libs/fail",
|
||||||
"libs/flowrate",
|
"libs/flowrate",
|
||||||
"libs/log",
|
"libs/log",
|
||||||
"libs/pubsub",
|
"libs/pubsub",
|
||||||
@ -614,7 +597,6 @@
|
|||||||
"rpc/core",
|
"rpc/core",
|
||||||
"rpc/core/types",
|
"rpc/core/types",
|
||||||
"rpc/grpc",
|
"rpc/grpc",
|
||||||
"rpc/lib",
|
|
||||||
"rpc/lib/client",
|
"rpc/lib/client",
|
||||||
"rpc/lib/server",
|
"rpc/lib/server",
|
||||||
"rpc/lib/types",
|
"rpc/lib/types",
|
||||||
@ -627,16 +609,17 @@
|
|||||||
"version",
|
"version",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778"
|
revision = "22dcc92232cd04ce7381043e09d85dd536ae3b96"
|
||||||
version = "v0.25.1-rc0"
|
version = "v0.26.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:d738326441b0b732070d727891855573dcb579e74d82fcf9a9459d3257f2eb0c"
|
||||||
digest = "1:56a43b9f51e5c5ea734e866b82d57c842b022c795a0611ff5f57f3d7c47de45d"
|
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = [
|
packages = [
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"curve25519",
|
"curve25519",
|
||||||
|
"ed25519",
|
||||||
|
"ed25519/internal/edwards25519",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"internal/chacha20",
|
"internal/chacha20",
|
||||||
"internal/subtle",
|
"internal/subtle",
|
||||||
@ -650,7 +633,8 @@
|
|||||||
"ssh/terminal",
|
"ssh/terminal",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb"
|
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||||
|
source = "https://github.com/tendermint/crypto"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
|
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
|
||||||
@ -673,8 +657,7 @@
|
|||||||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
digest = "1:96672c90ede3a9cd379151c13c436c93efe845c4a3fdd2ce2a94e9c96f233a2c"
|
||||||
digest = "1:d6d9bf31934efd450aeb0a07061dce8a0e84bc80e4aae05841dd588325832a40"
|
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = [
|
packages = [
|
||||||
"cpu",
|
"cpu",
|
||||||
@ -682,7 +665,7 @@
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "5cd93ef61a7c8f0f858690154eb6de2e69415fa1"
|
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
|
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
|
||||||
@ -721,11 +704,11 @@
|
|||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:849525811c9f6ae1f5bd9b866adb4c9436f4a12d767f48e33bf343596d4aafd7"
|
digest = "1:2460b53d2a66eb9897a17c59ce16c82eeec9affaa31a3ce5814d254abc80fbbd"
|
||||||
name = "google.golang.org/genproto"
|
name = "google.golang.org/genproto"
|
||||||
packages = ["googleapis/rpc/status"]
|
packages = ["googleapis/rpc/status"]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "94acd270e44e65579b9ee3cdab25034d33fed608"
|
revision = "5fc9ac5403620be16bcdb0c8e7644b1178472c3b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
|
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
|
||||||
@ -809,7 +792,11 @@
|
|||||||
"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/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",
|
||||||
|
30
Gopkg.toml
30
Gopkg.toml
@ -7,9 +7,8 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/cosmos/cosmos-sdk"
|
name = "github.com/cosmos/cosmos-sdk"
|
||||||
# TODO: Remove this once 0.25 has been released
|
# TODO: Replace this with 0.27
|
||||||
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
|
revision = "1ea0e4c457fc105b48131a60e3d28c6c1bb32cc0"
|
||||||
# version = "=0.24.2"
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/hashicorp/golang-lru"
|
name = "github.com/hashicorp/golang-lru"
|
||||||
@ -27,23 +26,38 @@
|
|||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
version = "=0.8.0"
|
version = "=0.8.0"
|
||||||
|
|
||||||
# dependecy overrides
|
[[constraint]]
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||||
|
|
||||||
|
#######################
|
||||||
|
# dependecy overrides #
|
||||||
|
#######################
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "gopkg.in/fatih/set.v0"
|
name = "gopkg.in/fatih/set.v0"
|
||||||
version = "=0.1.0"
|
version = "=0.1.0"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/tendermint/go-amino"
|
||||||
|
version = "v0.14.0"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/iavl"
|
name = "github.com/tendermint/iavl"
|
||||||
version = "=v0.11.0"
|
version = "=v0.11.1"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
source = "https://github.com/tendermint/crypto"
|
||||||
|
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/tendermint"
|
name = "github.com/tendermint/tendermint"
|
||||||
version = "=0.25.1-rc0"
|
version = "v0.26.1"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/tendermint/go-amino"
|
name = "golang.org/x/sys"
|
||||||
version = "=v0.12.0"
|
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||||
|
|
||||||
[prune]
|
[prune]
|
||||||
go-tests = true
|
go-tests = true
|
||||||
|
91
app/ante.go
Normal file
91
app/ante.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 // used for tx simulation
|
||||||
|
|
||||||
|
const (
|
||||||
|
memoCostPerByte = 1
|
||||||
|
maxMemoCharacters = 100
|
||||||
|
secp256k1VerifyCost = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
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
|
||||||
|
// transaction-level processing (e.g. fee payment, signature verification) before
|
||||||
|
// being passed onto it's respective handler.
|
||||||
|
//
|
||||||
|
// NOTE: The EVM will already consume (intrinsic) gas for signature verification
|
||||||
|
// and covering input size as well as handling nonce incrementing.
|
||||||
|
func NewAnteHandler(ak auth.AccountKeeper, fck auth.FeeCollectionKeeper) sdk.AnteHandler {
|
||||||
|
return func(
|
||||||
|
ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||||
|
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
|
||||||
|
stdTx, ok := tx.(auth.StdTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdk.ErrInternal("transaction type invalid: must be StdTx").Result(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle gas/fee checking and spam prevention. We may need two
|
||||||
|
// 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 {
|
||||||
|
return ethAnteHandler(ctx, ethTx, ak)
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.NewAnteHandler(ak, fck)(ctx, stdTx, sim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Ethereum Ante Handler
|
||||||
|
|
||||||
|
// ethAnteHandler defines an internal ante handler for an Ethereum transaction
|
||||||
|
// ethTx that implements the sdk.Msg interface. The Ethereum transaction is a
|
||||||
|
// single message inside a auth.StdTx.
|
||||||
|
//
|
||||||
|
// For now we simply pass the transaction on as the EVM shares common business
|
||||||
|
// logic of an ante handler. Anything not handled by the EVM that should be
|
||||||
|
// prior to transaction processing, should be done here.
|
||||||
|
func ethAnteHandler(
|
||||||
|
ctx sdk.Context, ethTx *evmtypes.MsgEthereumTx, ak auth.AccountKeeper,
|
||||||
|
) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||||
|
|
||||||
|
return ctx, sdk.Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Auxiliary
|
||||||
|
|
||||||
|
// isEthereumTx returns a boolean if a given standard SDK transaction contains
|
||||||
|
// an Ethereum transaction. If so, the transaction is also returned. A standard
|
||||||
|
// SDK transaction contains an Ethereum transaction if it only has a single
|
||||||
|
// message and that embedded message if of type MsgEthereumTx.
|
||||||
|
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
|
||||||
|
}
|
175
app/ante_test.go
Normal file
175
app/ante_test.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"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"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func requireValidTx(
|
||||||
|
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||||
|
) {
|
||||||
|
|
||||||
|
_, result, abort := anteHandler(ctx, tx, sim)
|
||||||
|
require.Equal(t, sdk.CodeOK, result.Code, result.Log)
|
||||||
|
require.False(t, abort)
|
||||||
|
require.True(t, result.IsOK())
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireInvalidTx(
|
||||||
|
t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context,
|
||||||
|
tx sdk.Tx, sim bool, code sdk.CodeType,
|
||||||
|
) {
|
||||||
|
|
||||||
|
newCtx, result, abort := anteHandler(ctx, tx, sim)
|
||||||
|
require.True(t, abort)
|
||||||
|
require.Equal(t, code, result.Code, fmt.Sprintf("invalid result: %v", result))
|
||||||
|
|
||||||
|
if code == sdk.CodeOutOfGas {
|
||||||
|
stdTx, ok := tx.(auth.StdTx)
|
||||||
|
require.True(t, ok, "tx must be in form auth.StdTx")
|
||||||
|
|
||||||
|
// require GasWanted is set correctly
|
||||||
|
require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "'GasWanted' wanted not set correctly")
|
||||||
|
require.True(t, result.GasUsed > result.GasWanted, "'GasUsed' not greater than GasWanted")
|
||||||
|
|
||||||
|
// require that context is set correctly
|
||||||
|
require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidTx(t *testing.T) {
|
||||||
|
setup := newTestSetup()
|
||||||
|
setup.ctx = setup.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, priv2 := newTestAddrKey()
|
||||||
|
|
||||||
|
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
||||||
|
acc1.SetCoins(newTestCoins())
|
||||||
|
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
||||||
|
|
||||||
|
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
|
||||||
|
acc2.SetCoins(newTestCoins())
|
||||||
|
setup.accKeeper.SetAccount(setup.ctx, acc2)
|
||||||
|
|
||||||
|
// require a valid SDK tx to pass
|
||||||
|
fee := newTestStdFee()
|
||||||
|
msg1 := newTestMsg(addr1, addr2)
|
||||||
|
msgs := []sdk.Msg{msg1}
|
||||||
|
|
||||||
|
privKeys := []crypto.PrivKey{priv1, priv2}
|
||||||
|
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
|
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
|
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireValidTx(t, setup.anteHandler, setup.ctx, tx, false)
|
||||||
|
|
||||||
|
// require accounts to update
|
||||||
|
acc1 = setup.accKeeper.GetAccount(setup.ctx, addr1)
|
||||||
|
acc2 = setup.accKeeper.GetAccount(setup.ctx, addr2)
|
||||||
|
require.Equal(t, accSeqs[0]+1, acc1.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) {
|
||||||
|
setup := newTestSetup()
|
||||||
|
setup.ctx = setup.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
addr2, priv2 := newTestAddrKey()
|
||||||
|
addr3, priv3 := newTestAddrKey()
|
||||||
|
|
||||||
|
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
||||||
|
acc1.SetCoins(newTestCoins())
|
||||||
|
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
||||||
|
|
||||||
|
acc2 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr2)
|
||||||
|
acc2.SetCoins(newTestCoins())
|
||||||
|
setup.accKeeper.SetAccount(setup.ctx, acc2)
|
||||||
|
|
||||||
|
fee := newTestStdFee()
|
||||||
|
msg1 := newTestMsg(addr1, addr2)
|
||||||
|
|
||||||
|
// require validation failure with no signers
|
||||||
|
msgs := []sdk.Msg{msg1}
|
||||||
|
|
||||||
|
privKeys := []crypto.PrivKey{}
|
||||||
|
accNums := []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
|
accSeqs := []int64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
|
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
|
||||||
|
// require validation failure with invalid number of signers
|
||||||
|
msgs = []sdk.Msg{msg1}
|
||||||
|
|
||||||
|
privKeys = []crypto.PrivKey{priv1}
|
||||||
|
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
|
||||||
|
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence()}
|
||||||
|
|
||||||
|
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnauthorized)
|
||||||
|
|
||||||
|
// require validation failure with an invalid signer
|
||||||
|
msg2 := newTestMsg(addr1, addr3)
|
||||||
|
msgs = []sdk.Msg{msg1, msg2}
|
||||||
|
|
||||||
|
privKeys = []crypto.PrivKey{priv1, priv2, priv3}
|
||||||
|
accNums = []int64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
|
||||||
|
accSeqs = []int64{acc1.GetSequence(), acc2.GetSequence(), 0}
|
||||||
|
|
||||||
|
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeUnknownAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSDKInvalidAcc(t *testing.T) {
|
||||||
|
setup := newTestSetup()
|
||||||
|
setup.ctx = setup.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
addr1, priv1 := newTestAddrKey()
|
||||||
|
|
||||||
|
acc1 := setup.accKeeper.NewAccountWithAddress(setup.ctx, addr1)
|
||||||
|
acc1.SetCoins(newTestCoins())
|
||||||
|
setup.accKeeper.SetAccount(setup.ctx, acc1)
|
||||||
|
|
||||||
|
fee := newTestStdFee()
|
||||||
|
msg1 := newTestMsg(addr1)
|
||||||
|
msgs := []sdk.Msg{msg1}
|
||||||
|
privKeys := []crypto.PrivKey{priv1}
|
||||||
|
|
||||||
|
// require validation failure with invalid account number
|
||||||
|
accNums := []int64{1}
|
||||||
|
accSeqs := []int64{acc1.GetSequence()}
|
||||||
|
|
||||||
|
tx := newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
|
||||||
|
|
||||||
|
// require validation failure with invalid sequence (nonce)
|
||||||
|
accNums = []int64{acc1.GetAccountNumber()}
|
||||||
|
accSeqs = []int64{1}
|
||||||
|
|
||||||
|
tx = newTestSDKTx(setup.ctx, msgs, privKeys, accNums, accSeqs, fee)
|
||||||
|
requireInvalidTx(t, setup.anteHandler, setup.ctx, tx, false, sdk.CodeInvalidSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSDKGasConsumption(t *testing.T) {
|
||||||
|
// TODO: Test gas consumption and OOG once ante handler implementation stabilizes
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
@ -3,7 +3,6 @@ package app
|
|||||||
import (
|
import (
|
||||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||||
"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/cosmos-sdk/x/bank"
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||||
@ -11,21 +10,30 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/x/params"
|
"github.com/cosmos/cosmos-sdk/x/params"
|
||||||
"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"
|
||||||
|
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/cosmos/ethermint/handlers"
|
|
||||||
"github.com/cosmos/ethermint/types"
|
|
||||||
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
tmcmn "github.com/tendermint/tendermint/libs/common"
|
tmcmn "github.com/tendermint/tendermint/libs/common"
|
||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
tmlog "github.com/tendermint/tendermint/libs/log"
|
tmlog "github.com/tendermint/tendermint/libs/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const appName = "Ethermint"
|
||||||
appName = "Ethermint"
|
|
||||||
|
// application multi-store keys
|
||||||
|
var (
|
||||||
|
storeKeyAccount = sdk.NewKVStoreKey("acc")
|
||||||
|
storeKeyStorage = sdk.NewKVStoreKey("contract_storage")
|
||||||
|
storeKeyMain = sdk.NewKVStoreKey("main")
|
||||||
|
storeKeyStake = sdk.NewKVStoreKey("stake")
|
||||||
|
storeKeySlashing = sdk.NewKVStoreKey("slashing")
|
||||||
|
storeKeyGov = sdk.NewKVStoreKey("gov")
|
||||||
|
storeKeyFeeColl = sdk.NewKVStoreKey("fee")
|
||||||
|
storeKeyParams = sdk.NewKVStoreKey("params")
|
||||||
|
storeKeyTransParams = sdk.NewTransientStoreKey("transient_params")
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -59,38 +67,35 @@ type (
|
|||||||
|
|
||||||
// NewEthermintApp returns a reference to a new initialized Ethermint
|
// NewEthermintApp returns a reference to a new initialized Ethermint
|
||||||
// application.
|
// application.
|
||||||
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *EthermintApp {
|
//
|
||||||
|
// TODO: Ethermint needs to support being bootstrapped as an application running
|
||||||
|
// in a sovereign zone and as an application running with a shared security model.
|
||||||
|
// For now, it will support only running as a sovereign application.
|
||||||
|
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, baseAppOpts ...func(*bam.BaseApp)) *EthermintApp {
|
||||||
cdc := CreateCodec()
|
cdc := CreateCodec()
|
||||||
cms := store.NewCommitMultiStore(db)
|
|
||||||
|
|
||||||
baseAppOpts := []func(*bam.BaseApp){
|
|
||||||
func(bApp *bam.BaseApp) { bApp.SetCMS(cms) },
|
|
||||||
}
|
|
||||||
baseApp := bam.NewBaseApp(appName, logger, db, types.TxDecoder(cdc, sdkAddr), baseAppOpts...)
|
|
||||||
|
|
||||||
|
baseApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOpts...)
|
||||||
app := &EthermintApp{
|
app := &EthermintApp{
|
||||||
BaseApp: baseApp,
|
BaseApp: baseApp,
|
||||||
cdc: cdc,
|
cdc: cdc,
|
||||||
accountKey: types.StoreKeyAccount,
|
accountKey: storeKeyAccount,
|
||||||
storageKey: types.StoreKeyStorage,
|
storageKey: storeKeyStorage,
|
||||||
mainKey: types.StoreKeyMain,
|
mainKey: storeKeyMain,
|
||||||
stakeKey: types.StoreKeyStake,
|
stakeKey: storeKeyStake,
|
||||||
slashingKey: types.StoreKeySlashing,
|
slashingKey: storeKeySlashing,
|
||||||
govKey: types.StoreKeyGov,
|
govKey: storeKeyGov,
|
||||||
feeCollKey: types.StoreKeyFeeColl,
|
feeCollKey: storeKeyFeeColl,
|
||||||
paramsKey: types.StoreKeyParams,
|
paramsKey: storeKeyParams,
|
||||||
tParamsKey: types.StoreKeyTransParams,
|
tParamsKey: storeKeyTransParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set application keepers and mappers
|
|
||||||
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
|
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.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
|
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
|
||||||
|
|
||||||
// register message handlers
|
// register message handlers
|
||||||
app.Router().
|
app.Router().
|
||||||
// TODO: Do we need to mount bank and IBC handlers? Should be handled
|
// TODO: add remaining routes
|
||||||
// directly in the EVM.
|
|
||||||
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
|
AddRoute("stake", stake.NewHandler(app.stakeKeeper)).
|
||||||
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
|
AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)).
|
||||||
AddRoute("gov", gov.NewHandler(app.govKeeper))
|
AddRoute("gov", gov.NewHandler(app.govKeeper))
|
||||||
@ -99,7 +104,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
|
|||||||
app.SetInitChainer(app.initChainer)
|
app.SetInitChainer(app.initChainer)
|
||||||
app.SetBeginBlocker(app.BeginBlocker)
|
app.SetBeginBlocker(app.BeginBlocker)
|
||||||
app.SetEndBlocker(app.EndBlocker)
|
app.SetEndBlocker(app.EndBlocker)
|
||||||
app.SetAnteHandler(handlers.AnteHandler(app.accountKeeper, app.feeCollKeeper))
|
app.SetAnteHandler(NewAnteHandler(app.accountKeeper, app.feeCollKeeper))
|
||||||
|
|
||||||
app.MountStoresIAVL(
|
app.MountStoresIAVL(
|
||||||
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
|
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
|
||||||
@ -117,19 +122,28 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
|
|||||||
|
|
||||||
// BeginBlocker signals the beginning of a block. It performs application
|
// BeginBlocker signals the beginning of a block. It performs application
|
||||||
// updates on the start of every block.
|
// updates on the start of every block.
|
||||||
func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
func (app *EthermintApp) BeginBlocker(
|
||||||
|
_ sdk.Context, _ abci.RequestBeginBlock,
|
||||||
|
) abci.ResponseBeginBlock {
|
||||||
|
|
||||||
return abci.ResponseBeginBlock{}
|
return abci.ResponseBeginBlock{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndBlocker signals the end of a block. It performs application updates on
|
// EndBlocker signals the end of a block. It performs application updates on
|
||||||
// the end of every block.
|
// the end of every block.
|
||||||
func (app *EthermintApp) EndBlocker(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock {
|
func (app *EthermintApp) EndBlocker(
|
||||||
|
_ sdk.Context, _ abci.RequestEndBlock,
|
||||||
|
) abci.ResponseEndBlock {
|
||||||
|
|
||||||
return abci.ResponseEndBlock{}
|
return abci.ResponseEndBlock{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initChainer initializes the application blockchain with validators and other
|
// initChainer initializes the application blockchain with validators and other
|
||||||
// state data from TendermintCore.
|
// state data from TendermintCore.
|
||||||
func (app *EthermintApp) initChainer(_ sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
func (app *EthermintApp) initChainer(
|
||||||
|
_ sdk.Context, req abci.RequestInitChain,
|
||||||
|
) abci.ResponseInitChain {
|
||||||
|
|
||||||
var genesisState GenesisState
|
var genesisState GenesisState
|
||||||
stateJSON := req.AppStateBytes
|
stateJSON := req.AppStateBytes
|
||||||
|
|
||||||
@ -148,8 +162,12 @@ func (app *EthermintApp) initChainer(_ sdk.Context, req abci.RequestInitChain) a
|
|||||||
func CreateCodec() *codec.Codec {
|
func CreateCodec() *codec.Codec {
|
||||||
cdc := codec.New()
|
cdc := codec.New()
|
||||||
|
|
||||||
types.RegisterCodec(cdc)
|
// TODO: Add remaining codec registrations:
|
||||||
|
// bank, staking, distribution, slashing, and gov
|
||||||
|
|
||||||
|
evmtypes.RegisterCodec(cdc)
|
||||||
auth.RegisterCodec(cdc)
|
auth.RegisterCodec(cdc)
|
||||||
|
sdk.RegisterCodec(cdc)
|
||||||
codec.RegisterCrypto(cdc)
|
codec.RegisterCrypto(cdc)
|
||||||
|
|
||||||
return cdc
|
return cdc
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package handlers
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
@ -6,13 +6,15 @@ import (
|
|||||||
dbm "github.com/tendermint/tendermint/libs/db"
|
dbm "github.com/tendermint/tendermint/libs/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createTestMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
|
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
capKey := sdk.NewKVStoreKey("capkey")
|
capKey := sdk.NewKVStoreKey("capkey")
|
||||||
|
capKey2 := sdk.NewKVStoreKey("capkey2")
|
||||||
ms := store.NewCommitMultiStore(db)
|
ms := store.NewCommitMultiStore(db)
|
||||||
|
|
||||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||||
|
ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db)
|
||||||
ms.LoadLatestVersion()
|
ms.LoadLatestVersion()
|
||||||
|
|
||||||
return ms, capKey
|
return ms, capKey, capKey2
|
||||||
}
|
}
|
111
app/test_utils.go
Normal file
111
app/test_utils.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto"
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testDenom = "testcoin"
|
||||||
|
|
||||||
|
type testSetup struct {
|
||||||
|
ctx sdk.Context
|
||||||
|
accKeeper auth.AccountKeeper
|
||||||
|
feeKeeper auth.FeeCollectionKeeper
|
||||||
|
anteHandler sdk.AnteHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestSetup() testSetup {
|
||||||
|
cdc := codec.New()
|
||||||
|
ms, capKey, capKey2 := setupMultiStore()
|
||||||
|
|
||||||
|
auth.RegisterBaseAccount(cdc)
|
||||||
|
|
||||||
|
accKeeper := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
|
||||||
|
feeKeeper := auth.NewFeeCollectionKeeper(cdc, capKey2)
|
||||||
|
anteHandler := NewAnteHandler(accKeeper, feeKeeper)
|
||||||
|
|
||||||
|
ctx := sdk.NewContext(
|
||||||
|
ms,
|
||||||
|
abci.Header{ChainID: "3", Time: time.Now().UTC()},
|
||||||
|
false,
|
||||||
|
log.NewNopLogger(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return testSetup{
|
||||||
|
ctx: ctx,
|
||||||
|
accKeeper: accKeeper,
|
||||||
|
feeKeeper: feeKeeper,
|
||||||
|
anteHandler: anteHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
|
||||||
|
return sdk.NewTestMsg(addrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestCoins() sdk.Coins {
|
||||||
|
return sdk.Coins{sdk.NewInt64Coin(testDenom, 10000000)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestStdFee() auth.StdFee {
|
||||||
|
return auth.NewStdFee(5000, sdk.NewInt64Coin(testDenom, 150))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateAddress generates an Ethereum address.
|
||||||
|
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
|
||||||
|
priv := tmsecp256k1.GenPrivKey()
|
||||||
|
addr := sdk.AccAddress(priv.PubKey().Address())
|
||||||
|
return addr, priv
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestSDKTx(
|
||||||
|
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
|
||||||
|
accNums []int64, seqs []int64, fee auth.StdFee,
|
||||||
|
) sdk.Tx {
|
||||||
|
|
||||||
|
sigs := make([]auth.StdSignature, len(privs))
|
||||||
|
for i, priv := range privs {
|
||||||
|
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
|
||||||
|
sig, err := priv.Sign(signBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sigs[i] = auth.StdSignature{
|
||||||
|
PubKey: priv.PubKey(),
|
||||||
|
Signature: sig,
|
||||||
|
AccountNumber: accNums[i],
|
||||||
|
Sequence: seqs[i],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.NewStdTx(msgs, fee, sigs, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) sdk.Tx {
|
||||||
|
chainID, ok := new(big.Int).SetString(ctx.ChainID(), 10)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("invalid chainID: %s", ctx.ChainID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := crypto.PrivKeyToSecp256k1(priv)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to convert private key: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Sign(chainID, privKey)
|
||||||
|
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
|
||||||
|
}
|
22
crypto/crypto.go
Normal file
22
crypto/crypto.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
24
crypto/crypto_test.go
Normal file
24
crypto/crypto_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -6,58 +6,39 @@ and subject to change.
|
|||||||
## Routing
|
## Routing
|
||||||
|
|
||||||
Ethermint needs to parse and handle transactions routed for both the EVM and for
|
Ethermint needs to parse and handle transactions routed for both the EVM and for
|
||||||
the Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure and utilizing
|
the Cosmos hub. We attempt to achieve this by mimicking
|
||||||
the `Payload` as the potential encoding of a Cosmos-routed transaction. What
|
[Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure and
|
||||||
designates this encoding, and ultimately routing, is the `Recipient` address --
|
treat it as a unique Cosmos SDK message type. An Ethereum transaction is a single
|
||||||
if this address matches some global unique predefined and configured address,
|
[`sdk.Msg`](https://godoc.org/github.com/cosmos/cosmos-sdk/types#Msg) contained
|
||||||
we regard it as a transaction meant for Cosmos, otherwise, the transaction is a
|
in an [`auth.StdTx`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#StdTx).
|
||||||
pure Ethereum transaction and will be executed in the EVM.
|
All relevant Ethereum transaction information is contained in this message. This
|
||||||
|
includes the signature, gas, payload, etc.
|
||||||
For Cosmos routed transactions, the `Transaction.Payload` will contain an [Amino](https://github.com/tendermint/go-amino) encoded embedded transaction that must
|
|
||||||
implement the `sdk.Tx` interface. Note, the embedding (outer) `Transaction` is
|
|
||||||
still RLP encoded in order to preserve compatibility with existing tooling. In
|
|
||||||
addition, at launch, Ethermint will only support the `auth.StdTx` embedded Cosmos
|
|
||||||
transaction type.
|
|
||||||
|
|
||||||
Being that Ethermint implements the Tendermint ABCI application interface, as
|
Being that Ethermint implements the Tendermint ABCI application interface, as
|
||||||
transactions are consumed, they are passed through a series of handlers. Once such
|
transactions are consumed, they are passed through a series of handlers. Once such
|
||||||
handler, `runTx`, is responsible for invoking the `TxDecoder` which performs the
|
handler, the `AnteHandler`, is responsible for performing preliminary message
|
||||||
business logic of properly deserializing raw transaction bytes into either an
|
execution business logic such as fee payment, signature verification, etc. This is
|
||||||
Ethereum transaction or a Cosmos transaction.
|
particular to Cosmos SDK routed transactions. Ethereum routed transactions will
|
||||||
|
bypass this as the EVM handles the same business logic.
|
||||||
|
|
||||||
__Note__: Our goal is to utilize Geth as a library, at least as much as possible,
|
Ethereum routed transactions coming from a web3 source are expected to be RLP
|
||||||
so it should be expected that these types and the operations you may perform on
|
encoded, however all internal interaction between Ethermint and Tendermint will
|
||||||
them will keep in line with Ethereum (e.g. signature algorithms and gas/fees).
|
utilize Amino encoding.
|
||||||
In addition, we aim to have existing tooling and frameworks in the Ethereum
|
|
||||||
ecosystem have 100% compatibility with creating transactions in Ethermint.
|
|
||||||
|
|
||||||
## Transactions & Messages
|
__Note__: Our goal is to utilize Geth/Turbo-Geth as a library, at least as much
|
||||||
|
as possible, so it should be expected that these types and the operations you may
|
||||||
The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`).
|
perform on them will keep in line with Ethereum (e.g. signature algorithms and
|
||||||
A `sdk.Tx` is a list of `sdk.Msg` wrapped with authentication and fee data. Users
|
gas/fees). In addition, we aim to have existing tooling and frameworks in the
|
||||||
can create messages containing arbitrary information by implementing the `sdk.Msg`
|
Ethereum ecosystem have 100% compatibility with creating transactions in Ethermint.
|
||||||
interface.
|
|
||||||
|
|
||||||
In Ethermint, the `Transaction` type implements the Cosmos SDK `sdk.Tx` interface.
|
|
||||||
It addition, it implements the Cosmos SDK `sdk.Msg` interface for the sole purpose
|
|
||||||
of being to perform basic validation checks in the `BaseApp`. It, however, has
|
|
||||||
no distinction between transactions and messages.
|
|
||||||
|
|
||||||
## Signatures
|
## Signatures
|
||||||
|
|
||||||
Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
|
Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
|
||||||
signatures. A `Transaction` is expected to have a single signature for Ethereum
|
signatures. A `Transaction` is expected to have a single signature for Ethereum
|
||||||
routed transactions. However, just as in Cosmos, Ethermint will support multiple
|
routed transactions. However, just as in Cosmos, Ethermint will support multiple
|
||||||
signers for embedded Cosmos routed transactions. Signatures over the
|
signers for non-Ethereum transactions. Signatures over the
|
||||||
`Transaction` type are identical to Ethereum. However, the embedded transaction contains
|
`Transaction` type are identical to Ethereum and the signatures will not be duplicated
|
||||||
a canonical signature structure that contains the signature itself and other
|
in the embedding [`auth.StdTx`](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#StdTx).
|
||||||
information such as an account's sequence number. This, in addition to the chainID,
|
|
||||||
helps prevent "replay attacks", where the same message could be executed over and
|
|
||||||
over again.
|
|
||||||
|
|
||||||
An embedded transaction's list of signatures must much the unique list of addresses
|
|
||||||
returned by each message's `GetSigners` call. In addition, the address of first
|
|
||||||
signer of the embedded transaction is responsible for paying the fees.
|
|
||||||
|
|
||||||
## Gas & Fees
|
## Gas & Fees
|
||||||
|
|
||||||
|
203
handlers/ante.go
203
handlers/ante.go
@ -1,203 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
|
|
||||||
"github.com/cosmos/ethermint/types"
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TODO: Ported from the SDK and may have a different context/value for
|
|
||||||
// Ethermint.
|
|
||||||
verifySigCost = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// internalAnteHandler reflects a function signature an internal ante handler
|
|
||||||
// must implementing. Internal ante handlers will be dependant upon the
|
|
||||||
// transaction type.
|
|
||||||
type internalAnteHandler func(
|
|
||||||
sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountKeeper,
|
|
||||||
) (newCtx sdk.Context, res sdk.Result, abort bool)
|
|
||||||
|
|
||||||
// AnteHandler is responsible for attempting to route an Ethereum or SDK
|
|
||||||
// transaction to an internal ante handler for performing transaction-level
|
|
||||||
// processing (e.g. fee payment, signature verification) before being passed
|
|
||||||
// onto it's respective handler.
|
|
||||||
func AnteHandler(ak auth.AccountKeeper, _ auth.FeeCollectionKeeper) sdk.AnteHandler {
|
|
||||||
return func(sdkCtx sdk.Context, tx sdk.Tx, _ bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
|
||||||
var (
|
|
||||||
handler internalAnteHandler
|
|
||||||
gasLimit int64
|
|
||||||
)
|
|
||||||
|
|
||||||
switch tx := tx.(type) {
|
|
||||||
case types.Transaction:
|
|
||||||
gasLimit = int64(tx.Data().GasLimit)
|
|
||||||
handler = handleEthTx
|
|
||||||
case auth.StdTx:
|
|
||||||
gasLimit = tx.Fee.Gas
|
|
||||||
handler = handleEmbeddedTx
|
|
||||||
default:
|
|
||||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
newCtx = sdkCtx.WithGasMeter(sdk.NewGasMeter(gasLimit))
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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 = gasLimit
|
|
||||||
res.GasUsed = newCtx.GasMeter().GasConsumed()
|
|
||||||
abort = true
|
|
||||||
default:
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return handler(newCtx, tx, ak)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEthTx implements an ante handler for an Ethereum transaction. It
|
|
||||||
// validates the signature and if valid returns an OK result.
|
|
||||||
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
|
|
||||||
ethTx, ok := tx.(types.Transaction)
|
|
||||||
if !ok {
|
|
||||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// the SDK chainID is a string representation of integer
|
|
||||||
chainID, ok := new(big.Int).SetString(sdkCtx.ChainID(), 10)
|
|
||||||
if !ok {
|
|
||||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante: verify Ethereum signature")
|
|
||||||
|
|
||||||
addr, err := ethTx.VerifySig(chainID)
|
|
||||||
if err != nil {
|
|
||||||
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := ak.GetAccount(sdkCtx, addr.Bytes())
|
|
||||||
|
|
||||||
// validate the account nonce (referred to as sequence in the AccountMapper)
|
|
||||||
seq := acc.GetSequence()
|
|
||||||
if ethTx.Data().AccountNonce != uint64(seq) {
|
|
||||||
return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: The EVM will handle incrementing the nonce (sequence number)
|
|
||||||
//
|
|
||||||
// TODO: Investigate gas consumption models as the EVM instruction set has its
|
|
||||||
// own and we should probably not charge for additional gas where we don't have
|
|
||||||
// to.
|
|
||||||
|
|
||||||
ak.SetAccount(sdkCtx, acc)
|
|
||||||
return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEmbeddedTx implements an ante handler for an SDK transaction. It
|
|
||||||
// validates the signature and if valid returns an OK result.
|
|
||||||
func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
|
|
||||||
stdTx, ok := tx.(auth.StdTx)
|
|
||||||
if !ok {
|
|
||||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateStdTxBasic(stdTx); err != nil {
|
|
||||||
return sdkCtx, err.Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
signerAddrs := stdTx.GetSigners()
|
|
||||||
signerAccs := make([]auth.Account, len(signerAddrs))
|
|
||||||
|
|
||||||
// validate signatures
|
|
||||||
for i, sig := range stdTx.Signatures {
|
|
||||||
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
|
|
||||||
|
|
||||||
acc, err := validateSignature(sdkCtx, stdTx, signer, sig, ak)
|
|
||||||
if err != nil {
|
|
||||||
return sdkCtx, err.Result(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Fees!
|
|
||||||
|
|
||||||
ak.SetAccount(sdkCtx, acc)
|
|
||||||
signerAccs[i] = acc
|
|
||||||
}
|
|
||||||
|
|
||||||
newCtx := auth.WithSigners(sdkCtx, signerAccs)
|
|
||||||
|
|
||||||
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateStdTxBasic validates an auth.StdTx based on parameters that do not
|
|
||||||
// depend on the context.
|
|
||||||
func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) {
|
|
||||||
sigs := stdTx.Signatures
|
|
||||||
if len(sigs) == 0 {
|
|
||||||
return sdk.ErrUnauthorized("transaction missing signatures")
|
|
||||||
}
|
|
||||||
|
|
||||||
signerAddrs := stdTx.GetSigners()
|
|
||||||
if len(sigs) != len(signerAddrs) {
|
|
||||||
return sdk.ErrUnauthorized("invalid number of transaction signers")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSignature(
|
|
||||||
sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address,
|
|
||||||
sig auth.StdSignature, ak auth.AccountKeeper,
|
|
||||||
) (acc auth.Account, sdkErr sdk.Error) {
|
|
||||||
|
|
||||||
chainID := sdkCtx.ChainID()
|
|
||||||
|
|
||||||
acc = ak.GetAccount(sdkCtx, signer.Bytes())
|
|
||||||
if acc == nil {
|
|
||||||
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
|
|
||||||
}
|
|
||||||
|
|
||||||
accNum := acc.GetAccountNumber()
|
|
||||||
if accNum != sig.AccountNumber {
|
|
||||||
return nil, sdk.ErrInvalidSequence(
|
|
||||||
fmt.Sprintf("invalid account number; got %d, expected %d", sig.AccountNumber, accNum))
|
|
||||||
}
|
|
||||||
|
|
||||||
accSeq := acc.GetSequence()
|
|
||||||
if accSeq != sig.Sequence {
|
|
||||||
return nil, sdk.ErrInvalidSequence(
|
|
||||||
fmt.Sprintf("invalid account sequence; got %d, expected %d", sig.Sequence, accSeq))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := acc.SetSequence(accSeq + 1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, sdk.ErrInternal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
signBytes := types.GetStdTxSignBytes(chainID, accNum, accSeq, stdTx.Fee, stdTx.GetMsgs(), stdTx.Memo)
|
|
||||||
|
|
||||||
// consume gas for signature verification
|
|
||||||
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante signature verification")
|
|
||||||
|
|
||||||
if err := types.ValidateSigner(signBytes, sig.Signature, signer); err != nil {
|
|
||||||
return nil, sdk.ErrUnauthorized(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
@ -66,8 +66,10 @@ func init() {
|
|||||||
func newTestCodec() *codec.Codec {
|
func newTestCodec() *codec.Codec {
|
||||||
cdc := codec.New()
|
cdc := codec.New()
|
||||||
|
|
||||||
|
evmtypes.RegisterCodec(cdc)
|
||||||
types.RegisterCodec(cdc)
|
types.RegisterCodec(cdc)
|
||||||
auth.RegisterCodec(cdc)
|
auth.RegisterCodec(cdc)
|
||||||
|
sdk.RegisterCodec(cdc)
|
||||||
codec.RegisterCrypto(cdc)
|
codec.RegisterCrypto(cdc)
|
||||||
|
|
||||||
return cdc
|
return cdc
|
||||||
@ -139,7 +141,10 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
|
|||||||
|
|
||||||
// persist multi-store root state
|
// persist multi-store root state
|
||||||
commitID := cms.Commit()
|
commitID := cms.Commit()
|
||||||
require.Equal(t, "12D4DB63083D5B01824A35BB70BF671686D60532", fmt.Sprintf("%X", commitID.Hash))
|
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()))
|
||||||
@ -168,8 +173,7 @@ func TestImportBlocks(t *testing.T) {
|
|||||||
cdc := newTestCodec()
|
cdc := newTestCodec()
|
||||||
cms := store.NewCommitMultiStore(db)
|
cms := store.NewCommitMultiStore(db)
|
||||||
|
|
||||||
// create account mapper
|
ak := auth.NewAccountKeeper(
|
||||||
am := auth.NewAccountKeeper(
|
|
||||||
cdc,
|
cdc,
|
||||||
accKey,
|
accKey,
|
||||||
types.ProtoBaseAccount,
|
types.ProtoBaseAccount,
|
||||||
@ -188,7 +192,7 @@ func TestImportBlocks(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// set and test genesis block
|
// set and test genesis block
|
||||||
createAndTestGenesis(t, cms, am)
|
createAndTestGenesis(t, cms, ak)
|
||||||
|
|
||||||
// open blockchain export file
|
// open blockchain export file
|
||||||
blockchainInput, err := os.Open(flagBlockchain)
|
blockchainInput, err := os.Open(flagBlockchain)
|
||||||
@ -230,7 +234,7 @@ func TestImportBlocks(t *testing.T) {
|
|||||||
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
|
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
|
||||||
ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
|
ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
|
||||||
|
|
||||||
stateDB := createStateDB(t, ctx, am)
|
stateDB := createStateDB(t, ctx, ak)
|
||||||
|
|
||||||
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
|
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||||
ethmisc.ApplyDAOHardFork(stateDB)
|
ethmisc.ApplyDAOHardFork(stateDB)
|
||||||
|
@ -2,7 +2,6 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var typesCodec = codec.New()
|
var typesCodec = codec.New()
|
||||||
@ -14,7 +13,5 @@ func init() {
|
|||||||
// RegisterCodec registers all the necessary types with amino for the given
|
// RegisterCodec registers all the necessary types with amino for the given
|
||||||
// codec.
|
// codec.
|
||||||
func RegisterCodec(cdc *codec.Codec) {
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
sdk.RegisterCodec(cdc)
|
|
||||||
cdc.RegisterConcrete(&Transaction{}, "types/Transaction", nil)
|
|
||||||
cdc.RegisterConcrete(&Account{}, "types/Account", nil)
|
cdc.RegisterConcrete(&Account{}, "types/Account", nil)
|
||||||
}
|
}
|
@ -4,25 +4,34 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ethermint error codes
|
||||||
const (
|
const (
|
||||||
// DefaultCodespace reserves a Codespace for Ethermint, as 0 and 1 are
|
// DefaultCodespace reserves a Codespace for Ethermint.
|
||||||
// reserved by SDK.
|
DefaultCodespace sdk.CodespaceType = "ethermint"
|
||||||
DefaultCodespace sdk.CodespaceType = 2
|
|
||||||
|
|
||||||
// CodeInvalidValue reserves the CodeInvalidValue with first non-OK
|
CodeInvalidValue sdk.CodeType = 1
|
||||||
// codetype.
|
CodeInvalidAccountNumber sdk.CodeType = 2
|
||||||
CodeInvalidValue sdk.CodeType = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func codeToDefaultMsg(code sdk.CodeType) string {
|
func codeToDefaultMsg(code sdk.CodeType) string {
|
||||||
switch code {
|
switch code {
|
||||||
|
case CodeInvalidValue:
|
||||||
|
return "invalid value"
|
||||||
|
case CodeInvalidAccountNumber:
|
||||||
|
return "invalid account number"
|
||||||
default:
|
default:
|
||||||
return sdk.CodeToDefaultMsg(code)
|
return sdk.CodeToDefaultMsg(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrInvalidValue returns a standardized SDK error for a given codespace and
|
// ErrInvalidValue returns a standardized SDK error resulting from an invalid
|
||||||
// message.
|
// value.
|
||||||
func ErrInvalidValue(codespace sdk.CodespaceType, msg string) sdk.Error {
|
func ErrInvalidValue(msg string) sdk.Error {
|
||||||
return sdk.NewError(codespace, CodeInvalidValue, msg)
|
return sdk.NewError(DefaultCodespace, CodeInvalidValue, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrInvalidAccountNumber returns a standardized SDK error resulting from an
|
||||||
|
// invalid account number.
|
||||||
|
func ErrInvalidAccountNumber(msg string) sdk.Error {
|
||||||
|
return sdk.NewError(DefaultCodespace, CodeInvalidAccountNumber, msg)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
|
|
||||||
// Application multi-store keys
|
|
||||||
var (
|
|
||||||
StoreKeyAccount = sdk.NewKVStoreKey("acc")
|
|
||||||
StoreKeyStorage = sdk.NewKVStoreKey("contract_storage")
|
|
||||||
StoreKeyMain = sdk.NewKVStoreKey("main")
|
|
||||||
StoreKeyStake = sdk.NewKVStoreKey("stake")
|
|
||||||
StoreKeySlashing = sdk.NewKVStoreKey("slashing")
|
|
||||||
StoreKeyGov = sdk.NewKVStoreKey("gov")
|
|
||||||
StoreKeyFeeColl = sdk.NewKVStoreKey("fee")
|
|
||||||
StoreKeyParams = sdk.NewKVStoreKey("params")
|
|
||||||
StoreKeyTransParams = sdk.NewTransientStoreKey("transient_params")
|
|
||||||
)
|
|
@ -1,103 +0,0 @@
|
|||||||
// nolint
|
|
||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// test variables
|
|
||||||
var (
|
|
||||||
TestSDKAddr = GenerateEthAddress()
|
|
||||||
TestChainID = big.NewInt(3)
|
|
||||||
|
|
||||||
TestPrivKey1, _ = ethcrypto.GenerateKey()
|
|
||||||
TestPrivKey2, _ = ethcrypto.GenerateKey()
|
|
||||||
|
|
||||||
TestAddr1 = PrivKeyToEthAddress(TestPrivKey1)
|
|
||||||
TestAddr2 = PrivKeyToEthAddress(TestPrivKey2)
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewTestCodec() *codec.Codec {
|
|
||||||
cdc := codec.New()
|
|
||||||
|
|
||||||
RegisterCodec(cdc)
|
|
||||||
auth.RegisterCodec(cdc)
|
|
||||||
codec.RegisterCrypto(cdc)
|
|
||||||
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
|
|
||||||
|
|
||||||
return cdc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestStdFee() auth.StdFee {
|
|
||||||
return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestStdTx(
|
|
||||||
chainID *big.Int, msgs []sdk.Msg, accNums, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee,
|
|
||||||
) sdk.Tx {
|
|
||||||
|
|
||||||
sigs := make([]auth.StdSignature, len(pKeys))
|
|
||||||
|
|
||||||
for i, priv := range pKeys {
|
|
||||||
signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "")
|
|
||||||
|
|
||||||
sig, err := ethcrypto.Sign(signBytes, priv)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]}
|
|
||||||
}
|
|
||||||
|
|
||||||
return auth.NewStdTx(msgs, fee, sigs, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestGethTxs(
|
|
||||||
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
|
|
||||||
) []*ethtypes.Transaction {
|
|
||||||
|
|
||||||
txs := make([]*ethtypes.Transaction, len(pKeys))
|
|
||||||
|
|
||||||
for i, privKey := range pKeys {
|
|
||||||
ethTx := ethtypes.NewTransaction(
|
|
||||||
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
|
|
||||||
)
|
|
||||||
|
|
||||||
signer := ethtypes.NewEIP155Signer(chainID)
|
|
||||||
|
|
||||||
ethTx, err := ethtypes.SignTx(ethTx, signer, privKey)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
txs[i] = ethTx
|
|
||||||
}
|
|
||||||
|
|
||||||
return txs
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestEthTxs(
|
|
||||||
chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey,
|
|
||||||
) []*Transaction {
|
|
||||||
|
|
||||||
txs := make([]*Transaction, len(pKeys))
|
|
||||||
|
|
||||||
for i, privKey := range pKeys {
|
|
||||||
ethTx := NewTransaction(
|
|
||||||
uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{},
|
|
||||||
)
|
|
||||||
|
|
||||||
ethTx.Sign(chainID, privKey)
|
|
||||||
txs[i] = ethTx
|
|
||||||
}
|
|
||||||
|
|
||||||
return txs
|
|
||||||
}
|
|
367
types/tx.go
367
types/tx.go
@ -1,367 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
||||||
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Move to the EVM module
|
|
||||||
|
|
||||||
// message constants
|
|
||||||
const (
|
|
||||||
TypeTxEthereum = "Ethereum"
|
|
||||||
RouteTxEthereum = "evm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Ethereum transaction
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
var _ sdk.Tx = (*Transaction)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Transaction implements the Ethereum transaction structure as an exact
|
|
||||||
// replica. It implements the Cosmos sdk.Tx interface. Due to the private
|
|
||||||
// fields, it must be replicated here and cannot be embedded or used
|
|
||||||
// directly.
|
|
||||||
//
|
|
||||||
// Note: The transaction also implements the sdk.Msg interface to perform
|
|
||||||
// basic validation that is done in the BaseApp.
|
|
||||||
Transaction struct {
|
|
||||||
data TxData
|
|
||||||
|
|
||||||
// caches
|
|
||||||
hash atomic.Value
|
|
||||||
size atomic.Value
|
|
||||||
from atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxData implements the Ethereum transaction data structure as an exact
|
|
||||||
// copy. It is used solely as intended in Ethereum abiding by the protocol
|
|
||||||
// except for the payload field which may embed a Cosmos SDK transaction.
|
|
||||||
TxData struct {
|
|
||||||
AccountNonce uint64 `json:"nonce"`
|
|
||||||
Price *big.Int `json:"gasPrice"`
|
|
||||||
GasLimit uint64 `json:"gas"`
|
|
||||||
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
|
||||||
Amount *big.Int `json:"value"`
|
|
||||||
Payload []byte `json:"input"`
|
|
||||||
|
|
||||||
// signature values
|
|
||||||
V *big.Int `json:"v"`
|
|
||||||
R *big.Int `json:"r"`
|
|
||||||
S *big.Int `json:"s"`
|
|
||||||
|
|
||||||
// hash is only used when marshaling to JSON
|
|
||||||
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// sigCache is used to cache the derived sender and contains the signer used
|
|
||||||
// to derive it.
|
|
||||||
sigCache struct {
|
|
||||||
signer ethtypes.Signer
|
|
||||||
from ethcmn.Address
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewTransaction returns a reference to a new Ethereum transaction.
|
|
||||||
func NewTransaction(
|
|
||||||
nonce uint64, to ethcmn.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
||||||
) *Transaction {
|
|
||||||
|
|
||||||
return newTransaction(nonce, &to, amount, gasLimit, gasPrice, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContractCreation returns a reference to a new Ethereum transaction
|
|
||||||
// designated for contract creation.
|
|
||||||
func NewContractCreation(
|
|
||||||
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
||||||
) *Transaction {
|
|
||||||
|
|
||||||
return newTransaction(nonce, nil, amount, gasLimit, gasPrice, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransaction(
|
|
||||||
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
|
||||||
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
|
||||||
) *Transaction {
|
|
||||||
|
|
||||||
if len(payload) > 0 {
|
|
||||||
payload = ethcmn.CopyBytes(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
txData := TxData{
|
|
||||||
AccountNonce: nonce,
|
|
||||||
Recipient: to,
|
|
||||||
Payload: payload,
|
|
||||||
GasLimit: gasLimit,
|
|
||||||
Amount: new(big.Int),
|
|
||||||
Price: new(big.Int),
|
|
||||||
V: new(big.Int),
|
|
||||||
R: new(big.Int),
|
|
||||||
S: new(big.Int),
|
|
||||||
}
|
|
||||||
|
|
||||||
if amount != nil {
|
|
||||||
txData.Amount.Set(amount)
|
|
||||||
}
|
|
||||||
if gasPrice != nil {
|
|
||||||
txData.Price.Set(gasPrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Transaction{data: txData}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the Transaction's data.
|
|
||||||
func (tx Transaction) Data() TxData {
|
|
||||||
return tx.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRLP implements the rlp.Encoder interface.
|
|
||||||
func (tx *Transaction) EncodeRLP(w io.Writer) error {
|
|
||||||
return rlp.Encode(w, &tx.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeRLP implements the rlp.Decoder interface.
|
|
||||||
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
|
||||||
_, size, _ := s.Kind()
|
|
||||||
err := s.Decode(&tx.data)
|
|
||||||
if err == nil {
|
|
||||||
tx.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash hashes the RLP encoding of a transaction.
|
|
||||||
func (tx *Transaction) Hash() ethcmn.Hash {
|
|
||||||
if hash := tx.hash.Load(); hash != nil {
|
|
||||||
return hash.(ethcmn.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := rlpHash(tx)
|
|
||||||
tx.hash.Store(v)
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SigHash returns the RLP hash of a transaction with a given chainID used for
|
|
||||||
// signing.
|
|
||||||
func (tx Transaction) SigHash(chainID *big.Int) ethcmn.Hash {
|
|
||||||
return rlpHash([]interface{}{
|
|
||||||
tx.data.AccountNonce,
|
|
||||||
tx.data.Price,
|
|
||||||
tx.data.GasLimit,
|
|
||||||
tx.data.Recipient,
|
|
||||||
tx.data.Amount,
|
|
||||||
tx.data.Payload,
|
|
||||||
chainID, uint(0), uint(0),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
|
||||||
// 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
|
|
||||||
// fields of the Transaction's Signature.
|
|
||||||
func (tx *Transaction) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
|
||||||
txHash := tx.SigHash(chainID)
|
|
||||||
|
|
||||||
sig, err := ethcrypto.Sign(txHash[:], priv)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sig) != 65 {
|
|
||||||
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
|
|
||||||
}
|
|
||||||
|
|
||||||
r := new(big.Int).SetBytes(sig[:32])
|
|
||||||
s := new(big.Int).SetBytes(sig[32:64])
|
|
||||||
|
|
||||||
var v *big.Int
|
|
||||||
if chainID.Sign() == 0 {
|
|
||||||
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
|
||||||
} else {
|
|
||||||
v = big.NewInt(int64(sig[64] + 35))
|
|
||||||
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
|
||||||
v.Add(v, chainIDMul)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.data.V = v
|
|
||||||
tx.data.R = r
|
|
||||||
tx.data.S = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
|
||||||
signer := ethtypes.NewEIP155Signer(chainID)
|
|
||||||
|
|
||||||
if sc := tx.from.Load(); sc != nil {
|
|
||||||
sigCache := sc.(sigCache)
|
|
||||||
// If the signer used to derive from in a previous
|
|
||||||
// call is not the same as used current, invalidate
|
|
||||||
// the cache.
|
|
||||||
if sigCache.signer.Equal(signer) {
|
|
||||||
return sigCache.from, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not allow recovery for transactions with an unprotected chainID
|
|
||||||
if chainID.Sign() == 0 {
|
|
||||||
return ethcmn.Address{}, errors.New("invalid chainID")
|
|
||||||
}
|
|
||||||
|
|
||||||
txHash := tx.SigHash(chainID)
|
|
||||||
sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID)
|
|
||||||
|
|
||||||
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
|
|
||||||
if err != nil {
|
|
||||||
return ethcmn.Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var addr ethcmn.Address
|
|
||||||
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
|
||||||
|
|
||||||
tx.from.Store(sigCache{signer: signer, from: addr})
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type implements the sdk.Msg interface. It returns the type of the
|
|
||||||
// Transaction.
|
|
||||||
func (tx Transaction) Type() string { return TypeTxEthereum }
|
|
||||||
|
|
||||||
// Route implements the sdk.Msg interface. It returns the route of the
|
|
||||||
// Transaction.
|
|
||||||
func (tx Transaction) Route() string { return RouteTxEthereum }
|
|
||||||
|
|
||||||
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
|
||||||
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
|
||||||
func (tx Transaction) ValidateBasic() sdk.Error {
|
|
||||||
if tx.data.Price.Sign() != 1 {
|
|
||||||
return ErrInvalidValue(DefaultCodespace, "price must be positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tx.data.Amount.Sign() != 1 {
|
|
||||||
return ErrInvalidValue(DefaultCodespace, "amount must be positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignBytes performs a no-op and should not be used. It implements the
|
|
||||||
// sdk.Msg Interface
|
|
||||||
func (tx Transaction) GetSignBytes() (sigBytes []byte) { return }
|
|
||||||
|
|
||||||
// GetSigners performs a no-op and should not be used. It implements the
|
|
||||||
// sdk.Msg Interface
|
|
||||||
//
|
|
||||||
// CONTRACT: The transaction must already be signed.
|
|
||||||
func (tx Transaction) GetSigners() (signers []sdk.AccAddress) { return }
|
|
||||||
|
|
||||||
// GetMsgs returns a single message containing the Transaction itself. It
|
|
||||||
// implements the Cosmos sdk.Tx interface.
|
|
||||||
func (tx Transaction) GetMsgs() []sdk.Msg {
|
|
||||||
return []sdk.Msg{tx}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasEmbeddedTx returns a boolean reflecting if the transaction contains an
|
|
||||||
// SDK transaction or not based on the recipient address.
|
|
||||||
func (tx Transaction) hasEmbeddedTx(addr ethcmn.Address) bool {
|
|
||||||
return bytes.Equal(tx.data.Recipient.Bytes(), addr.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEmbeddedTx returns the embedded SDK transaction from an Ethereum
|
|
||||||
// transaction. It returns an error if decoding the inner transaction fails.
|
|
||||||
//
|
|
||||||
// CONTRACT: The payload field of an Ethereum transaction must contain a valid
|
|
||||||
// encoded SDK transaction.
|
|
||||||
func (tx Transaction) GetEmbeddedTx(codec *codec.Codec) (sdk.Tx, sdk.Error) {
|
|
||||||
var etx sdk.Tx
|
|
||||||
|
|
||||||
err := codec.UnmarshalBinary(tx.data.Payload, &etx)
|
|
||||||
if err != nil {
|
|
||||||
return etx, sdk.ErrTxDecode("failed to decode embedded transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
return etx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Utilities
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an
|
|
||||||
// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or
|
|
||||||
// returning an error if decoding fails.
|
|
||||||
func TxDecoder(codec *codec.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
|
|
||||||
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
||||||
var tx = Transaction{}
|
|
||||||
|
|
||||||
if len(txBytes) == 0 {
|
|
||||||
return nil, sdk.ErrTxDecode("transaction bytes are empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := rlp.DecodeBytes(txBytes, &tx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, sdk.ErrTxDecode("failed to decode transaction").TraceSDK(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the transaction is routed as an SDK transaction, decode and return
|
|
||||||
// the embedded SDK transaction.
|
|
||||||
if tx.hasEmbeddedTx(sdkAddress) {
|
|
||||||
etx, err := tx.GetEmbeddedTx(codec)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return etx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// recoverEthSig recovers a signature according to the Ethereum specification.
|
|
||||||
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
|
|
||||||
var v byte
|
|
||||||
|
|
||||||
r, s := R.Bytes(), S.Bytes()
|
|
||||||
sig := make([]byte, 65)
|
|
||||||
|
|
||||||
copy(sig[32-len(r):32], r)
|
|
||||||
copy(sig[64-len(s):64], s)
|
|
||||||
|
|
||||||
if chainID.Sign() == 0 {
|
|
||||||
v = byte(Vb.Uint64() - 27)
|
|
||||||
} else {
|
|
||||||
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
|
||||||
V := new(big.Int).Sub(Vb, chainIDMul)
|
|
||||||
|
|
||||||
v = byte(V.Uint64() - 35)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig[64] = v
|
|
||||||
return sig
|
|
||||||
}
|
|
||||||
|
|
||||||
func rlpHash(x interface{}) (hash ethcmn.Hash) {
|
|
||||||
hasher := ethsha.NewKeccak256()
|
|
||||||
|
|
||||||
rlp.Encode(hasher, x)
|
|
||||||
hasher.Sum(hash[:0])
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
140
types/tx_test.go
140
types/tx_test.go
@ -1,140 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
|
||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTransactionRLPEncode(t *testing.T) {
|
|
||||||
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
|
||||||
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
|
||||||
|
|
||||||
txRLP, err := rlp.EncodeToBytes(txs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, gtxRLP, txRLP)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransactionRLPDecode(t *testing.T) {
|
|
||||||
txs := NewTestEthTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
|
||||||
gtxs := NewTestGethTxs(TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1})
|
|
||||||
|
|
||||||
txRLP, err := rlp.EncodeToBytes(txs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
gtxRLP, err := rlp.EncodeToBytes(gtxs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var (
|
|
||||||
decodedTx Transaction
|
|
||||||
decodedGtx ethtypes.Transaction
|
|
||||||
)
|
|
||||||
|
|
||||||
err = rlp.DecodeBytes(txRLP, &decodedTx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = rlp.DecodeBytes(gtxRLP, &decodedGtx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, decodedGtx.Hash(), decodedTx.Hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidation(t *testing.T) {
|
|
||||||
ethTxs := NewTestEthTxs(
|
|
||||||
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
|
||||||
)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
msg sdk.Msg
|
|
||||||
mutate func(sdk.Msg) sdk.Msg
|
|
||||||
expectedErr bool
|
|
||||||
}{
|
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg { return msg }, false},
|
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
|
||||||
tx := msg.(*Transaction)
|
|
||||||
tx.data.Price = big.NewInt(-1)
|
|
||||||
return tx
|
|
||||||
}, true},
|
|
||||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
|
||||||
tx := msg.(*Transaction)
|
|
||||||
tx.data.Amount = big.NewInt(-1)
|
|
||||||
return tx
|
|
||||||
}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range testCases {
|
|
||||||
msg := tc.mutate(tc.msg)
|
|
||||||
err := msg.ValidateBasic()
|
|
||||||
|
|
||||||
if tc.expectedErr {
|
|
||||||
require.NotEqual(t, sdk.CodeOK, err.Code(), fmt.Sprintf("expected error: test case #%d", i))
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTransactionVerifySig(t *testing.T) {
|
|
||||||
txs := NewTestEthTxs(
|
|
||||||
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
|
||||||
)
|
|
||||||
|
|
||||||
addr, err := txs[0].VerifySig(TestChainID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, TestAddr1, addr)
|
|
||||||
|
|
||||||
addr, err = txs[0].VerifySig(big.NewInt(100))
|
|
||||||
require.Error(t, err)
|
|
||||||
require.NotEqual(t, TestAddr1, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTxDecoder(t *testing.T) {
|
|
||||||
testCodec := NewTestCodec()
|
|
||||||
txDecoder := TxDecoder(testCodec, TestSDKAddr)
|
|
||||||
msgs := []sdk.Msg{sdk.NewTestMsg()}
|
|
||||||
|
|
||||||
// create a non-SDK Ethereum transaction
|
|
||||||
txs := NewTestEthTxs(
|
|
||||||
TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1},
|
|
||||||
)
|
|
||||||
|
|
||||||
txBytes, err := rlp.EncodeToBytes(txs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// require the transaction to properly decode into a Transaction
|
|
||||||
decodedTx, err := txDecoder(txBytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.IsType(t, Transaction{}, decodedTx)
|
|
||||||
require.Equal(t, txs[0].data, (decodedTx.(Transaction)).data)
|
|
||||||
|
|
||||||
// create a SDK (auth.StdTx) transaction and encode
|
|
||||||
stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee())
|
|
||||||
payload := testCodec.MustMarshalBinary(stdTx)
|
|
||||||
tx := NewTransaction(0, TestSDKAddr, big.NewInt(10), 1000, big.NewInt(100), payload)
|
|
||||||
|
|
||||||
txBytes, err = rlp.EncodeToBytes(tx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// require the transaction to properly decode into a Transaction
|
|
||||||
decodedTx, err = txDecoder(txBytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.IsType(t, auth.StdTx{}, decodedTx)
|
|
||||||
require.Equal(t, stdTx, decodedTx)
|
|
||||||
|
|
||||||
// require the decoding to fail when no transaction bytes are given
|
|
||||||
decodedTx, err = txDecoder([]byte{})
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, decodedTx)
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidateSigner(t *testing.T) {
|
|
||||||
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(TestAddr1.Bytes()))}
|
|
||||||
|
|
||||||
// create message signing structure and bytes
|
|
||||||
signBytes := GetStdTxSignBytes(TestChainID.String(), 0, 0, NewTestStdFee(), msgs, "")
|
|
||||||
|
|
||||||
// require signing not to fail
|
|
||||||
sig, err := ethcrypto.Sign(signBytes, TestPrivKey1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// require signature to be valid
|
|
||||||
err = ValidateSigner(signBytes, sig, TestAddr1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
sig, err = ethcrypto.Sign(signBytes, TestPrivKey2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// require signature to be invalid
|
|
||||||
err = ValidateSigner(signBytes, sig, TestAddr1)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
// require invalid signature bytes return an error
|
|
||||||
err = ValidateSigner([]byte{}, sig, TestAddr2)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
1
x/evm/handler.go
Normal file
1
x/evm/handler.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package evm
|
19
x/evm/types/codec.go
Normal file
19
x/evm/types/codec.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
|
||||||
|
var msgCodec = codec.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cdc := codec.New()
|
||||||
|
|
||||||
|
RegisterCodec(cdc)
|
||||||
|
codec.RegisterCrypto(cdc)
|
||||||
|
|
||||||
|
msgCodec = cdc.Seal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register concrete types and interfaces on the given codec.
|
||||||
|
func RegisterCodec(cdc *codec.Codec) {
|
||||||
|
cdc.RegisterConcrete(MsgEthereumTx{}, "ethermint/MsgEthereumTx", nil)
|
||||||
|
}
|
286
x/evm/types/msg.go
Normal file
286
x/evm/types/msg.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/ethermint/types"
|
||||||
|
|
||||||
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ sdk.Msg = MsgEthereumTx{}
|
||||||
|
|
||||||
|
// message type and route constants
|
||||||
|
const (
|
||||||
|
TypeMsgEthereumTx = "ethereum_tx"
|
||||||
|
RouteMsgEthereumTx = "evm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
|
||||||
|
type (
|
||||||
|
MsgEthereumTx struct {
|
||||||
|
Data TxData
|
||||||
|
|
||||||
|
// caches
|
||||||
|
hash atomic.Value
|
||||||
|
size atomic.Value
|
||||||
|
from atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxData implements the Ethereum transaction data structure. It is used
|
||||||
|
// solely as intended in Ethereum abiding by the protocol.
|
||||||
|
TxData struct {
|
||||||
|
AccountNonce uint64 `json:"nonce"`
|
||||||
|
Price *big.Int `json:"gasPrice"`
|
||||||
|
GasLimit uint64 `json:"gas"`
|
||||||
|
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
|
||||||
|
Amount *big.Int `json:"value"`
|
||||||
|
Payload []byte `json:"input"`
|
||||||
|
|
||||||
|
// signature values
|
||||||
|
V *big.Int `json:"v"`
|
||||||
|
R *big.Int `json:"r"`
|
||||||
|
S *big.Int `json:"s"`
|
||||||
|
|
||||||
|
// hash is only used when marshaling to JSON
|
||||||
|
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sigCache is used to cache the derived sender and contains the signer used
|
||||||
|
// to derive it.
|
||||||
|
sigCache struct {
|
||||||
|
signer ethtypes.Signer
|
||||||
|
from ethcmn.Address
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMsgEthereumTx returns a reference to a new Ethereum transaction message.
|
||||||
|
func NewMsgEthereumTx(
|
||||||
|
nonce uint64, to ethcmn.Address, amount *big.Int,
|
||||||
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
|
) *MsgEthereumTx {
|
||||||
|
|
||||||
|
return newMsgEthereumTx(nonce, &to, amount, gasLimit, gasPrice, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMsgEthereumTxContract returns a reference to a new Ethereum transaction
|
||||||
|
// message designated for contract creation.
|
||||||
|
func NewMsgEthereumTxContract(
|
||||||
|
nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
|
) *MsgEthereumTx {
|
||||||
|
|
||||||
|
return newMsgEthereumTx(nonce, nil, amount, gasLimit, gasPrice, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMsgEthereumTx(
|
||||||
|
nonce uint64, to *ethcmn.Address, amount *big.Int,
|
||||||
|
gasLimit uint64, gasPrice *big.Int, payload []byte,
|
||||||
|
) *MsgEthereumTx {
|
||||||
|
|
||||||
|
if len(payload) > 0 {
|
||||||
|
payload = ethcmn.CopyBytes(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
txData := TxData{
|
||||||
|
AccountNonce: nonce,
|
||||||
|
Recipient: to,
|
||||||
|
Payload: payload,
|
||||||
|
GasLimit: gasLimit,
|
||||||
|
Amount: new(big.Int),
|
||||||
|
Price: new(big.Int),
|
||||||
|
V: new(big.Int),
|
||||||
|
R: new(big.Int),
|
||||||
|
S: new(big.Int),
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount != nil {
|
||||||
|
txData.Amount.Set(amount)
|
||||||
|
}
|
||||||
|
if gasPrice != nil {
|
||||||
|
txData.Price.Set(gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MsgEthereumTx{Data: txData}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route returns the route value of an MsgEthereumTx.
|
||||||
|
func (msg MsgEthereumTx) Route() string { return RouteMsgEthereumTx }
|
||||||
|
|
||||||
|
// Type returns the type value of an MsgEthereumTx.
|
||||||
|
func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
|
||||||
|
|
||||||
|
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
|
||||||
|
// checks of a Transaction. If returns an sdk.Error if validation fails.
|
||||||
|
func (msg MsgEthereumTx) ValidateBasic() sdk.Error {
|
||||||
|
if msg.Data.Price.Sign() != 1 {
|
||||||
|
return types.ErrInvalidValue("price must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Data.Amount.Sign() != 1 {
|
||||||
|
return types.ErrInvalidValue("amount must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSigners returns the expected signers for an Ethereum transaction message.
|
||||||
|
// 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
|
||||||
|
// from the signature. Use 'VerifySig' instead.
|
||||||
|
func (msg MsgEthereumTx) GetSigners() []sdk.AccAddress {
|
||||||
|
panic("must use 'VerifySig' with a chain ID to get the signer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignBytes returns the Amino bytes of an Ethereum transaction message used
|
||||||
|
// for signing.
|
||||||
|
//
|
||||||
|
// NOTE: This method cannot be used as a chain ID is needed to create valid bytes
|
||||||
|
// to sign over. Use 'RLPSignBytes' instead.
|
||||||
|
func (msg MsgEthereumTx) GetSignBytes() []byte {
|
||||||
|
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
|
||||||
|
// given chainID used for signing.
|
||||||
|
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
|
||||||
|
return rlpHash([]interface{}{
|
||||||
|
msg.Data.AccountNonce,
|
||||||
|
msg.Data.Price,
|
||||||
|
msg.Data.GasLimit,
|
||||||
|
msg.Data.Recipient,
|
||||||
|
msg.Data.Amount,
|
||||||
|
msg.Data.Payload,
|
||||||
|
chainID, uint(0), uint(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeRLP implements the rlp.Encoder interface.
|
||||||
|
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
|
||||||
|
return rlp.Encode(w, &msg.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeRLP implements the rlp.Decoder interface.
|
||||||
|
func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
|
||||||
|
_, size, _ := s.Kind()
|
||||||
|
|
||||||
|
err := s.Decode(&msg.Data)
|
||||||
|
if err == nil {
|
||||||
|
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash hashes the RLP encoding of a transaction.
|
||||||
|
func (msg *MsgEthereumTx) Hash() ethcmn.Hash {
|
||||||
|
if hash := msg.hash.Load(); hash != nil {
|
||||||
|
return hash.(ethcmn.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := rlpHash(msg)
|
||||||
|
msg.hash.Store(v)
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
|
||||||
|
// 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
|
||||||
|
// fields of the Transaction's Signature.
|
||||||
|
func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) {
|
||||||
|
txHash := msg.RLPSignBytes(chainID)
|
||||||
|
|
||||||
|
sig, err := ethcrypto.Sign(txHash[:], priv)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != 65 {
|
||||||
|
panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(big.Int).SetBytes(sig[:32])
|
||||||
|
s := new(big.Int).SetBytes(sig[32:64])
|
||||||
|
|
||||||
|
var v *big.Int
|
||||||
|
|
||||||
|
if chainID.Sign() == 0 {
|
||||||
|
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
||||||
|
} else {
|
||||||
|
v = big.NewInt(int64(sig[64] + 35))
|
||||||
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||||
|
|
||||||
|
v.Add(v, chainIDMul)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Data.V = v
|
||||||
|
msg.Data.R = r
|
||||||
|
msg.Data.S = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (msg MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
|
||||||
|
signer := ethtypes.NewEIP155Signer(chainID)
|
||||||
|
|
||||||
|
if sc := msg.from.Load(); sc != nil {
|
||||||
|
sigCache := sc.(sigCache)
|
||||||
|
// If the signer used to derive from in a previous call is not the same as
|
||||||
|
// used current, invalidate the cache.
|
||||||
|
if sigCache.signer.Equal(signer) {
|
||||||
|
return sigCache.from, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not allow recovery for transactions with an unprotected chainID
|
||||||
|
if chainID.Sign() == 0 {
|
||||||
|
return ethcmn.Address{}, errors.New("invalid chainID")
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash := msg.RLPSignBytes(chainID)
|
||||||
|
sig := recoverEthSig(msg.Data.R, msg.Data.S, msg.Data.V, chainID)
|
||||||
|
|
||||||
|
pub, err := ethcrypto.Ecrecover(txHash[:], sig)
|
||||||
|
if err != nil {
|
||||||
|
return ethcmn.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr ethcmn.Address
|
||||||
|
copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:])
|
||||||
|
|
||||||
|
msg.from.Store(sigCache{signer: signer, from: addr})
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// recoverEthSig recovers a signature according to the Ethereum specification.
|
||||||
|
func recoverEthSig(R, S, Vb, chainID *big.Int) []byte {
|
||||||
|
var v byte
|
||||||
|
|
||||||
|
r, s := R.Bytes(), S.Bytes()
|
||||||
|
sig := make([]byte, 65)
|
||||||
|
|
||||||
|
copy(sig[32-len(r):32], r)
|
||||||
|
copy(sig[64-len(s):64], s)
|
||||||
|
|
||||||
|
if chainID.Sign() == 0 {
|
||||||
|
v = byte(Vb.Uint64() - 27)
|
||||||
|
} else {
|
||||||
|
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||||
|
V := new(big.Int).Sub(Vb, chainIDMul)
|
||||||
|
|
||||||
|
v = byte(V.Uint64() - 35)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig[64] = v
|
||||||
|
return sig
|
||||||
|
}
|
123
x/evm/types/msg_test.go
Normal file
123
x/evm/types/msg_test.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMsgEthereumTx(t *testing.T) {
|
||||||
|
addr := GenerateEthAddress()
|
||||||
|
|
||||||
|
msg1 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
require.NotNil(t, msg1)
|
||||||
|
require.Equal(t, *msg1.Data.Recipient, addr)
|
||||||
|
|
||||||
|
msg2 := NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
|
||||||
|
require.NotNil(t, msg2)
|
||||||
|
require.Nil(t, msg2.Data.Recipient)
|
||||||
|
|
||||||
|
msg3 := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
require.Equal(t, msg3.Route(), RouteMsgEthereumTx)
|
||||||
|
require.Equal(t, msg3.Type(), TypeMsgEthereumTx)
|
||||||
|
require.Panics(t, func() { msg3.GetSigners() })
|
||||||
|
require.Panics(t, func() { msg3.GetSignBytes() })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxValidation(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
nonce uint64
|
||||||
|
to ethcmn.Address
|
||||||
|
amount *big.Int
|
||||||
|
gasLimit uint64
|
||||||
|
gasPrice *big.Int
|
||||||
|
payload []byte
|
||||||
|
expectPass bool
|
||||||
|
}{
|
||||||
|
{amount: big.NewInt(100), gasPrice: big.NewInt(100000), expectPass: true},
|
||||||
|
{amount: big.NewInt(-1), gasPrice: big.NewInt(100000), expectPass: false},
|
||||||
|
{amount: big.NewInt(100), gasPrice: big.NewInt(-1), expectPass: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
msg := NewMsgEthereumTx(tc.nonce, tc.to, tc.amount, tc.gasLimit, tc.gasPrice, tc.payload)
|
||||||
|
|
||||||
|
if tc.expectPass {
|
||||||
|
require.Nil(t, msg.ValidateBasic(), "test: %v", i)
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, msg.ValidateBasic(), "test: %v", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxRLPSignBytes(t *testing.T) {
|
||||||
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
|
chainID := big.NewInt(3)
|
||||||
|
|
||||||
|
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
hash := msg.RLPSignBytes(chainID)
|
||||||
|
require.Equal(t, "5BD30E35AD27449390B14C91E6BCFDCAADF8FE44EF33680E3BC200FC0DC083C7", fmt.Sprintf("%X", hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxRLPEncode(t *testing.T) {
|
||||||
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
|
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
|
raw, err := rlp.EncodeToBytes(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080"), raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxRLPDecode(t *testing.T) {
|
||||||
|
var msg MsgEthereumTx
|
||||||
|
|
||||||
|
raw := ethcmn.FromHex("E48080830186A0940000000000000000746573745F61646472657373808474657374808080")
|
||||||
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
|
expectedMsg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
|
err := rlp.Decode(bytes.NewReader(raw), &msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedMsg.Data, msg.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxHash(t *testing.T) {
|
||||||
|
addr := ethcmn.BytesToAddress([]byte("test_address"))
|
||||||
|
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
|
hash := msg.Hash()
|
||||||
|
require.Equal(t, "E2AA2E68E7586AE9700F1D3D643330866B6AC2B6CA4C804F7C85ECB11D0B0B29", fmt.Sprintf("%X", hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxSig(t *testing.T) {
|
||||||
|
priv, _ := ethcrypto.GenerateKey()
|
||||||
|
addr := PrivKeyToEthAddress(priv)
|
||||||
|
|
||||||
|
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
chainID := big.NewInt(3)
|
||||||
|
|
||||||
|
msg.Sign(chainID, priv)
|
||||||
|
|
||||||
|
resultAddr, err := msg.VerifySig(chainID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, addr, resultAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMsgEthereumTxAmino(t *testing.T) {
|
||||||
|
addr := GenerateEthAddress()
|
||||||
|
msg := NewMsgEthereumTx(0, addr, nil, 100000, nil, []byte("test"))
|
||||||
|
|
||||||
|
raw, err := msgCodec.MarshalBinaryBare(msg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var msg2 MsgEthereumTx
|
||||||
|
|
||||||
|
err = msgCodec.UnmarshalBinaryBare(raw, &msg2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, msg.Data, msg2.Data)
|
||||||
|
}
|
@ -2,17 +2,21 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
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"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
||||||
"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 := ethcrypto.GenerateKey()
|
||||||
@ -23,11 +27,6 @@ func GenerateEthAddress() ethcmn.Address {
|
|||||||
return PrivKeyToEthAddress(priv)
|
return PrivKeyToEthAddress(priv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
|
|
||||||
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
|
|
||||||
return ethcrypto.PubkeyToAddress(p.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
|
||||||
// which a signature and signer is given. An error is returned if address
|
// which a signature and signer is given. An error is returned if address
|
||||||
// derived from the signature and bytes signed does not match the given signer.
|
// derived from the signature and bytes signed does not match the given signer.
|
||||||
@ -43,10 +42,11 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStdTxSignBytes returns the signature bytes for an auth.StdTx transaction
|
func rlpHash(x interface{}) (hash ethcmn.Hash) {
|
||||||
// that is compatible with Ethereum's signature mechanism.
|
hasher := ethsha.NewKeccak256()
|
||||||
func GetStdTxSignBytes(chainID string, accNum int64, seq int64, fee auth.StdFee, msgs []sdk.Msg, memo string) []byte {
|
|
||||||
signBytes := auth.StdSignBytes(chainID, accNum, seq, fee, msgs, "")
|
rlp.Encode(hasher, x)
|
||||||
hash := sha256.Sum256(signBytes)
|
hasher.Sum(hash[:0])
|
||||||
return hash[:]
|
|
||||||
|
return
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user