diff --git a/Gopkg.lock b/Gopkg.lock index 6d78de47..9c6941f0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,22 +3,29 @@ [[projects]] branch = "master" + digest = "1:60d3b49df18861c92ac49cce49e49f61b3ec927e5b7f39c5ae1128ec5c197b98" name = "github.com/aristanetworks/goarista" packages = ["monotime"] - revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f" + pruneopts = "T" + revision = "fb622b9b46608fdb39d36447f4d8ef52fe37fc3d" [[projects]] branch = "master" + digest = "1:b9f5e0f033febe59a62d01e78486c0dd9e4afc9ac5d240aee6ce78a927142e8b" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8" + pruneopts = "T" + revision = "79e00513b1011888b1e675157ab89f527f901cae" [[projects]] + digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533" name = "github.com/btcsuite/btcutil" packages = ["bech32"] + pruneopts = "T" revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] + digest = "1:bc28e755cf6a9fd8e65497514d20c4907973e7a6a6409d30ead3fd37bfeb19a9" name = "github.com/cosmos/cosmos-sdk" packages = [ "baseapp", @@ -27,26 +34,47 @@ "version", "wire", "x/auth", - "x/stake/types" + "x/bank", + "x/gov", + "x/gov/tags", + "x/mock", + "x/params", + "x/slashing", + "x/stake", + "x/stake/keeper", + "x/stake/tags", + "x/stake/types", ] - revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5" - version = "v0.23.1" + pruneopts = "T" + revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9" + version = "v0.24.2" [[projects]] + digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a" name = "github.com/davecgh/go-spew" packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + pruneopts = "T" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] branch = "master" + digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7" name = "github.com/edsrzf/mmap-go" packages = ["."] + pruneopts = "T" revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e" [[projects]] + digest = "1:3238a0c355a81640974751f7d3bab21bf91035165f75c2c457959425c0422a4b" name = "github.com/ethereum/go-ethereum" packages = [ + ".", + "accounts", + "accounts/abi", + "accounts/keystore", + "accounts/usbwallet", + "accounts/usbwallet/internal/trezor", "common", "common/bitutil", "common/hexutil", @@ -64,44 +92,61 @@ "crypto/bn256", "crypto/bn256/cloudflare", "crypto/bn256/google", + "crypto/ecies", + "crypto/randentropy", "crypto/secp256k1", "crypto/sha3", + "eth/downloader", "ethdb", "event", + "internal/ethapi", "log", "metrics", + "p2p", + "p2p/discover", + "p2p/discv5", + "p2p/nat", "p2p/netutil", "params", "rlp", "rpc", - "trie" + "signer/core", + "trie", ] + pruneopts = "T" revision = "dea1ce052a10cd7d401a5c04f83f371a06fe293c" version = "v1.8.11" [[projects]] + digest = "1:0b9c3ad6c948d57a379da9c4e1cdd989b1c73ddc5ec8673f52a9539ce60a109b" name = "github.com/go-kit/kit" packages = [ "log", "log/level", - "log/term" + "log/term", ] + pruneopts = "T" revision = "4dc7be5d2d12881735283bcab7352178e190fc71" version = "v0.6.0" [[projects]] + digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" name = "github.com/go-logfmt/logfmt" packages = ["."] + pruneopts = "T" revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" version = "v0.3.0" [[projects]] + digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" name = "github.com/go-stack/stack" packages = ["."] - revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" - version = "v1.7.0" + pruneopts = "T" + revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" + version = "v1.8.0" [[projects]] + digest = "1:da39f4a22829ca95e63566208e0ea42d6f055f41dff1b14fdab88d88f62df653" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -109,96 +154,172 @@ "proto", "protoc-gen-gogo/descriptor", "sortkeys", - "types" + "types", ] + pruneopts = "T" revision = "636bf0302bc95575d69441b25a2603156ffdddf1" version = "v1.1.1" [[projects]] + digest = "1:832e17df5ff8bbe0e0693d2fb46c5e53f96c662ee804049ce3ab6557df74e3ab" name = "github.com/golang/protobuf" packages = [ "proto", + "protoc-gen-go/descriptor", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] + pruneopts = "T" revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:6027b20c168728321bd99ad01f35118eded457b01c03e647a84833ab331f2f5b" name = "github.com/golang/snappy" packages = ["."] + pruneopts = "T" revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" [[projects]] + digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db" name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru" + "simplelru", ] + pruneopts = "T" revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" [[projects]] + branch = "master" + digest = "1:202e4a1a283dd740ca9d131787e73bb9d69611a01ef86e82ed262e035b0dd792" + name = "github.com/huin/goupnp" + packages = [ + ".", + "dcps/internetgateway1", + "dcps/internetgateway2", + "httpu", + "scpd", + "soap", + "ssdp", + ] + pruneopts = "T" + revision = "1395d1447324cbea88d249fbfcfd70ea878fdfca" + +[[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "T" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" +[[projects]] + digest = "1:32b82e71cf24f8b78323e0d7903c4b90278486283965aa2a19b1ea13763b8f34" + name = "github.com/jackpal/go-nat-pmp" + packages = ["."] + pruneopts = "T" + revision = "c9cfead9f2a36ddf3daa40ba269aa7f4bbba6b62" + version = "v1.0.1" + [[projects]] branch = "master" + digest = "1:dc6b1a6801b3055e9bd3da4cd1e568606eb48118cc6f28e947783aa5d998ad74" name = "github.com/jmhodges/levigo" packages = ["."] + pruneopts = "T" revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" [[projects]] branch = "master" + digest = "1:01f1325bf6f105bb633029a2d8b63f1a2357181e60af8dadabf14ad2e84398c5" + name = "github.com/karalabe/hid" + packages = ["."] + pruneopts = "T" + revision = "2b4488a37358b7283de4f9622553e85ebbe73125" + +[[projects]] + branch = "master" + digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" name = "github.com/kr/logfmt" packages = ["."] + pruneopts = "T" revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" [[projects]] + digest = "1:361de06aa7ae272616cbe71c3994a654cc6316324e30998e650f7765b20c5b33" + name = "github.com/pborman/uuid" + packages = ["."] + pruneopts = "T" + revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53" + version = "v1.1" + +[[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "T" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:22aa691fe0213cb5c07d103f9effebcb7ad04bee45a0ce5fe5369d0ca2ec3a1f" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "T" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] + digest = "1:602081d2a289d1f76ea90b806b0c61c19038d76504e9005ccb969864dbaee339" + name = "github.com/rjeczalik/notify" + packages = ["."] + pruneopts = "T" + revision = "0f065fa99b48b842c3fd3e2c8b194c6f2b69f6b8" + version = "v0.9.1" + +[[projects]] + digest = "1:6cae6970d70fc5fe75bf83c48ee33e9c4c561a62d0b033254bee8dd5942b815a" name = "github.com/rs/cors" packages = ["."] + pruneopts = "T" revision = "3fb1b69b103a84de38a19c3c6ec073dd6caa4d3f" version = "v1.5.0" [[projects]] + digest = "1:8be8b3743fc9795ec21bbd3e0fc28ff6234018e1a269b0a7064184be95ac13e0" name = "github.com/spf13/cobra" packages = ["."] + pruneopts = "T" revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" version = "v0.0.3" [[projects]] + digest = "1:9ba911fe3884995431690e7eb180cf848da0d637ba5f61711783b795d031793f" name = "github.com/spf13/pflag" packages = ["."] - revision = "583c0c0531f06d5278b7d917446061adc344b5cd" - version = "v1.0.1" + pruneopts = "T" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" [[projects]] + digest = "1:8f39978e4fb2a11d43cc954f2ab458cb38995d4c1557b6d3a7c8cafe0ec2277c" name = "github.com/stretchr/testify" packages = [ "assert", - "require" + "require", + "suite", ] + pruneopts = "T" revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" version = "v1.2.1" [[projects]] branch = "master" + digest = "1:ee395d0d8c1719b5a1407f34af93953b4763bacb19a8961aba5b6d312824da41" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -212,33 +333,41 @@ "leveldb/opt", "leveldb/storage", "leveldb/table", - "leveldb/util" + "leveldb/util", ] - revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + pruneopts = "T" + revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" [[projects]] branch = "master" + digest = "1:2b15c0442dc80b581ce7028b2e43029d2f3f985da43cb1d55f7bcdeca785bda0" name = "github.com/tendermint/ed25519" packages = [ ".", "edwards25519", - "extra25519" + "extra25519", ] + pruneopts = "T" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" [[projects]] + digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245" name = "github.com/tendermint/go-amino" packages = ["."] + pruneopts = "T" revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" version = "0.10.1" [[projects]] + digest = "1:bf042d2f7d1252b9dcae8e694e2f0a9b5294cb357c086fd86dc540d2f32c9fdf" name = "github.com/tendermint/iavl" packages = ["."] + pruneopts = "T" revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9" version = "v0.9.2" [[projects]] + digest = "1:5a60cb048b401c0263c227baf8778ecaf038be531707057607949540486874ef" name = "github.com/tendermint/tendermint" packages = [ "abci/server", @@ -255,58 +384,100 @@ "libs/log", "libs/pubsub", "libs/pubsub/query", - "types" + "types", ] - revision = "d542d2c3945116697f60451e6a407082c41c3cc9" - version = "v0.22.8" + pruneopts = "T" + revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e" + version = "v0.23.1-rc0" [[projects]] branch = "master" + digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd" name = "golang.org/x/crypto" - packages = ["ripemd160"] - revision = "f027049dab0ad238e394a753dba2d14753473a04" + packages = [ + "pbkdf2", + "ripemd160", + "scrypt", + "ssh/terminal", + ] + pruneopts = "T" + revision = "182538f80094b6a8efaade63a8fd8e0d9d5843dd" [[projects]] + digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7" name = "golang.org/x/net" packages = [ "context", + "html", + "html/atom", + "html/charset", "http/httpguts", "http2", "http2/hpack", "idna", "internal/timeseries", "trace", - "websocket" + "websocket", ] + pruneopts = "T" revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] + branch = "master" + digest = "1:bfa444982d49ce4ca1360599270a94de12a573ccd3bf04493c79bee09da3170b" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "T" + revision = "fa5fdf94c78965f1aa8423f0cc50b8b8d728b05a" + +[[projects]] + digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a" name = "golang.org/x/text" packages = [ "collate", "collate/build", + "encoding", + "encoding/charmap", + "encoding/htmlindex", + "encoding/internal", + "encoding/internal/identifier", + "encoding/japanese", + "encoding/korean", + "encoding/simplifiedchinese", + "encoding/traditionalchinese", + "encoding/unicode", "internal/colltab", "internal/gen", "internal/tag", "internal/triegen", "internal/ucd", + "internal/utf8internal", "language", + "runes", "secure/bidirule", "transform", "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable" + "unicode/rangetable", ] + pruneopts = "T" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] + branch = "master" + digest = "1:e43f1cb3f488a0c2be85939c2a594636f60b442a12a196c778bd2d6c9aca3df7" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + pruneopts = "T" + revision = "11092d34479b07829b72e10713b159248caf5dad" [[projects]] + digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770" name = "google.golang.org/grpc" packages = [ ".", @@ -333,32 +504,78 @@ "stats", "status", "tap", - "transport" + "transport", ] + pruneopts = "T" revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" version = "v1.13.0" [[projects]] + digest = "1:3ccd10c863188cfe0d936fcfe6a055c95362e43af8e7039e33baade846928e74" name = "gopkg.in/fatih/set.v0" packages = ["."] + pruneopts = "T" revision = "57907de300222151a123d29255ed17f5ed43fad3" version = "v0.1.0" [[projects]] branch = "v2" + digest = "1:dae137be246befa42ce4b48c0feff2c5796b8a5027139a283f31a21173744410" name = "gopkg.in/karalabe/cookiejar.v2" packages = ["collections/prque"] + pruneopts = "T" revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57" [[projects]] branch = "v2" + digest = "1:3d3f9391ab615be8655ae0d686a1564f3fec413979bb1aaf018bac1ec1bb1cc7" name = "gopkg.in/natefinch/npipe.v2" packages = ["."] + pruneopts = "T" revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "353f6762943df5f67c2fba13bc530c800977ed507155bb9edc3b44947f0abc6e" + input-imports = [ + "github.com/cosmos/cosmos-sdk/baseapp", + "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/cosmos/cosmos-sdk/x/bank", + "github.com/cosmos/cosmos-sdk/x/gov", + "github.com/cosmos/cosmos-sdk/x/params", + "github.com/cosmos/cosmos-sdk/x/slashing", + "github.com/cosmos/cosmos-sdk/x/stake", + "github.com/cosmos/cosmos-sdk/x/stake/types", + "github.com/ethereum/go-ethereum/common", + "github.com/ethereum/go-ethereum/common/hexutil", + "github.com/ethereum/go-ethereum/common/math", + "github.com/ethereum/go-ethereum/consensus", + "github.com/ethereum/go-ethereum/consensus/ethash", + "github.com/ethereum/go-ethereum/consensus/misc", + "github.com/ethereum/go-ethereum/core", + "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/signer/core", + "github.com/ethereum/go-ethereum/trie", + "github.com/hashicorp/golang-lru", + "github.com/pkg/errors", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", + "github.com/stretchr/testify/suite", + "github.com/tendermint/tendermint/abci/types", + "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 diff --git a/Gopkg.toml b/Gopkg.toml index 1010c541..1bd7ac6c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -4,7 +4,7 @@ [[constraint]] name = "github.com/cosmos/cosmos-sdk" - version = "=0.23.1" + version = "=0.24.2" [[constraint]] name = "github.com/hashicorp/golang-lru" @@ -15,12 +15,12 @@ version = "~0.0.1" [[override]] - name = "google.golang.org/genproto" - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + name = "github.com/tendermint/iavl" + version = "=v0.9.2" [[override]] name = "github.com/tendermint/tendermint" - version = "=v0.22.8" + version = "=v0.23.1-rc0" [[constraint]] name = "github.com/stretchr/testify" diff --git a/Makefile b/Makefile index d68cdbee..239556c4 100644 --- a/Makefile +++ b/Makefile @@ -28,16 +28,16 @@ all: tools deps install build: ifeq ($(OS),Windows_NT) - go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY).exe ./cmd/ethermintd - go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY).exe ./cmd/ethermintcli + go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY).exe ./cmd/emintd + go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY).exe ./cmd/emintcli else - go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY) ./cmd/ethermintd/ - go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY) ./cmd/ethermintcli/ + go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY) ./cmd/emintd/ + go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY) ./cmd/emintcli/ endif install: - go install $(BUILD_FLAGS) ./cmd/ethermintd - go install $(BUILD_FLAGS) ./cmd/ethermintcli + go install $(BUILD_FLAGS) ./cmd/emintd + go install $(BUILD_FLAGS) ./cmd/emintcli clean: @rm -rf ./build ./vendor diff --git a/README.md b/README.md index 854683bd..25731bd0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![](https://godoc.org/github.com/cosmos/ethermint?status.svg)](http://godoc.org/github.com/cosmos/ethermint) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/ethermint)](https://goreportcard.com/report/github.com/cosmos/ethermint) [![CircleCI](https://circleci.com/gh/cosmos/ethermint.svg?style=svg)](https://circleci.com/gh/cosmos/ethermint) - +[![CircleCI](https://circleci.com/gh/cosmos/ethermint.svg?style=svg)](https://circleci.com/gh/cosmos/ethermint) +[![](https://godoc.org/github.com/cosmos/ethermint?status.svg)](http://godoc.org/github.com/cosmos/ethermint) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/ethermint)](https://goreportcard.com/report/github.com/cosmos/ethermint) # Ethermint __**WARNING:**__ Ethermint is under VERY ACTIVE DEVELOPMENT and should be treated as pre-alpha software. This means it is not meant to be run in production, its APIs are subject to change without warning and should not be relied upon, and it should not be used to hold any value. We will remove this warning when we have a release that is stable, secure, and properly tested. @@ -13,18 +13,26 @@ __**WARNING:**__ Ethermint is under VERY ACTIVE DEVELOPMENT and should be treate ### Implementation -- [x] Have a working implementation that can parse and validate the existing ETH Chain and persist it in a Tendermint store -- [ ] Benchmark this implementation to ensure performance -- [ ] Allow the Ethermint EVM to interact with other [Cosmos SDK modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/app3.md) -- [ ] Implement the Web3 APIs as a Cosmos Light Client for Ethermint -- [ ] Ethermint is a full Cosmos SDK application and can be deployed as it's own zone +#### Completed +- Have a working implementation that can parse and validate the existing ETH Chain and persist it in a Tendermint store +- Implement Ethereum transactions in the CosmosSDK + +#### Current Work +- Implement web3 compatible API layer +- Implement the EVM as a CosmosSDK module +- Allow the Ethermint EVM to interact with other [Cosmos SDK modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/app3.md) + +#### Next Steps +- Hard spoon enablement: The ability to export state from `geth` and import token balances into Ethermint +- Ethermint is a functioning Cosmos SDK application and can be deployed as its own zone +- Full web3 compatibility will enable existing Ethereum applications to use Ethermint ### Building Ethermint To build, execute the following commands: ```bash -# To build the binary and put the results in ./build +# To build the binary and put the resulting binary in ./build $ make tools deps build # To build the project and install it in $GOBIN diff --git a/app/ethermint.go b/app/ethermint.go index 575452eb..b911cd76 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -5,13 +5,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/pkg/errors" "github.com/cosmos/ethermint/handlers" "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" - ethparams "github.com/ethereum/go-ethereum/params" + abci "github.com/tendermint/tendermint/abci/types" tmcmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" tmlog "github.com/tendermint/tendermint/libs/log" @@ -28,63 +34,159 @@ type ( EthermintApp struct { *bam.BaseApp - codec *wire.Codec - sealed bool + codec *wire.Codec - accountKey *sdk.KVStoreKey - accountMapper auth.AccountMapper - // TODO: keys, stores, mappers, and keepers + accountKey *sdk.KVStoreKey + mainKey *sdk.KVStoreKey + stakeKey *sdk.KVStoreKey + slashingKey *sdk.KVStoreKey + govKey *sdk.KVStoreKey + feeCollKey *sdk.KVStoreKey + paramsKey *sdk.KVStoreKey + tParamsKey *sdk.TransientStoreKey + + accountMapper auth.AccountMapper + feeCollKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper + govKeeper gov.Keeper + paramsKeeper params.Keeper } - - // 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( - logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, - sdkAddr ethcmn.Address, opts ...Options, + logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address, baseAppOpts ...func(*bam.BaseApp), ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, codec, logger, db), - codec: codec, - accountKey: sdk.NewKVStoreKey("accounts"), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec, sdkAddr), baseAppOpts...), + codec: codec, + accountKey: sdk.NewKVStoreKey("acc"), + mainKey: sdk.NewKVStoreKey("main"), + stakeKey: sdk.NewKVStoreKey("stake"), + slashingKey: sdk.NewKVStoreKey("slashing"), + govKey: sdk.NewKVStoreKey("gov"), + feeCollKey: sdk.NewKVStoreKey("fee"), + paramsKey: sdk.NewKVStoreKey("params"), + tParamsKey: sdk.NewTransientStoreKey("transient_params"), } + + // set application keepers and mappers app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount) + app.coinKeeper = bank.NewKeeper(app.accountMapper) + app.paramsKeeper = params.NewKeeper(app.codec, app.paramsKey) + app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.codec, app.feeCollKey) + app.stakeKeeper = stake.NewKeeper( + app.codec, app.stakeKey, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace), + ) + app.govKeeper = gov.NewKeeper( + app.codec, app.govKey, app.paramsKeeper.Setter(), app.coinKeeper, + app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace), + ) + app.slashingKeeper = slashing.NewKeeper( + app.codec, app.slashingKey, app.stakeKeeper, + app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace), + ) - app.SetTxDecoder(types.TxDecoder(codec, sdkAddr)) - app.SetAnteHandler(handlers.AnteHandler(app.accountMapper)) - app.MountStoresIAVL(app.accountKey) + // register message handlers + app.Router(). + // TODO: Do we need to mount bank and IBC handlers? Should be handled + // directly in the EVM. + AddRoute("stake", stake.NewHandler(app.stakeKeeper)). + AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). + AddRoute("gov", gov.NewHandler(app.govKeeper)) - for _, opt := range opts { - opt(app) - } + // initialize the underlying ABCI BaseApp + app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.SetAnteHandler(handlers.AnteHandler(app.accountMapper, app.feeCollKeeper)) - err := app.LoadLatestVersion(app.accountKey) - if err != nil { + app.MountStoresIAVL( + app.mainKey, app.accountKey, app.stakeKey, app.slashingKey, + app.govKey, app.feeCollKey, app.paramsKey, + ) + app.MountStore(app.tParamsKey, sdk.StoreTypeTransient) + + if err := app.LoadLatestVersion(app.accountKey); err != nil { tmcmn.Exit(err.Error()) } - app.seal() + app.BaseApp.Seal() return app } -// seal seals the Ethermint application and prohibits any future modifications -// that change critical components. -func (app *EthermintApp) seal() { - app.sealed = true +// BeginBlocker signals the beginning of a block. It performs application +// updates on the start of every block. +func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// EndBlocker signals the end of a block. It performs application updates on +// the end of every block. +func (app *EthermintApp) EndBlocker(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock { + tags := gov.EndBlocker(ctx, app.govKeeper) + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + app.slashingKeeper.AddValidators(ctx, validatorUpdates) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + Tags: tags, + } +} + +// initChainer initializes the application blockchain with validators and other +// state data from TendermintCore. +func (app *EthermintApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var genesisState GenesisState + stateJSON := req.AppStateBytes + + err := app.codec.UnmarshalJSON(stateJSON, &genesisState) + if err != nil { + panic(errors.Wrap(err, "failed to parse application genesis state")) + } + + // load the genesis accounts + for _, genAcc := range genesisState.Accounts { + acc := genAcc.ToAccount() + acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) + app.accountMapper.SetAccount(ctx, acc) + } + + // load the genesis stake information + validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + if err != nil { + panic(errors.Wrap(err, "failed to initialize genesis validators")) + } + + slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData) + gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + + return abci.ResponseInitChain{ + Validators: validators, + } } // CreateCodec creates a new amino wire codec and registers all the necessary -// structures and interfaces needed for the application. +// concrete types and interfaces needed for the application. func CreateCodec() *wire.Codec { codec := wire.NewCodec() - // Register other modules, types, and messages... types.RegisterWire(codec) + auth.RegisterWire(codec) + gov.RegisterWire(codec) + slashing.RegisterWire(codec) + stake.RegisterWire(codec) + wire.RegisterCrypto(codec) + return codec } diff --git a/app/genesis.go b/app/genesis.go new file mode 100644 index 00000000..972ac8ca --- /dev/null +++ b/app/genesis.go @@ -0,0 +1,47 @@ +package app + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/ethermint/types" +) + +type ( + // GenesisState defines the application's genesis state. It contains all the + // information required and accounts to initialize the blockchain. + GenesisState struct { + Accounts []GenesisAccount `json:"accounts"` + StakeData stake.GenesisState `json:"stake"` + GovData gov.GenesisState `json:"gov"` + } + + // GenesisAccount defines an account to be initialized in the genesis state. + GenesisAccount struct { + Address sdk.AccAddress `json:"address"` + Coins sdk.Coins `json:"coins"` + Code []byte `json:"code,omitempty"` + Storage types.Storage `json:"storage,omitempty"` + } +) + +// NewGenesisAccount returns a reference to a new initialized genesis account. +func NewGenesisAccount(acc *types.Account) GenesisAccount { + return GenesisAccount{ + Address: acc.GetAddress(), + Coins: acc.GetCoins(), + Code: acc.Code, + Storage: acc.Storage, + } +} + +// ToAccount converts a genesis account to an initialized Ethermint account. +func (ga *GenesisAccount) ToAccount() (acc *types.Account) { + base := auth.BaseAccount{ + Address: ga.Address, + Coins: ga.Coins.Sort(), + } + + return types.NewAccount(base, ga.Code, ga.Storage) +} diff --git a/cmd/ethermintcli/main.go b/cmd/emintcli/main.go similarity index 100% rename from cmd/ethermintcli/main.go rename to cmd/emintcli/main.go diff --git a/cmd/ethermintd/main.go b/cmd/emintd/main.go similarity index 100% rename from cmd/ethermintd/main.go rename to cmd/emintd/main.go diff --git a/docs/intro/README.md b/docs/intro/README.md new file mode 100644 index 00000000..61ef3acb --- /dev/null +++ b/docs/intro/README.md @@ -0,0 +1,50 @@ +# Introduction + +## What is Ethermint + +Ethermint is a high throughput PoS blockchain that is fully compatible and +interoperable with Ethereum. In other words, it allows for running vanilla Ethereum +on top of [Tendermint](https://github.com/tendermint/tendermint) consensus via +the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/). This allows developers +to have all the desired features of Ethereum, while at the same time benefit +from Tendermint’s PoS implementation. Also, because it is built on top of the +Cosmos SDK, it will be able to exchange value with the rest of the Cosmos Ecosystem. + +Here’s a glance at some of the key features of Ethermint: + +* Web3 compatibility +* High throughput +* Horizontal scalability +* Transaction finality + +Ethermint enables these key features through: + +* Implementing Tendermint's ABCI application interface to manage the base Blockchain +* Leveraging [modules](https://github.com/cosmos/cosmos-sdk/tree/master/x/) and other mechanisms implemented by the Cosmos SDK +* Utilizing [`geth`](https://github.com/ethereum/go-ethereum) as a library to avoid code reuse and improve maintainability +* Exposing a fully compatible Web3 RPC layer for interacting with the system + +The sum of these features allows developers to leverage existing Ethereum ecosystem +tooling and software to seamlessly deploy smart contracts which interact with the rest of the Cosmos +ecosystem! + +## In-depth Topics + +### Tendermint Core & the Application Blockchain Interface (ABCI) + +Tendermint consists of two chief technical components: a blockchain consensus +engine and a generic application interface. The consensus engine, called +Tendermint Core, ensures that the same transactions are recorded on every machine +in the same order. The application interface, called the Application Blockchain +Interface (ABCI), enables the transactions to be processed in any programming +language. + +Tendermint has evolved to be a general purpose blockchain consensus engine that +can host arbitrary application states. Since Tendermint can replicate arbitrary +applications, it can be used as a plug-and-play replacement for the consensus +engines of other blockchains. Ethermint is such an example of an ABCI application +replacing Ethereum's PoW via Tendermint's consensus engine. + +Another example of a cryptocurrency application built on Tendermint is the Cosmos +network. Tendermint is able to decompose the blockchain design by offering a very +simple API (ie. the ABCI) between the application process and consensus process. diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md new file mode 100644 index 00000000..7fe16367 --- /dev/null +++ b/docs/spec/transactions/README.md @@ -0,0 +1,64 @@ +# Transactions + +> NOTE: The specification documented below is still highly active in development +and subject to change. + +## Routing + +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 `Payload` as the potential encoding of a Cosmos-routed transaction. What +designates this encoding, and ultimately routing, is the `Recipient` address -- +if this address matches some global unique predefined and configured address, +we regard it as a transaction meant for Cosmos, otherwise, the transaction is a +pure Ethereum transaction and will be executed in the EVM. + +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 +transactions are consumed, they are passed through a series of handlers. Once such +handler, `runTx`, is responsible for invoking the `TxDecoder` which performs the +business logic of properly deserializing raw transaction bytes into either an +Ethereum transaction or a Cosmos transaction. + +__Note__: Our goal is to utilize Geth as a library, at least as much as possible, +so it should be expected that these types and the operations you may perform on +them will keep in line with Ethereum (e.g. signature algorithms and gas/fees). +In addition, we aim to have existing tooling and frameworks in the Ethereum +ecosystem have 100% compatibility with creating transactions in Ethermint. + +## Transactions & Messages + +The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`). +A `sdk.Tx` is a list of `sdk.Msg` wrapped with authentication and fee data. Users +can create messages containing arbitrary information by implementing the `sdk.Msg` +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 + +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 +routed transactions. However, just as in Cosmos, Ethermint will support multiple +signers for embedded Cosmos routed transactions. Signatures over the +`Transaction` type are identical to Ethereum. However, the embedded transaction contains +a canonical signature structure that contains the signature itself and other +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 + +TODO diff --git a/handlers/ante.go b/handlers/ante.go index 3a8be0d6..72f97460 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -24,23 +24,22 @@ 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 { +// 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(am auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHandler { return func(sdkCtx sdk.Context, tx sdk.Tx) (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) + gasLimit = int64(tx.Data().GasLimit) handler = handleEthTx - case types.EmbeddedTx: + case auth.StdTx: gasLimit = tx.Fee.Gas handler = handleEmbeddedTx default: @@ -90,73 +89,75 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Cont return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true } - // validate signature - sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante verify") - addr, err := ethTx.VerifySig(chainID) + 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 } - // validate AccountNonce (called Sequence in AccountMapper) - acc := am.GetAccount(sdkCtx, addr[:]) + acc := am.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("Wrong AccountNonce: expected %d", seq)).Result(), true + if ethTx.Data().AccountNonce != uint64(seq) { + return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true } + err = acc.SetSequence(seq + 1) if err != nil { - panic(err) + return sdkCtx, sdk.ErrInternal(err.Error()).Result(), true } - am.SetAccount(sdkCtx, acc) - return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data.GasLimit)}, false + am.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, am auth.AccountMapper) (sdk.Context, sdk.Result, bool) { - etx, ok := tx.(types.EmbeddedTx) + stdTx, ok := tx.(auth.StdTx) if !ok { return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true } - if err := validateEmbeddedTxBasic(etx); err != nil { + if err := validateStdTxBasic(stdTx); err != nil { return sdkCtx, err.Result(), true } - signerAddrs := etx.GetRequiredSigners() + signerAddrs := stdTx.GetSigners() signerAccs := make([]auth.Account, len(signerAddrs)) // validate signatures - for i, sig := range etx.Signatures { + for i, sig := range stdTx.Signatures { signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes()) - signerAcc, err := validateSignature(sdkCtx, etx, signer, sig, am) + acc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) + // err.Code() != sdk.CodeOK if err != nil { return sdkCtx, err.Result(), true } // TODO: Fees! - am.SetAccount(sdkCtx, signerAcc) - signerAccs[i] = signerAcc + am.SetAccount(sdkCtx, acc) + signerAccs[i] = acc } newCtx := auth.WithSigners(sdkCtx, signerAccs) - return newCtx, sdk.Result{GasWanted: etx.Fee.Gas}, false + return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false } -// validateEmbeddedTxBasic validates an EmbeddedTx based on things that don't +// validateStdTxBasic validates an auth.StdTx based on parameters that do not // depend on the context. -func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) { - sigs := etx.Signatures +func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) { + sigs := stdTx.Signatures if len(sigs) == 0 { return sdk.ErrUnauthorized("transaction missing signatures") } - signerAddrs := etx.GetRequiredSigners() + signerAddrs := stdTx.GetSigners() if len(sigs) != len(signerAddrs) { return sdk.ErrUnauthorized("invalid number of transaction signers") } @@ -165,8 +166,8 @@ func validateEmbeddedTxBasic(etx types.EmbeddedTx) (err sdk.Error) { } func validateSignature( - sdkCtx sdk.Context, etx types.EmbeddedTx, signer ethcmn.Address, - sig []byte, am auth.AccountMapper, + sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address, + sig auth.StdSignature, am auth.AccountMapper, ) (acc auth.Account, sdkErr sdk.Error) { chainID := sdkCtx.ChainID() @@ -176,28 +177,29 @@ func validateSignature( 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, + accNum := acc.GetAccountNumber() + if accNum != sig.AccountNumber { + return nil, sdk.ErrInvalidSequence( + fmt.Sprintf("invalid account number; got %d, expected %d", sig.AccountNumber, accNum)) } - err := acc.SetSequence(signEtx.Sequence + 1) + 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, err := signEtx.Bytes() - 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 verify") + sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante signature verification") - if err := types.ValidateSigner(signBytes, sig, signer); err != nil { + if err := types.ValidateSigner(signBytes, sig.Signature, signer); err != nil { return nil, sdk.ErrUnauthorized(err.Error()) } diff --git a/server/rpc/apis.go b/server/rpc/apis.go new file mode 100644 index 00000000..d0f7bb38 --- /dev/null +++ b/server/rpc/apis.go @@ -0,0 +1,45 @@ +// Package rpc contains RPC handler methods and utilities to start +// Ethermint's Web3-compatibly JSON-RPC server. +package rpc + +import ( + "github.com/cosmos/ethermint/version" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rpc" +) + +// returns the master list of public APIs for use with StartHTTPEndpoint +func GetRPCAPIs() []rpc.API { + return []rpc.API{ + { + Namespace: "web3", + Version: "1.0", + Service: NewPublicWeb3API(), + }, + { + Namespace: "eth", + Version: "1.0", + Service: NewPublicEthAPI(), + }, + } +} + +// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicWeb3API struct { +} + +// NewPublicWeb3API creates an instance of the Web3 API. +func NewPublicWeb3API() *PublicWeb3API { + return &PublicWeb3API{} +} + +// ClientVersion returns the client version in the Web3 user agent format. +func (a *PublicWeb3API) ClientVersion() string { + return version.ClientVersion() +} + +// Sha3 returns the keccak-256 hash of the passed-in input. +func (a *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { + return crypto.Keccak256(input) +} diff --git a/server/rpc/apis_test.go b/server/rpc/apis_test.go new file mode 100644 index 00000000..d0bec724 --- /dev/null +++ b/server/rpc/apis_test.go @@ -0,0 +1,65 @@ +package rpc + +import ( + "context" + "github.com/cosmos/ethermint/version" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "testing" +) + +type apisTestSuite struct { + suite.Suite + Stop context.CancelFunc + Port int +} + +func (s *apisTestSuite) SetupSuite() { + stop, port, err := startAPIServer() + require.Nil(s.T(), err, "unexpected error") + s.Stop = stop + s.Port = port +} + +func (s *apisTestSuite) TearDownSuite() { + s.Stop() +} + +func (s *apisTestSuite) TestPublicWeb3APIClientVersion() { + res, err := rpcCall(s.Port, "web3_clientVersion", []string{}) + require.Nil(s.T(), err, "unexpected error") + require.Equal(s.T(), version.ClientVersion(), res) +} + +func (s *apisTestSuite) TestPublicWeb3APISha3() { + res, err := rpcCall(s.Port, "web3_sha3", []string{"0x67656c6c6f20776f726c64"}) + require.Nil(s.T(), err, "unexpected error") + require.Equal(s.T(), "0x1b84adea42d5b7d192fd8a61a85b25abe0757e9a65cab1da470258914053823f", res) +} + +func (s *apisTestSuite) TestMiningAPIs() { + res, err := rpcCall(s.Port, "eth_mining", nil) + require.Nil(s.T(), err, "unexpected error") + require.Equal(s.T(), false, res) + + res, err = rpcCall(s.Port, "eth_hashrate", nil) + require.Nil(s.T(), err, "unexpected error") + require.Equal(s.T(), "0x0", res) +} + +func TestAPIsTestSuite(t *testing.T) { + suite.Run(t, new(apisTestSuite)) +} + +func startAPIServer() (context.CancelFunc, int, error) { + config := &Config{ + RPCAddr: "127.0.0.1", + RPCPort: randomPort(), + } + ctx, cancel := context.WithCancel(context.Background()) + _, err := StartHTTPEndpoint(ctx, config, GetRPCAPIs()) + if err != nil { + return cancel, 0, err + } + return cancel, config.RPCPort, nil +} diff --git a/server/rpc/config.go b/server/rpc/config.go new file mode 100644 index 00000000..2c030f1b --- /dev/null +++ b/server/rpc/config.go @@ -0,0 +1,16 @@ +package rpc + +// Config contains configuration fields that determine the +// behavior of the RPC HTTP server. +type Config struct { + // EnableRPC defines whether or not to enable the RPC server + EnableRPC bool + // RPCAddr defines the IP address to listen on + RPCAddr string + // RPCPort defines the port to listen on + RPCPort int + // RPCCORSDomains defines list of domains to enable CORS headers for (used by browsers) + RPCCORSDomains []string + // RPCVhosts defines list of domains to listen on (useful if Tendermint is addressable via DNS) + RPCVHosts []string +} diff --git a/server/rpc/eth_api.go b/server/rpc/eth_api.go new file mode 100644 index 00000000..fc212ff0 --- /dev/null +++ b/server/rpc/eth_api.go @@ -0,0 +1,196 @@ +package rpc + +import ( + "github.com/cosmos/ethermint/version" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core" + "math/big" +) + +// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicEthAPI struct{} + +// NewPublicEthAPI creates an instance of the public ETH Web3 API. +func NewPublicEthAPI() *PublicEthAPI { + return &PublicEthAPI{} +} + +// ProtocolVersion returns the supported Ethereum protocol version. +func (e *PublicEthAPI) ProtocolVersion() string { + return version.ProtocolVersion +} + +// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct +// outlining the state of the sync if it is. +func (e *PublicEthAPI) Syncing() interface{} { + return false +} + +// Coinbase returns this node's coinbase address. Not used in Ethermint. +func (e *PublicEthAPI) Coinbase() (addr common.Address) { + return +} + +// Mining returns whether or not this node is currently mining. Always false. +func (e *PublicEthAPI) Mining() bool { + return false +} + +// Hashrate returns the current node's hashrate. Always 0. +func (e *PublicEthAPI) Hashrate() hexutil.Uint64 { + return 0 +} + +// GasPrice returns the current gas price based on Ethermint's gas price oracle. +func (e *PublicEthAPI) GasPrice() *hexutil.Big { + out := big.NewInt(0) + return (*hexutil.Big)(out) +} + +// Accounts returns the list of accounts available to this node. +func (e *PublicEthAPI) Accounts() []common.Address { + return nil +} + +// BlockNumber returns the current block number. +func (e *PublicEthAPI) BlockNumber() *big.Int { + return big.NewInt(0) +} + +// GetBalance returns the provided account's balance up to the provided block number. +func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpc.BlockNumber) *hexutil.Big { + out := big.NewInt(0) + return (*hexutil.Big)(out) +} + +// GetStorageAt returns the contract storage at the given address, block number, and key. +func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum rpc.BlockNumber) hexutil.Bytes { + return nil +} + +// GetTransactionCount returns the number of transactions at the given address up to the given block number. +func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpc.BlockNumber) hexutil.Uint64 { + return 0 +} + +// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. +func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) hexutil.Uint { + return 0 +} + +// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. +func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) hexutil.Uint { + return 0 +} + +// GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero. +func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint { + return 0 +} + +// GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero. +func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum rpc.BlockNumber) hexutil.Uint { + return 0 +} + +// GetCode returns the contract code at the given address and block number. +func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpc.BlockNumber) hexutil.Bytes { + return nil +} + +// Sign signs the provided data using the private key of address via Geth's signature standard. +func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) hexutil.Bytes { + return nil +} + +// SendTransaction sends an Ethereum transaction. +func (e *PublicEthAPI) SendTransaction(args core.SendTxArgs) common.Hash { + var h common.Hash + return h +} + +// SendRawTransaction send a raw Ethereum transaction. +func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) common.Hash { + var h common.Hash + return h +} + +// CallArgs represents arguments to a smart contract call as provided by RPC clients. +type CallArgs struct { + From common.Address `json:"from"` + To common.Address `json:"to"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice hexutil.Big `json:"gasPrice"` + Value hexutil.Big `json:"value"` + Data hexutil.Bytes `json:"data"` +} + +// Call performs a raw contract call. +func (e *PublicEthAPI) Call(args CallArgs, blockNum rpc.BlockNumber) hexutil.Bytes { + return nil +} + +// EstimateGas estimates gas usage for the given smart contract call. +func (e *PublicEthAPI) EstimateGas(args CallArgs, blockNum rpc.BlockNumber) hexutil.Uint64 { + return 0 +} + +// GetBlockByHash returns the block identified by hash. +func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) map[string]interface{} { + return nil +} + +// GetBlockByNumber returns the block identified by number. +func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) map[string]interface{} { + return nil +} + +// Transaction represents a transaction returned to RPC clients. +type Transaction struct { + BlockHash common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex hexutil.Uint `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` +} + +// GetTransactionByHash returns the transaction identified by hash. +func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) *Transaction { + return nil +} + +// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. +func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) *Transaction { + return nil +} + +// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. +func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNumber rpc.BlockNumber, idx hexutil.Uint) *Transaction { + return nil +} + +// GetTransactionReceipt returns the transaction receipt identified by hash. +func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) map[string]interface{} { + return nil +} + +// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. +func (e *PublicEthAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} { + return nil +} + +// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil. +func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} { + return nil +} diff --git a/server/rpc/rpc.go b/server/rpc/rpc.go new file mode 100644 index 00000000..df621eb6 --- /dev/null +++ b/server/rpc/rpc.go @@ -0,0 +1,33 @@ +package rpc + +import ( + "context" + "fmt" + "github.com/ethereum/go-ethereum/rpc" +) + +// StartHTTPEndpoint starts the Tendermint Web3-compatible RPC layer. Consumes a Context for cancellation, a config +// struct, and a list of rpc.API interfaces that will be automatically wired into a JSON-RPC webserver. +func StartHTTPEndpoint(ctx context.Context, config *Config, apis []rpc.API) (*rpc.Server, error) { + uniqModules := make(map[string]string) + for _, api := range apis { + uniqModules[api.Namespace] = api.Namespace + } + modules := make([]string, len(uniqModules)) + i := 0 + for k := range uniqModules { + modules[i] = k + i++ + } + + endpoint := fmt.Sprintf("%s:%d", config.RPCAddr, config.RPCPort) + _, server, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, config.RPCCORSDomains, config.RPCVHosts) + + go func() { + <-ctx.Done() + fmt.Println("Shutting down server.") + server.Stop() + }() + + return server, err +} diff --git a/server/rpc/rpc_test.go b/server/rpc/rpc_test.go new file mode 100644 index 00000000..fcf50192 --- /dev/null +++ b/server/rpc/rpc_test.go @@ -0,0 +1,74 @@ +package rpc + +import ( + "context" + "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" + "io/ioutil" + "math/rand" + "net/http" + "strings" + "testing" +) + +type TestService struct{} + +func (s *TestService) Foo(arg string) string { + return arg +} + +func TestStartHTTPEndpointStartStop(t *testing.T) { + config := &Config{ + RPCAddr: "127.0.0.1", + RPCPort: randomPort(), + } + ctx, cancel := context.WithCancel(context.Background()) + _, err := StartHTTPEndpoint(ctx, config, []rpc.API{ + { + Namespace: "test", + Version: "1.0", + Service: &TestService{}, + Public: true, + }, + }) + require.Nil(t, err, "unexpected error") + res, err := rpcCall(config.RPCPort, "test_foo", []string{"baz"}) + require.Nil(t, err, "unexpected error") + resStr := res.(string) + require.Equal(t, "baz", resStr) + cancel() + _, err = rpcCall(config.RPCPort, "test_foo", []string{"baz"}) + require.NotNil(t, err) +} + +func rpcCall(port int, method string, params []string) (interface{}, error) { + parsedParams, err := json.Marshal(params) + if err != nil { + return nil, err + } + fullBody := fmt.Sprintf(`{ "id": 1, "jsonrpc": "2.0", "method": "%s", "params": %s }`, + method, string(parsedParams)) + res, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d", port), "application/json", strings.NewReader(fullBody)) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + var out map[string]interface{} + err = json.Unmarshal(data, &out) + if err != nil { + return nil, err + } + + result := out["result"].(interface{}) + return result, nil +} + +func randomPort() int { + return rand.Intn(65535-1025) + 1025 +} diff --git a/server/start.go b/server/start.go deleted file mode 100644 index abb4e431..00000000 --- a/server/start.go +++ /dev/null @@ -1 +0,0 @@ -package server diff --git a/types/account.go b/types/account.go new file mode 100644 index 00000000..b82808c4 --- /dev/null +++ b/types/account.go @@ -0,0 +1,53 @@ +package types + +import ( + 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" +) + +var _ auth.Account = (*Account)(nil) + +type ( + // Storage defines account storage + Storage map[ethcmn.Hash]ethcmn.Hash + + // Account defines an auth.BaseAccount extension for Ethermint. It is + // compatible with the auth.AccountMapper. + Account struct { + auth.BaseAccount + + Code []byte + Storage Storage + } +) + +// NewAccount returns a reference to a new initialized account. +func NewAccount(base auth.BaseAccount, code []byte, storage Storage) *Account { + return &Account{ + BaseAccount: base, + Code: code, + Storage: storage, + } +} + +// GetAccountDecoder returns the auth.AccountDecoder function for the custom +// Account type. +func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder { + return func(accBytes []byte) (auth.Account, error) { + if len(accBytes) == 0 { + return nil, sdk.ErrTxDecode("account bytes are empty") + } + + acc := new(Account) + + err := cdc.UnmarshalBinaryBare(accBytes, &acc) + if err != nil { + return nil, sdk.ErrTxDecode("failed to decode account bytes") + } + + return acc, err + } +} diff --git a/types/test_common.go b/types/test_common.go index d344ede4..6dc76eb6 100644 --- a/types/test_common.go +++ b/types/test_common.go @@ -1,3 +1,4 @@ +// nolint package types import ( @@ -7,119 +8,113 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - stake "github.com/cosmos/cosmos-sdk/x/stake/types" - - ethcrypto "github.com/ethereum/go-ethereum/crypto" - ethtypes "github.com/ethereum/go-ethereum/core/types" ethcmn "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + ethcrypto "github.com/ethereum/go-ethereum/crypto" ) var ( - TestChainID = sdk.NewInt(3) + TestSDKAddr = GenerateEthAddress() + TestChainID = big.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) + auth.RegisterWire(codec) + wire.RegisterCrypto(codec) codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) - // Register any desired SDK msgs to be embedded - stake.RegisterWire(codec) - return codec } -func NewStdFee() auth.StdFee { - return auth.NewStdFee(5000, sdk.NewCoin("photon", 150)) +func NewTestStdFee() auth.StdFee { + return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) } -func NewTestEmbeddedTx( - chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, - accNums []int64, seqs []int64, fee auth.StdFee, +func NewTestStdTx( + chainID *big.Int, msgs []sdk.Msg, accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee, ) sdk.Tx { - sigs := make([][]byte, len(pKeys)) + sigs := make([]auth.StdSignature, len(pKeys)) for i, priv := range pKeys { - signEtx := EmbeddedTxSign{chainID.String(), accNums[i], seqs[i], msgs, fee} - - signBytes, err := signEtx.Bytes() - if err != nil { - panic(err) - } + signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "") sig, err := ethcrypto.Sign(signBytes, priv) if err != nil { panic(err) } - sigs[i] = sig + sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} } - return EmbeddedTx{msgs, fee, sigs} + return auth.NewStdTx(msgs, fee, sigs, "") } -func NewTestGethTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []ethtypes.Transaction { - txs := make([]ethtypes.Transaction, len(pKeys)) +func NewTestGethTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*ethtypes.Transaction { - for i, priv := range pKeys { + txs := make([]*ethtypes.Transaction, len(pKeys)) + + for i, privKey := range pKeys { ethTx := ethtypes.NewTransaction( - uint64(i), addrs[i], big.NewInt(10), 100, big.NewInt(100), nil, + uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{}, ) - signer := ethtypes.NewEIP155Signer(chainID.BigInt()) - ethTx, _ = ethtypes.SignTx(ethTx, signer, priv) + signer := ethtypes.NewEIP155Signer(chainID) - txs[i] = *ethTx + ethTx, err := ethtypes.SignTx(ethTx, signer, privKey) + if err != nil { + panic(err) + } + + txs[i] = ethTx } return txs } -func NewTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction { - txs := make([]Transaction, len(pKeys)) +func NewTestEthTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*Transaction { - for i, priv := range pKeys { - emintTx := NewTransaction( - uint64(i), addrs[i], sdk.NewInt(10), 1000, sdk.NewInt(100), nil, + 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{}, ) - emintTx.Sign(chainID, priv) - - txs[i] = emintTx + ethTx.Sign(chainID, privKey) + txs[i] = ethTx } return txs } func NewTestSDKTxs( - codec *wire.Codec, chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, - accNums []int64, seqs []int64, fee auth.StdFee, -) []Transaction { + codec *wire.Codec, chainID *big.Int, to ethcmn.Address, msgs []sdk.Msg, + accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee, +) []*Transaction { - txs := make([]Transaction, len(pKeys)) - etx := NewTestEmbeddedTx(chainID, msgs, pKeys, accNums, seqs, fee) + txs := make([]*Transaction, len(pKeys)) + stdTx := NewTestStdTx(chainID, msgs, accNums, seqs, pKeys, fee) + payload := codec.MustMarshalBinary(stdTx) - for i, priv := range pKeys { - payload := codec.MustMarshalBinary(etx) + for i, privKey := range pKeys { + ethTx := NewTransaction(uint64(seqs[i]), to, big.NewInt(10), 1000, big.NewInt(100), payload) - emintTx := NewTransaction( - uint64(i), TestSDKAddress, sdk.NewInt(10), 1000, - sdk.NewInt(100), payload, - ) - - emintTx.Sign(TestChainID, priv) - - txs[i] = emintTx + ethTx.Sign(chainID, privKey) + txs[i] = ethTx } return txs diff --git a/types/tx.go b/types/tx.go index 9fcfe1fe..00a696c9 100644 --- a/types/tx.go +++ b/types/tx.go @@ -3,21 +3,18 @@ package types import ( "bytes" "crypto/ecdsa" - "crypto/sha256" - "encoding/json" "fmt" + "io" "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" ) @@ -30,16 +27,18 @@ const ( // Ethereum transaction // ---------------------------------------------------------------------------- +var _ sdk.Tx = (*Transaction)(nil) + type ( // Transaction implements the Ethereum transaction structure as an exact - // copy. It implements the Cosmos sdk.Tx interface. Due to the private + // 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 + data TxData // caches hash atomic.Value @@ -52,100 +51,131 @@ type ( // except for the payload field which may embed a Cosmos SDK transaction. TxData struct { AccountNonce uint64 `json:"nonce"` - Price sdk.Int `json:"gasPrice"` + Price *big.Int `json:"gasPrice"` GasLimit uint64 `json:"gas"` - Recipient *ethcmn.Address `json:"to"` // nil means contract creation - Amount sdk.Int `json:"value"` + Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation + Amount *big.Int `json:"value"` Payload []byte `json:"input"` - Signature *EthSignature `json:"signature"` + + // 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"` + Hash *ethcmn.Hash `json:"hash" rlp:"-"` } - // EthSignature reflects an Ethereum signature. We wrap this in a structure - // to support Amino serialization of transactions. - EthSignature struct { - v, r, s *big.Int - } - - // sigCache is used to cache the derived sender and contains - // the signer used to derive it. + // sigCache is used to cache the derived sender and contains the signer used + // to derive it. sigCache struct { signer ethtypes.Signer from ethcmn.Address } ) -// 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. +// NewTransaction 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 { + 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{ - Recipient: &to, AccountNonce: nonce, + Recipient: to, Payload: payload, GasLimit: gasLimit, - Amount: amount, - Price: gasPrice, - Signature: NewEthSignature(new(big.Int), new(big.Int), new(big.Int)), + Amount: new(big.Int), + Price: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), } - return Transaction{Data: txData} + 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 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), - }) +func (tx *Transaction) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) { + txHash := tx.SigHash(chainID) - sig, err := ethcrypto.Sign(h[:], priv) + sig, err := ethcrypto.Sign(txHash[:], priv) if err != nil { panic(err) } @@ -162,17 +192,20 @@ func (tx *Transaction) Sign(chainID sdk.Int, priv *ecdsa.PrivateKey) { 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)) + chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) v.Add(v, chainIDMul) } - tx.Data.Signature.v = v - tx.Data.Signature.r = r - tx.Data.Signature.s = s + 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 @@ -183,24 +216,15 @@ func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) { } } - // Do not allow unprotected chainID + // do not allow recovery for transactions with an unprotected chainID if chainID.Sign() == 0 { - return ethcmn.Address{}, errors.New("Cannot have 0 as ChainID") + return ethcmn.Address{}, errors.New("invalid chainID") } - - signBytes := rlpHash([]interface{}{ - tx.Data.AccountNonce, - tx.Data.Price.BigInt(), - tx.Data.GasLimit, - tx.Data.Recipient, - tx.Data.Amount.BigInt(), - tx.Data.Payload, - chainID, uint(0), uint(0), - }) - sig := recoverEthSig(tx.Data.Signature, chainID) + txHash := tx.SigHash(chainID) + sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID) - pub, err := ethcrypto.Ecrecover(signBytes[:], sig) + pub, err := ethcrypto.Ecrecover(txHash[:], sig) if err != nil { return ethcmn.Address{}, err } @@ -221,11 +245,11 @@ func (tx Transaction) Type() string { // 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 { + if tx.data.Price.Sign() != 1 { return ErrInvalidValue(DefaultCodespace, "price must be positive") } - if tx.Data.Amount.Sign() != 1 { + if tx.data.Amount.Sign() != 1 { return ErrInvalidValue(DefaultCodespace, "amount must be positive") } @@ -248,10 +272,10 @@ func (tx Transaction) GetMsgs() []sdk.Msg { return []sdk.Msg{tx} } -// HasEmbeddedTx returns a boolean reflecting if the transaction contains an +// 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()) +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 @@ -259,147 +283,40 @@ func (tx Transaction) HasEmbeddedTx(addr ethcmn.Address) bool { // // 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{} +func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) { + var etx sdk.Tx - err := codec.UnmarshalBinary(tx.Data.Payload, &etx) + err := codec.UnmarshalBinary(tx.data.Payload, &etx) if err != nil { - return EmbeddedTx{}, sdk.ErrTxDecode("failed to encode embedded tx") + return etx, sdk.ErrTxDecode("failed to decode embedded transaction") } return etx, nil } -// Copies Ethereum tx's Protected function -func (tx Transaction) protected() bool { - if tx.Data.Signature.v.BitLen() <= 8 { - v := tx.Data.Signature.v.Uint64() - return v != 27 && v != 28 - } - return true -} - -// ---------------------------------------------------------------------------- -// 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. +// 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 *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") + return nil, sdk.ErrTxDecode("transaction bytes are empty") } - // The given codec should have all the appropriate message types - // registered. - err := codec.UnmarshalBinary(txBytes, &tx) + err := rlp.DecodeBytes(txBytes, &tx) if err != nil { - return nil, sdk.ErrTxDecode("failed to decode tx").TraceSDK(err.Error()) + 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 transaction. - if tx.HasEmbeddedTx(sdkAddress) { + // 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 @@ -413,20 +330,20 @@ func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder { } // recoverEthSig recovers a signature according to the Ethereum specification. -func recoverEthSig(es *EthSignature, chainID *big.Int) []byte { +func recoverEthSig(R, S, Vb, chainID *big.Int) []byte { var v byte - r, s := es.r.Bytes(), es.s.Bytes() + 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(es.v.Uint64() - 27) + v = byte(Vb.Uint64() - 27) } else { chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) - V := new(big.Int).Sub(es.v, chainIDMul) + V := new(big.Int).Sub(Vb, chainIDMul) v = byte(V.Uint64() - 35) } @@ -435,43 +352,11 @@ func recoverEthSig(es *EthSignature, chainID *big.Int) []byte { return sig } -func rlpHash(x interface{}) (h ethcmn.Hash) { +func rlpHash(x interface{}) (hash 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 - } + hasher.Sum(hash[:0]) return } diff --git a/types/tx_test.go b/types/tx_test.go index f4a553ce..0df8a8a7 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -3,18 +3,57 @@ 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, - []*ecdsa.PrivateKey{TestPrivKey1}, - []ethcmn.Address{TestAddr1}, + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) testCases := []struct { @@ -24,13 +63,13 @@ func TestValidation(t *testing.T) { }{ {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) + 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 = sdk.NewInt(-1) + tx := msg.(*Transaction) + tx.data.Amount = big.NewInt(-1) return tx }, true}, } @@ -47,147 +86,57 @@ func TestValidation(t *testing.T) { } } -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(), +func TestTransactionVerifySig(t *testing.T) { + txs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) - etx, err := sdkTxs[0].GetEmbeddedTx(testCodec) + addr, err := txs[0].VerifySig(TestChainID) require.NoError(t, err) - require.NotEmpty(t, etx.Messages) + require.Equal(t, TestAddr1, addr) - etx, err = ethTxs[0].GetEmbeddedTx(testCodec) + addr, err = txs[0].VerifySig(big.NewInt(100)) 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 TestVerifySig(t *testing.T) { - ethTx := NewTestEthTxs( - TestChainID, - []*ecdsa.PrivateKey{TestPrivKey1}, - []ethcmn.Address{TestAddr1}, - )[0] - - addr, err := ethTx.VerifySig(TestChainID.BigInt()) - - require.Nil(t, err, "Sig verification failed") - require.Equal(t, TestAddr1, addr, "Address is not the same") + require.NotEqual(t, TestAddr1, addr) } func TestTxDecoder(t *testing.T) { testCodec := NewTestCodec() - txDecoder := TxDecoder(testCodec, TestSDKAddress) - msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(TestAddr1.Bytes()))} + txDecoder := TxDecoder(testCodec, TestSDKAddr) + msgs := []sdk.Msg{sdk.NewTestMsg()} // create a non-SDK Ethereum transaction - emintTx := NewTransaction( - uint64(0), TestAddr1, sdk.NewInt(10), 100, sdk.NewInt(100), nil, + txs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) - emintTx.Sign(TestChainID, TestPrivKey1) + + txBytes, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) // require the transaction to properly decode into a Transaction - txBytes := testCodec.MustMarshalBinary(emintTx) - tx, err := txDecoder(txBytes) + decodedTx, err := txDecoder(txBytes) require.NoError(t, err) - require.Equal(t, emintTx, tx) + require.IsType(t, Transaction{}, decodedTx) + require.Equal(t, txs[0].data, (decodedTx.(Transaction)).data) - // create embedded transaction and encode - etx := NewTestEmbeddedTx( - TestChainID, msgs, []*ecdsa.PrivateKey{TestPrivKey1}, - []int64{0}, []int64{0}, NewStdFee(), + // create a SDK (auth.StdTx) transaction and encode + txs = NewTestSDKTxs( + testCodec, TestChainID, TestSDKAddr, msgs, []int64{0}, []int64{0}, + []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee(), ) - 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) + txBytes, err = rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) // require the transaction to properly decode into a Transaction - txBytes = testCodec.MustMarshalBinary(emintTx) - tx, err = txDecoder(txBytes) + stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee()) + decodedTx, err = txDecoder(txBytes) require.NoError(t, err) - require.Equal(t, expectedEtx, tx) + require.IsType(t, auth.StdTx{}, decodedTx) + require.Equal(t, stdTx, decodedTx) // require the decoding to fail when no transaction bytes are given - tx, err = txDecoder([]byte{}) + decodedTx, 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) + require.Nil(t, decodedTx) } diff --git a/types/utils.go b/types/utils.go index 01f8261d..7135a583 100644 --- a/types/utils.go +++ b/types/utils.go @@ -2,8 +2,11 @@ package types import ( "crypto/ecdsa" + "crypto/sha256" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" ethcmn "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" @@ -32,10 +35,18 @@ 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") + return errors.Wrap(err, "failed to derive public key from signature") } else if ethcrypto.PubkeyToAddress(*pk) != signer { return fmt.Errorf("invalid signature for signer: %s", signer) } return nil } + +// GetStdTxSignBytes returns the signature bytes for an auth.StdTx transaction +// that is compatible with Ethereum's signature mechanism. +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, "") + hash := sha256.Sum256(signBytes) + return hash[:] +} diff --git a/types/utils_test.go b/types/utils_test.go index 430c5b03..12e27d8f 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -12,12 +12,8 @@ import ( 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) + // 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) @@ -33,4 +29,8 @@ func TestValidateSigner(t *testing.T) { // 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) } diff --git a/types/wire.go b/types/wire.go index 818b1742..ad1f2e30 100644 --- a/types/wire.go +++ b/types/wire.go @@ -3,7 +3,6 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" ) var typesCodec = wire.NewCodec() @@ -16,10 +15,6 @@ func init() { // codec. func RegisterWire(codec *wire.Codec) { sdk.RegisterWire(codec) - wire.RegisterCrypto(codec) - auth.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) + codec.RegisterConcrete(&Transaction{}, "types/Transaction", nil) + codec.RegisterConcrete(&Account{}, "types/Account", nil) } diff --git a/version/version.go b/version/version.go index 44d4e60f..015a5b96 100644 --- a/version/version.go +++ b/version/version.go @@ -1,10 +1,26 @@ package version +import ( + "fmt" + "runtime" +) + +// AppName represents the application name as the 'user agent' on the larger Ethereum network. +const AppName = "Ethermint" + // Version contains the application semantic version. // // TODO: How do we want to version this being that an initial Ethermint has // been developed? const Version = "0.0.0" +// ProtocolVersion is the supported Ethereum protocol version (e.g., Homestead, Olympic, etc.) +const ProtocolVersion = "63" + // GitCommit contains the git SHA1 short hash set by build flags. var GitCommit = "" + +// ClientVersion returns the full version string for identification on the larger Ethereum network. +func ClientVersion() string { + return fmt.Sprintf("%s/%s+%s/%s/%s", AppName, Version, GitCommit, runtime.GOOS, runtime.Version()) +}