Merge pull request #451: Transaction Implementation
This commit is contained in:
parent
cc1e582d97
commit
fbf3137b85
110
Gopkg.lock
generated
110
Gopkg.lock
generated
@ -3,30 +3,29 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9881039571249d9ff92d4a3383a015c233a4c172f6b5a3d6d71e6c4bfdb70efc"
|
||||
digest = "1:fcdf62d2d7e43c2565d6f8707ab4eae54dac702ed4bafb194b85139f0508929f"
|
||||
name = "github.com/aristanetworks/goarista"
|
||||
packages = ["monotime"]
|
||||
pruneopts = "T"
|
||||
revision = "32f94db2e6faa2c7250286dfb4c7ad3dc0f3ead2"
|
||||
revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cafb561ce87d0eaa309ad6853380d437df3c1142561c5afa700311825aa38df1"
|
||||
digest = "1:d4d66abd43dbb9b5f5e6a176c5ed279c289f8db734904c047d95113a04aa2e60"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "T"
|
||||
revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898"
|
||||
revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:20d11bf9fd5c6d8f7bb393229fdd981db8b4350aa9a05f28856b64640851c9b8"
|
||||
digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533"
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
packages = ["bech32"]
|
||||
pruneopts = "T"
|
||||
revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b"
|
||||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:83f4d10d8cbc8174248ac57b9788ff94f9e3af1999e8708fc50f80b7b91949fb"
|
||||
digest = "1:36773b598dec105de46a87978ae14e64c8d2c45aa556b8e0ddfc62d6abc7c47e"
|
||||
name = "github.com/cosmos/cosmos-sdk"
|
||||
packages = [
|
||||
"baseapp",
|
||||
@ -37,11 +36,11 @@
|
||||
"x/auth",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "1a1373cc220e402397ad536aee6b8f5b068914c6"
|
||||
version = "v0.21.0"
|
||||
revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5"
|
||||
version = "v0.23.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3aa953edddec96fd00285789ccd4a31efaff0a2979a3e35b77f5c19d5eaa37f7"
|
||||
digest = "1:52f195ad0e20a92d8604c1ba3cd246c61644c03eaa454b5acd41be89841e0d10"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "T"
|
||||
@ -57,7 +56,7 @@
|
||||
revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:24ae9f4a9d6e2bed9aca667af245834c9a80c39d5ae32e3fc99a2ace91287047"
|
||||
digest = "1:3238a0c355a81640974751f7d3bab21bf91035165f75c2c457959425c0422a4b"
|
||||
name = "github.com/ethereum/go-ethereum"
|
||||
packages = [
|
||||
"common",
|
||||
@ -94,7 +93,7 @@
|
||||
version = "v1.8.11"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b18534450f89f7007960ff1804d63fb0cc6e7d1989446fcb05d77fb24afc51fc"
|
||||
digest = "1:0b9c3ad6c948d57a379da9c4e1cdd989b1c73ddc5ec8673f52a9539ce60a109b"
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = [
|
||||
"log",
|
||||
@ -122,7 +121,7 @@
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2a1db9bae44464f781d3637b67df38e896c6e1b9c902e27d24ee9037cb50f23b"
|
||||
digest = "1:da39f4a22829ca95e63566208e0ea42d6f055f41dff1b14fdab88d88f62df653"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"gogoproto",
|
||||
@ -133,11 +132,11 @@
|
||||
"types",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f3df7b8eccb559fa1bda8dae71fdb5f24da5e9aec2696e21f19e6d24062602f"
|
||||
digest = "1:832e17df5ff8bbe0e0693d2fb46c5e53f96c662ee804049ce3ab6557df74e3ab"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
@ -147,19 +146,18 @@
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:968462840e6d86b12990015ac6ab297c022ccde102953040724be1df0e9e6c96"
|
||||
digest = "1:6027b20c168728321bd99ad01f35118eded457b01c03e647a84833ab331f2f5b"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = [
|
||||
@ -179,7 +177,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:14f2079ea27e7c67ecdab4f35e774463abc4f9d1806b5d674c0594b52127ab1d"
|
||||
digest = "1:dc6b1a6801b3055e9bd3da4cd1e568606eb48118cc6f28e947783aa5d998ad74"
|
||||
name = "github.com/jmhodges/levigo"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
@ -210,15 +208,15 @@
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:540558c17f78ee4f056aa043cf3389c283b56754db79112a2d64172e80e685db"
|
||||
digest = "1:6cae6970d70fc5fe75bf83c48ee33e9c4c561a62d0b033254bee8dd5942b815a"
|
||||
name = "github.com/rs/cors"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
revision = "ca016a06a5753f8ba03029c0aa5e54afb1bf713f"
|
||||
version = "v1.4.0"
|
||||
revision = "3fb1b69b103a84de38a19c3c6ec073dd6caa4d3f"
|
||||
version = "v1.5.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:daab027a0bfb143afb503f7b63673bfa8d44f69ce9484c6d19b97957aadc1252"
|
||||
digest = "1:8be8b3743fc9795ec21bbd3e0fc28ff6234018e1a269b0a7064184be95ac13e0"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
@ -234,7 +232,7 @@
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c7f05297d9ad389d81e6d764388d97c4b6a64665eff9fd2550fbdd8545430b80"
|
||||
digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
@ -246,7 +244,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:9c39a878048f4a5468675b814fb7d2528d622f8c3612511ff0b5e2a48d451ad2"
|
||||
digest = "1:7d44c4d11eb65cfdc78c76040f37ef305b16474c019c98a8a7cf188fece2d574"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
@ -267,7 +265,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8403202d034640f399279a4f735faabefeb6ee64bbcb03c9c93be1d4c7230382"
|
||||
digest = "1:2b15c0442dc80b581ce7028b2e43029d2f3f985da43cb1d55f7bcdeca785bda0"
|
||||
name = "github.com/tendermint/ed25519"
|
||||
packages = [
|
||||
".",
|
||||
@ -278,7 +276,7 @@
|
||||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4431caadcd2cc6a245bf0f6f61884029f5cc70833a8cc458cd1ba4a578b18c71"
|
||||
digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245"
|
||||
name = "github.com/tendermint/go-amino"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
@ -286,7 +284,7 @@
|
||||
version = "0.10.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f11b64e85907d8909a820d0cd793dff19dc268432626411398aa0e26e8a61338"
|
||||
digest = "1:bf042d2f7d1252b9dcae8e694e2f0a9b5294cb357c086fd86dc540d2f32c9fdf"
|
||||
name = "github.com/tendermint/iavl"
|
||||
packages = ["."]
|
||||
pruneopts = "T"
|
||||
@ -294,13 +292,16 @@
|
||||
version = "v0.9.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f056bee4848f0fff6c92e6b99d4972b175d8a8eec9700df30b143b2e6cc48ea8"
|
||||
digest = "1:9f6704ae2aedbadf616e5850375c504909d46b6ea57d4679de2b7cbc715f08e1"
|
||||
name = "github.com/tendermint/tendermint"
|
||||
packages = [
|
||||
"abci/server",
|
||||
"abci/types",
|
||||
"crypto",
|
||||
"crypto/ed25519",
|
||||
"crypto/encoding/amino",
|
||||
"crypto/merkle",
|
||||
"crypto/secp256k1",
|
||||
"crypto/tmhash",
|
||||
"libs/bech32",
|
||||
"libs/common",
|
||||
@ -311,28 +312,19 @@
|
||||
"types",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "5ff65274b84ea905787a48512cc3124385bddf2f"
|
||||
version = "v0.22.2"
|
||||
revision = "d542d2c3945116697f60451e6a407082c41c3cc9"
|
||||
version = "v0.22.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2c4971d2da7bb27fa225a119dc96af2119dd096869c1228438a0b5fda5f6fe15"
|
||||
digest = "1:2cbe8758697d867fcebf73bcc69dff8e8abaa7fd65e5704e0744e522ccff4e6a"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"internal/subtle",
|
||||
"nacl/secretbox",
|
||||
"openpgp/armor",
|
||||
"openpgp/errors",
|
||||
"poly1305",
|
||||
"ripemd160",
|
||||
"salsa20/salsa",
|
||||
]
|
||||
packages = ["ripemd160"]
|
||||
pruneopts = "T"
|
||||
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
|
||||
revision = "f027049dab0ad238e394a753dba2d14753473a04"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:501f63fec0206818ab3dac086383bd2146841951c6bd6f3c4f72613f113f25fd"
|
||||
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
@ -345,10 +337,10 @@
|
||||
"websocket",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0"
|
||||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:24db346d9931fe01f1e9a02aba78ba22c1ecd55bf0f79dd10ba5169719cf002d"
|
||||
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
@ -371,14 +363,14 @@
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cfa1bbb9ee86ade0914bd5f8e8516386cf7d573957191ecb5163d8f6e023ca0c"
|
||||
digest = "1:8cfa91d1b7f6b66fa9b1a738a4bc1325837b861e63fb9a2919931d68871bb770"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "T"
|
||||
revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1faab7c2380bc84698a62531c4af8c9475fbc7b3b1b2696f2f94feff97c47a49"
|
||||
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
@ -390,9 +382,11 @@
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"internal/backoff",
|
||||
"internal/channelz",
|
||||
"internal/grpcrand",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
@ -406,8 +400,8 @@
|
||||
"transport",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "d11072e7ca9811b1100b80ca0269ac831f06d024"
|
||||
version = "v1.11.3"
|
||||
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8"
|
||||
version = "v1.13.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ccd10c863188cfe0d936fcfe6a055c95362e43af8e7039e33baade846928e74"
|
||||
@ -419,7 +413,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
digest = "1:35056a4c53d0b725735422545c3c11bdc9007da2fdb644fee96f3a6b7c42c69f"
|
||||
digest = "1:dae137be246befa42ce4b48c0feff2c5796b8a5027139a283f31a21173744410"
|
||||
name = "gopkg.in/karalabe/cookiejar.v2"
|
||||
packages = ["collections/prque"]
|
||||
pruneopts = "T"
|
||||
@ -441,6 +435,7 @@
|
||||
"github.com/cosmos/cosmos-sdk/store",
|
||||
"github.com/cosmos/cosmos-sdk/types",
|
||||
"github.com/cosmos/cosmos-sdk/wire",
|
||||
"github.com/cosmos/cosmos-sdk/x/auth",
|
||||
"github.com/ethereum/go-ethereum/common",
|
||||
"github.com/ethereum/go-ethereum/common/math",
|
||||
"github.com/ethereum/go-ethereum/consensus",
|
||||
@ -450,14 +445,19 @@
|
||||
"github.com/ethereum/go-ethereum/core/state",
|
||||
"github.com/ethereum/go-ethereum/core/types",
|
||||
"github.com/ethereum/go-ethereum/core/vm",
|
||||
"github.com/ethereum/go-ethereum/crypto",
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3",
|
||||
"github.com/ethereum/go-ethereum/ethdb",
|
||||
"github.com/ethereum/go-ethereum/params",
|
||||
"github.com/ethereum/go-ethereum/rlp",
|
||||
"github.com/ethereum/go-ethereum/rpc",
|
||||
"github.com/ethereum/go-ethereum/trie",
|
||||
"github.com/hashicorp/golang-lru",
|
||||
"github.com/pkg/errors",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/tendermint/tendermint/libs/common",
|
||||
"github.com/tendermint/tendermint/libs/db",
|
||||
"github.com/tendermint/tendermint/libs/log",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
13
Gopkg.toml
13
Gopkg.toml
@ -4,10 +4,11 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/cosmos/cosmos-sdk"
|
||||
version = "=0.21.0"
|
||||
version = "=0.23.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
@ -19,11 +20,19 @@
|
||||
|
||||
[[override]]
|
||||
name = "github.com/tendermint/tendermint"
|
||||
version = "=0.22.2"
|
||||
version = "=v0.22.8"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "=1.2.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "=0.8.0"
|
||||
|
||||
[[override]]
|
||||
name = "gopkg.in/fatih/set.v0"
|
||||
version = "=0.1.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
|
@ -2,38 +2,73 @@ package app
|
||||
|
||||
import (
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/cosmos/ethermint/handlers"
|
||||
"github.com/cosmos/ethermint/types"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethparams "github.com/ethereum/go-ethereum/params"
|
||||
|
||||
tmcmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
tmlog "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "Ethermint"
|
||||
)
|
||||
|
||||
// EthermintApp implements an extended ABCI application.
|
||||
type EthermintApp struct {
|
||||
*bam.BaseApp
|
||||
type (
|
||||
// EthermintApp implements an extended ABCI application. It is an application
|
||||
// that may process transactions through Ethereum's EVM running atop of
|
||||
// Tendermint consensus.
|
||||
EthermintApp struct {
|
||||
*bam.BaseApp
|
||||
|
||||
codec *wire.Codec
|
||||
sealed bool
|
||||
codec *wire.Codec
|
||||
sealed bool
|
||||
|
||||
// TODO: stores and keys
|
||||
accountKey *sdk.KVStoreKey
|
||||
accountMapper auth.AccountMapper
|
||||
// TODO: keys, stores, mappers, and keepers
|
||||
}
|
||||
|
||||
// TODO: keepers
|
||||
|
||||
// TODO: mappers
|
||||
}
|
||||
// Options is a function signature that provides the ability to modify
|
||||
// options of an EthermintApp during initialization.
|
||||
Options func(*EthermintApp)
|
||||
)
|
||||
|
||||
// NewEthermintApp returns a reference to a new initialized Ethermint
|
||||
// application.
|
||||
func NewEthermintApp(opts ...func(*EthermintApp)) *EthermintApp {
|
||||
app := &EthermintApp{}
|
||||
func NewEthermintApp(
|
||||
logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig,
|
||||
sdkAddr ethcmn.Address, opts ...Options,
|
||||
) *EthermintApp {
|
||||
|
||||
// TODO: implement constructor
|
||||
codec := CreateCodec()
|
||||
app := &EthermintApp{
|
||||
BaseApp: bam.NewBaseApp(appName, codec, logger, db),
|
||||
codec: codec,
|
||||
accountKey: sdk.NewKVStoreKey("accounts"),
|
||||
}
|
||||
app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount)
|
||||
|
||||
app.SetTxDecoder(types.TxDecoder(codec, sdkAddr))
|
||||
app.SetAnteHandler(handlers.AnteHandler(app.accountMapper))
|
||||
app.MountStoresIAVL(app.accountKey)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(app)
|
||||
}
|
||||
|
||||
err := app.LoadLatestVersion(app.accountKey)
|
||||
if err != nil {
|
||||
tmcmn.Exit(err.Error())
|
||||
}
|
||||
|
||||
app.seal()
|
||||
return app
|
||||
}
|
||||
@ -43,3 +78,13 @@ func NewEthermintApp(opts ...func(*EthermintApp)) *EthermintApp {
|
||||
func (app *EthermintApp) seal() {
|
||||
app.sealed = true
|
||||
}
|
||||
|
||||
// CreateCodec creates a new amino wire codec and registers all the necessary
|
||||
// structures and interfaces needed for the application.
|
||||
func CreateCodec() *wire.Codec {
|
||||
codec := wire.NewCodec()
|
||||
|
||||
// Register other modules, types, and messages...
|
||||
types.RegisterWire(codec)
|
||||
return codec
|
||||
}
|
||||
|
196
handlers/ante.go
Normal file
196
handlers/ante.go
Normal file
@ -0,0 +1,196 @@
|
||||
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"
|
||||
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
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, am auth.AccountMapper,
|
||||
) (newCtx sdk.Context, res sdk.Result, abort bool)
|
||||
|
||||
// AnteHandler handles Ethereum transactions and passes SDK transactions to the
|
||||
// embeddedAnteHandler if it's an Ethermint transaction. The ante handler gets
|
||||
// invoked after the BaseApp performs the runTx. At this point, the transaction
|
||||
// should be properly decoded via the TxDecoder and should be of a proper type,
|
||||
// Transaction or EmbeddedTx.
|
||||
func AnteHandler(am auth.AccountMapper) sdk.AnteHandler {
|
||||
return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
|
||||
var (
|
||||
gasLimit int64
|
||||
handler internalAnteHandler
|
||||
)
|
||||
|
||||
switch tx := tx.(type) {
|
||||
case types.Transaction:
|
||||
gasLimit = int64(tx.Data.GasLimit)
|
||||
handler = handleEthTx
|
||||
case types.EmbeddedTx:
|
||||
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, am)
|
||||
}
|
||||
}
|
||||
|
||||
// handleEthTx implements an ante handler for an Ethereum transaction. It
|
||||
// validates the signature and if valid returns an OK result.
|
||||
//
|
||||
// TODO: Do we need to do any further validation or account manipulation
|
||||
// (e.g. increment nonce)?
|
||||
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (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 {
|
||||
// TODO: ErrInternal may not be correct error to throw here?
|
||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
|
||||
}
|
||||
|
||||
// validate signature
|
||||
gethTx := ethTx.ConvertTx(chainID)
|
||||
signer := ethtypes.NewEIP155Signer(chainID)
|
||||
|
||||
_, err := signer.Sender(&gethTx)
|
||||
if err != nil {
|
||||
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
|
||||
}
|
||||
|
||||
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, am auth.AccountMapper) (sdk.Context, sdk.Result, bool) {
|
||||
etx, ok := tx.(types.EmbeddedTx)
|
||||
if !ok {
|
||||
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
|
||||
}
|
||||
|
||||
if err := validateEmbeddedTxBasic(etx); err != nil {
|
||||
return sdkCtx, err.Result(), true
|
||||
}
|
||||
|
||||
signerAddrs := etx.GetRequiredSigners()
|
||||
signerAccs := make([]auth.Account, len(signerAddrs))
|
||||
|
||||
// validate signatures
|
||||
for i, sig := range etx.Signatures {
|
||||
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
|
||||
|
||||
signerAcc, err := validateSignature(sdkCtx, etx, signer, sig, am)
|
||||
if err.Code() != sdk.CodeOK {
|
||||
return sdkCtx, err.Result(), false
|
||||
}
|
||||
|
||||
// TODO: Fees!
|
||||
|
||||
am.SetAccount(sdkCtx, signerAcc)
|
||||
signerAccs[i] = signerAcc
|
||||
}
|
||||
|
||||
newCtx := auth.WithSigners(sdkCtx, signerAccs)
|
||||
|
||||
return newCtx, sdk.Result{GasWanted: etx.Fee.Gas}, false
|
||||
}
|
||||
|
||||
// validateEmbeddedTxBasic validates an EmbeddedTx based on things that don't
|
||||
// depend on the context.
|
||||
func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) {
|
||||
sigs := etx.Signatures
|
||||
if len(sigs) == 0 {
|
||||
return sdk.ErrUnauthorized("transaction missing signatures")
|
||||
}
|
||||
|
||||
signerAddrs := etx.GetRequiredSigners()
|
||||
if len(sigs) != len(signerAddrs) {
|
||||
return sdk.ErrUnauthorized("invalid number of transaction signers")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSignature(
|
||||
sdkCtx sdk.Context, etx types.EmbeddedTx, signer ethcmn.Address,
|
||||
sig []byte, am auth.AccountMapper,
|
||||
) (acc auth.Account, sdkErr sdk.Error) {
|
||||
|
||||
chainID := sdkCtx.ChainID()
|
||||
|
||||
acc = am.GetAccount(sdkCtx, signer.Bytes())
|
||||
if acc == nil {
|
||||
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
|
||||
}
|
||||
|
||||
signEtx := types.EmbeddedTxSign{
|
||||
ChainID: chainID,
|
||||
AccountNumber: acc.GetAccountNumber(),
|
||||
Sequence: acc.GetSequence(),
|
||||
Messages: etx.Messages,
|
||||
Fee: etx.Fee,
|
||||
}
|
||||
|
||||
err := acc.SetSequence(signEtx.Sequence + 1)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrInternal(err.Error())
|
||||
}
|
||||
|
||||
signBytes, err := signEtx.Bytes()
|
||||
if err != nil {
|
||||
return nil, sdk.ErrInternal(err.Error())
|
||||
}
|
||||
|
||||
// consume gas for signature verification
|
||||
sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante verify")
|
||||
|
||||
if err := types.ValidateSigner(signBytes, sig, signer); err != nil {
|
||||
return nil, sdk.ErrUnauthorized(err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
28
types/errors.go
Normal file
28
types/errors.go
Normal file
@ -0,0 +1,28 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultCodespace reserves a Codespace for Ethermint, as 0 and 1 are
|
||||
// reserved by SDK.
|
||||
DefaultCodespace sdk.CodespaceType = 2
|
||||
|
||||
// CodeInvalidValue reserves the CodeInvalidValue with first non-OK
|
||||
// codetype.
|
||||
CodeInvalidValue sdk.CodeType = 1
|
||||
)
|
||||
|
||||
func codeToDefaultMsg(code sdk.CodeType) string {
|
||||
switch code {
|
||||
default:
|
||||
return sdk.CodeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidValue returns a standardized SDK error for a given codespace and
|
||||
// message.
|
||||
func ErrInvalidValue(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidValue, msg)
|
||||
}
|
441
types/tx.go
Normal file
441
types/tx.go
Normal file
@ -0,0 +1,441 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"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"
|
||||
ethsha "github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// TypeTxEthereum reflects an Ethereum Transaction type.
|
||||
TypeTxEthereum = "Ethereum"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Ethereum transaction
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type (
|
||||
// Transaction implements the Ethereum transaction structure as an exact
|
||||
// copy. 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 sdk.Int `json:"gasPrice"`
|
||||
GasLimit uint64 `json:"gas"`
|
||||
Recipient *ethcmn.Address `json:"to"` // nil means contract creation
|
||||
Amount sdk.Int `json:"value"`
|
||||
Payload []byte `json:"input"`
|
||||
Signature *EthSignature `json:"signature"`
|
||||
|
||||
// hash is only used when marshaling to JSON
|
||||
Hash *ethcmn.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
// EthSignature reflects an Ethereum signature. We wrap this in a structure
|
||||
// to support Amino serialization of transactions.
|
||||
EthSignature struct {
|
||||
v, r, s *big.Int
|
||||
}
|
||||
)
|
||||
|
||||
// NewEthSignature returns a new instantiated Ethereum signature.
|
||||
func NewEthSignature(v, r, s *big.Int) *EthSignature {
|
||||
return &EthSignature{v, r, s}
|
||||
}
|
||||
|
||||
func (es *EthSignature) sanitize() {
|
||||
if es.v == nil {
|
||||
es.v = new(big.Int)
|
||||
}
|
||||
if es.r == nil {
|
||||
es.r = new(big.Int)
|
||||
}
|
||||
if es.s == nil {
|
||||
es.s = new(big.Int)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalAmino defines a custom encoding scheme for a EthSignature.
|
||||
func (es EthSignature) MarshalAmino() ([3]string, error) {
|
||||
es.sanitize()
|
||||
return ethSigMarshalAmino(es)
|
||||
}
|
||||
|
||||
// UnmarshalAmino defines a custom decoding scheme for a EthSignature.
|
||||
func (es *EthSignature) UnmarshalAmino(raw [3]string) error {
|
||||
es.sanitize()
|
||||
return ethSigUnmarshalAmino(es, raw)
|
||||
}
|
||||
|
||||
// NewTransaction mimics ethereum's NewTransaction function. It returns a
|
||||
// reference to a new Ethereum Transaction.
|
||||
func NewTransaction(
|
||||
nonce uint64, to ethcmn.Address, amount sdk.Int,
|
||||
gasLimit uint64, gasPrice sdk.Int, payload []byte,
|
||||
) Transaction {
|
||||
|
||||
if len(payload) > 0 {
|
||||
payload = ethcmn.CopyBytes(payload)
|
||||
}
|
||||
|
||||
txData := TxData{
|
||||
Recipient: &to,
|
||||
AccountNonce: nonce,
|
||||
Payload: payload,
|
||||
GasLimit: gasLimit,
|
||||
Amount: amount,
|
||||
Price: gasPrice,
|
||||
Signature: NewEthSignature(new(big.Int), new(big.Int), new(big.Int)),
|
||||
}
|
||||
|
||||
return Transaction{Data: txData}
|
||||
}
|
||||
|
||||
// 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 sdk.Int, priv *ecdsa.PrivateKey) {
|
||||
h := rlpHash([]interface{}{
|
||||
tx.Data.AccountNonce,
|
||||
tx.Data.Price.BigInt(),
|
||||
tx.Data.GasLimit,
|
||||
tx.Data.Recipient,
|
||||
tx.Data.Amount.BigInt(),
|
||||
tx.Data.Payload,
|
||||
chainID.BigInt(), uint(0), uint(0),
|
||||
})
|
||||
|
||||
sig, err := ethcrypto.Sign(h[:], 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.BigInt(), big.NewInt(2))
|
||||
v.Add(v, chainIDMul)
|
||||
}
|
||||
|
||||
tx.Data.Signature.v = v
|
||||
tx.Data.Signature.r = r
|
||||
tx.Data.Signature.s = s
|
||||
}
|
||||
|
||||
// Type implements the sdk.Msg interface. It returns the type of the
|
||||
// Transaction.
|
||||
func (tx Transaction) Type() string {
|
||||
return TypeTxEthereum
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// ConvertTx attempts to converts a Transaction to a new Ethereum transaction
|
||||
// with the signature set. The signature if first recovered and then a new
|
||||
// Transaction is created with that signature. If setting the signature fails,
|
||||
// a panic will be triggered.
|
||||
func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction {
|
||||
gethTx := ethtypes.NewTransaction(
|
||||
tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(),
|
||||
tx.Data.GasLimit, tx.Data.Price.BigInt(), tx.Data.Payload,
|
||||
)
|
||||
|
||||
sig := recoverEthSig(tx.Data.Signature, chainID)
|
||||
signer := ethtypes.NewEIP155Signer(chainID)
|
||||
|
||||
gethTx, err := gethTx.WithSignature(signer, sig)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "failed to convert transaction with a given signature"))
|
||||
}
|
||||
|
||||
return *gethTx
|
||||
}
|
||||
|
||||
// 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 *wire.Codec) (EmbeddedTx, sdk.Error) {
|
||||
etx := EmbeddedTx{}
|
||||
|
||||
err := codec.UnmarshalBinary(tx.Data.Payload, &etx)
|
||||
if err != nil {
|
||||
return EmbeddedTx{}, sdk.ErrTxDecode("failed to encode embedded tx")
|
||||
}
|
||||
|
||||
return etx, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// embedded SDK transaction
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type (
|
||||
// EmbeddedTx implements an SDK transaction. It is to be encoded into the
|
||||
// payload field of an Ethereum transaction in order to route and handle SDK
|
||||
// transactions.
|
||||
EmbeddedTx struct {
|
||||
Messages []sdk.Msg `json:"messages"`
|
||||
Fee auth.StdFee `json:"fee"`
|
||||
Signatures [][]byte `json:"signatures"`
|
||||
}
|
||||
|
||||
// embeddedSignDoc implements a simple SignDoc for a EmbeddedTx signer to
|
||||
// sign over.
|
||||
embeddedSignDoc struct {
|
||||
ChainID string `json:"chainID"`
|
||||
AccountNumber int64 `json:"accountNumber"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
Messages []json.RawMessage `json:"messages"`
|
||||
Fee json.RawMessage `json:"fee"`
|
||||
}
|
||||
|
||||
// EmbeddedTxSign implements a structure for containing the information
|
||||
// necessary for building and signing an EmbeddedTx.
|
||||
EmbeddedTxSign struct {
|
||||
ChainID string
|
||||
AccountNumber int64
|
||||
Sequence int64
|
||||
Messages []sdk.Msg
|
||||
Fee auth.StdFee
|
||||
}
|
||||
)
|
||||
|
||||
// GetMsgs implements the sdk.Tx interface. It returns all the SDK transaction
|
||||
// messages.
|
||||
func (etx EmbeddedTx) GetMsgs() []sdk.Msg {
|
||||
return etx.Messages
|
||||
}
|
||||
|
||||
// GetRequiredSigners returns all the required signers of an SDK transaction
|
||||
// accumulated from messages. It returns them in a deterministic fashion given
|
||||
// a list of messages.
|
||||
func (etx EmbeddedTx) GetRequiredSigners() []sdk.AccAddress {
|
||||
seen := map[string]bool{}
|
||||
|
||||
var signers []sdk.AccAddress
|
||||
for _, msg := range etx.GetMsgs() {
|
||||
for _, addr := range msg.GetSigners() {
|
||||
if !seen[addr.String()] {
|
||||
signers = append(signers, sdk.AccAddress(addr))
|
||||
seen[addr.String()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return signers
|
||||
}
|
||||
|
||||
// Bytes returns the EmbeddedTxSign signature bytes for a signer to sign over.
|
||||
func (ets EmbeddedTxSign) Bytes() ([]byte, error) {
|
||||
sigBytes, err := EmbeddedSignBytes(ets.ChainID, ets.AccountNumber, ets.Sequence, ets.Messages, ets.Fee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(sigBytes)
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
// EmbeddedSignBytes creates signature bytes for a signer to sign an embedded
|
||||
// transaction. The signature bytes require a chainID and an account number.
|
||||
// The signature bytes are JSON encoded.
|
||||
func EmbeddedSignBytes(chainID string, accnum, sequence int64, msgs []sdk.Msg, fee auth.StdFee) ([]byte, error) {
|
||||
var msgsBytes []json.RawMessage
|
||||
for _, msg := range msgs {
|
||||
msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes()))
|
||||
}
|
||||
|
||||
signDoc := embeddedSignDoc{
|
||||
ChainID: chainID,
|
||||
AccountNumber: accnum,
|
||||
Sequence: sequence,
|
||||
Messages: msgsBytes,
|
||||
Fee: json.RawMessage(fee.Bytes()),
|
||||
}
|
||||
|
||||
bz, err := typesCodec.MarshalJSON(signDoc)
|
||||
if err != nil {
|
||||
errors.Wrap(err, "failed to JSON encode EmbeddedSignDoc")
|
||||
}
|
||||
|
||||
return bz, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes,
|
||||
// attempts to decode them into a Transaction or an EmbeddedTx or returning an
|
||||
// error if decoding fails.
|
||||
func TxDecoder(codec *wire.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("txBytes are empty")
|
||||
}
|
||||
|
||||
// The given codec should have all the appropriate message types
|
||||
// registered.
|
||||
err := codec.UnmarshalBinary(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error())
|
||||
}
|
||||
|
||||
// If the transaction is routed as an SDK transaction, decode and
|
||||
// return the embedded 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(es *EthSignature, chainID *big.Int) []byte {
|
||||
var v byte
|
||||
|
||||
r, s := es.r.Bytes(), es.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(es.v.Uint64() - 27)
|
||||
} else {
|
||||
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
|
||||
V := new(big.Int).Sub(es.v, chainIDMul)
|
||||
|
||||
v = byte(V.Uint64() - 35)
|
||||
}
|
||||
|
||||
sig[64] = v
|
||||
return sig
|
||||
}
|
||||
|
||||
func rlpHash(x interface{}) (h ethcmn.Hash) {
|
||||
hasher := ethsha.NewKeccak256()
|
||||
|
||||
rlp.Encode(hasher, x)
|
||||
hasher.Sum(h[:0])
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func ethSigMarshalAmino(es EthSignature) (raw [3]string, err error) {
|
||||
vb, err := es.v.MarshalText()
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
rb, err := es.r.MarshalText()
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
sb, err := es.s.MarshalText()
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
|
||||
raw[0], raw[1], raw[2] = string(vb), string(rb), string(sb)
|
||||
return raw, err
|
||||
}
|
||||
|
||||
func ethSigUnmarshalAmino(es *EthSignature, raw [3]string) (err error) {
|
||||
if err = es.v.UnmarshalText([]byte(raw[0])); err != nil {
|
||||
return
|
||||
}
|
||||
if err = es.r.UnmarshalText([]byte(raw[1])); err != nil {
|
||||
return
|
||||
}
|
||||
if err = es.s.UnmarshalText([]byte(raw[2])); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
326
types/tx_test.go
Normal file
326
types/tx_test.go
Normal file
@ -0,0 +1,326 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
testChainID = sdk.NewInt(3)
|
||||
|
||||
testPrivKey1, _ = ethcrypto.GenerateKey()
|
||||
testPrivKey2, _ = ethcrypto.GenerateKey()
|
||||
|
||||
testAddr1 = PrivKeyToEthAddress(testPrivKey1)
|
||||
testAddr2 = PrivKeyToEthAddress(testPrivKey2)
|
||||
|
||||
testSDKAddress = GenerateEthAddress()
|
||||
)
|
||||
|
||||
func newTestCodec() *wire.Codec {
|
||||
codec := wire.NewCodec()
|
||||
|
||||
RegisterWire(codec)
|
||||
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
|
||||
|
||||
return codec
|
||||
}
|
||||
|
||||
func newStdFee() auth.StdFee {
|
||||
return auth.NewStdFee(5000, sdk.NewCoin("photon", 150))
|
||||
}
|
||||
|
||||
func newTestEmbeddedTx(
|
||||
chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
|
||||
accNums []int64, seqs []int64, fee auth.StdFee,
|
||||
) sdk.Tx {
|
||||
|
||||
sigs := make([][]byte, len(pKeys))
|
||||
|
||||
for i, priv := range pKeys {
|
||||
signEtx := EmbeddedTxSign{chainID.String(), accNums[i], seqs[i], msgs, newStdFee()}
|
||||
|
||||
signBytes, err := signEtx.Bytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sig, err := ethcrypto.Sign(signBytes, priv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sigs[i] = sig
|
||||
}
|
||||
|
||||
return EmbeddedTx{msgs, fee, sigs}
|
||||
}
|
||||
|
||||
func newTestGethTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []ethtypes.Transaction {
|
||||
txs := make([]ethtypes.Transaction, len(pKeys))
|
||||
|
||||
for i, priv := range pKeys {
|
||||
ethTx := ethtypes.NewTransaction(
|
||||
uint64(i), addrs[i], big.NewInt(10), 100, big.NewInt(100), nil,
|
||||
)
|
||||
|
||||
signer := ethtypes.NewEIP155Signer(chainID.BigInt())
|
||||
ethTx, _ = ethtypes.SignTx(ethTx, signer, priv)
|
||||
|
||||
txs[i] = *ethTx
|
||||
}
|
||||
|
||||
return txs
|
||||
}
|
||||
|
||||
func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction {
|
||||
txs := make([]Transaction, len(pKeys))
|
||||
|
||||
for i, priv := range pKeys {
|
||||
emintTx := NewTransaction(
|
||||
uint64(i), addrs[i], sdk.NewInt(10), 100, sdk.NewInt(100), nil,
|
||||
)
|
||||
|
||||
emintTx.Sign(chainID, priv)
|
||||
|
||||
txs[i] = emintTx
|
||||
}
|
||||
|
||||
return txs
|
||||
}
|
||||
|
||||
func newTestSDKTxs(
|
||||
codec *wire.Codec, chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey,
|
||||
accNums []int64, seqs []int64, fee auth.StdFee,
|
||||
) []Transaction {
|
||||
|
||||
txs := make([]Transaction, len(pKeys))
|
||||
etx := newTestEmbeddedTx(chainID, msgs, pKeys, accNums, seqs, fee)
|
||||
|
||||
for i, priv := range pKeys {
|
||||
payload := codec.MustMarshalBinary(etx)
|
||||
|
||||
emintTx := NewTransaction(
|
||||
uint64(i), testSDKAddress, sdk.NewInt(10), 100,
|
||||
sdk.NewInt(100), payload,
|
||||
)
|
||||
|
||||
emintTx.Sign(testChainID, priv)
|
||||
|
||||
txs[i] = emintTx
|
||||
}
|
||||
|
||||
return txs
|
||||
}
|
||||
|
||||
func TestConvertTx(t *testing.T) {
|
||||
gethTxs := newTestGethTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1, testPrivKey2},
|
||||
[]ethcmn.Address{testAddr1, testAddr2},
|
||||
)
|
||||
ethTxs := newTestEthTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1, testPrivKey2},
|
||||
[]ethcmn.Address{testAddr1, testAddr2},
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
ethTx ethtypes.Transaction
|
||||
emintTx Transaction
|
||||
expectedEq bool
|
||||
}{
|
||||
{gethTxs[0], ethTxs[0], true},
|
||||
{gethTxs[0], ethTxs[1], false},
|
||||
{gethTxs[1], ethTxs[0], false},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
convertedTx := tc.emintTx.ConvertTx(testChainID.BigInt())
|
||||
|
||||
if tc.expectedEq {
|
||||
require.Equal(t, tc.ethTx, convertedTx, fmt.Sprintf("unexpected result: test case #%d", i))
|
||||
} else {
|
||||
require.NotEqual(t, tc.ethTx, convertedTx, fmt.Sprintf("unexpected result: test case #%d", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
ethTxs := newTestEthTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]ethcmn.Address{testAddr1},
|
||||
)
|
||||
|
||||
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 = sdk.NewInt(-1)
|
||||
return tx
|
||||
}, true},
|
||||
{ethTxs[0], func(msg sdk.Msg) sdk.Msg {
|
||||
tx := msg.(Transaction)
|
||||
tx.Data.Amount = sdk.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 TestHasEmbeddedTx(t *testing.T) {
|
||||
testCodec := newTestCodec()
|
||||
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
||||
|
||||
sdkTxs := newTestSDKTxs(
|
||||
testCodec, testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]int64{0}, []int64{0}, newStdFee(),
|
||||
)
|
||||
require.True(t, sdkTxs[0].HasEmbeddedTx(testSDKAddress))
|
||||
|
||||
ethTxs := newTestEthTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]ethcmn.Address{testAddr1},
|
||||
)
|
||||
require.False(t, ethTxs[0].HasEmbeddedTx(testSDKAddress))
|
||||
}
|
||||
|
||||
func TestGetEmbeddedTx(t *testing.T) {
|
||||
testCodec := newTestCodec()
|
||||
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
||||
|
||||
ethTxs := newTestEthTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]ethcmn.Address{testAddr1},
|
||||
)
|
||||
sdkTxs := newTestSDKTxs(
|
||||
testCodec, testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]int64{0}, []int64{0}, newStdFee(),
|
||||
)
|
||||
|
||||
etx, err := sdkTxs[0].GetEmbeddedTx(testCodec)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, etx.Messages)
|
||||
|
||||
etx, err = ethTxs[0].GetEmbeddedTx(testCodec)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, etx.Messages)
|
||||
}
|
||||
|
||||
func TestTransactionGetMsgs(t *testing.T) {
|
||||
ethTxs := newTestEthTxs(
|
||||
testChainID,
|
||||
[]*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]ethcmn.Address{testAddr1},
|
||||
)
|
||||
|
||||
msgs := ethTxs[0].GetMsgs()
|
||||
require.Len(t, msgs, 1)
|
||||
require.Equal(t, ethTxs[0], msgs[0])
|
||||
|
||||
expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
||||
etx := newTestEmbeddedTx(
|
||||
testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]int64{0}, []int64{0}, newStdFee(),
|
||||
)
|
||||
|
||||
msgs = etx.GetMsgs()
|
||||
require.Len(t, msgs, len(expectedMsgs))
|
||||
require.Equal(t, expectedMsgs, msgs)
|
||||
}
|
||||
|
||||
func TestGetRequiredSigners(t *testing.T) {
|
||||
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
||||
etx := newTestEmbeddedTx(
|
||||
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]int64{0}, []int64{0}, newStdFee(),
|
||||
)
|
||||
|
||||
signers := etx.(EmbeddedTx).GetRequiredSigners()
|
||||
require.Equal(t, []sdk.AccAddress{sdk.AccAddress(testAddr1.Bytes())}, signers)
|
||||
}
|
||||
|
||||
func TestTxDecoder(t *testing.T) {
|
||||
testCodec := newTestCodec()
|
||||
txDecoder := TxDecoder(testCodec, testSDKAddress)
|
||||
msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))}
|
||||
|
||||
// create a non-SDK Ethereum transaction
|
||||
emintTx := NewTransaction(
|
||||
uint64(0), testAddr1, sdk.NewInt(10), 100, sdk.NewInt(100), nil,
|
||||
)
|
||||
emintTx.Sign(testChainID, testPrivKey1)
|
||||
|
||||
// require the transaction to properly decode into a Transaction
|
||||
txBytes := testCodec.MustMarshalBinary(emintTx)
|
||||
tx, err := txDecoder(txBytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, emintTx, tx)
|
||||
|
||||
// create embedded transaction and encode
|
||||
etx := newTestEmbeddedTx(
|
||||
testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1},
|
||||
[]int64{0}, []int64{0}, newStdFee(),
|
||||
)
|
||||
|
||||
payload := testCodec.MustMarshalBinary(etx)
|
||||
|
||||
expectedEtx := EmbeddedTx{}
|
||||
testCodec.UnmarshalBinary(payload, &expectedEtx)
|
||||
|
||||
emintTx = NewTransaction(
|
||||
uint64(0), testSDKAddress, sdk.NewInt(10), 100,
|
||||
sdk.NewInt(100), payload,
|
||||
)
|
||||
emintTx.Sign(testChainID, testPrivKey1)
|
||||
|
||||
// require the transaction to properly decode into a Transaction
|
||||
txBytes = testCodec.MustMarshalBinary(emintTx)
|
||||
tx, err = txDecoder(txBytes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedEtx, tx)
|
||||
|
||||
// require the decoding to fail when no transaction bytes are given
|
||||
tx, err = txDecoder([]byte{})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, tx)
|
||||
|
||||
// create a non-SDK Ethereum transaction with an SDK address and garbage payload
|
||||
emintTx = NewTransaction(
|
||||
uint64(0), testSDKAddress, sdk.NewInt(10), 100, sdk.NewInt(100), []byte("garbage"),
|
||||
)
|
||||
emintTx.Sign(testChainID, testPrivKey1)
|
||||
|
||||
// require the transaction to fail decoding as the payload is invalid
|
||||
txBytes = testCodec.MustMarshalBinary(emintTx)
|
||||
tx, err = txDecoder(txBytes)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, tx)
|
||||
}
|
41
types/utils.go
Normal file
41
types/utils.go
Normal file
@ -0,0 +1,41 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GenerateAddress generates an Ethereum address.
|
||||
func GenerateEthAddress() ethcmn.Address {
|
||||
priv, err := ethcrypto.GenerateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return PrivKeyToEthAddress(priv)
|
||||
}
|
||||
|
||||
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
|
||||
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
|
||||
return ethcrypto.PubkeyToAddress(ecdsa.PublicKey(p.PublicKey))
|
||||
}
|
||||
|
||||
// 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
|
||||
// derived from the signature and bytes signed does not match the given signer.
|
||||
func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error {
|
||||
pk, err := ethcrypto.SigToPub(signBytes, sig)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "signature verification failed")
|
||||
} else if ethcrypto.PubkeyToAddress(*pk) != signer {
|
||||
return fmt.Errorf("invalid signature for signer: %s", signer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
36
types/utils_test.go
Normal file
36
types/utils_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
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
|
||||
signEtx := EmbeddedTxSign{testChainID.String(), 0, 0, msgs, newStdFee()}
|
||||
|
||||
// create signing bytes and sign
|
||||
signBytes, err := signEtx.Bytes()
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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)
|
||||
}
|
22
types/wire.go
Normal file
22
types/wire.go
Normal file
@ -0,0 +1,22 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
var typesCodec = wire.NewCodec()
|
||||
|
||||
func init() {
|
||||
RegisterWire(typesCodec)
|
||||
}
|
||||
|
||||
// RegisterWire registers all the necessary types with amino for the given
|
||||
// codec.
|
||||
func RegisterWire(codec *wire.Codec) {
|
||||
sdk.RegisterWire(codec)
|
||||
codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil)
|
||||
codec.RegisterConcrete(TxData{}, "types/TxData", nil)
|
||||
codec.RegisterConcrete(Transaction{}, "types/Transaction", nil)
|
||||
codec.RegisterConcrete(EmbeddedTx{}, "types/EmbeddedTx", nil)
|
||||
}
|
Loading…
Reference in New Issue
Block a user