From 8f58d23a99e39984986ab86bd6710d10a31ef27d Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 16 Aug 2018 12:05:14 -0400 Subject: [PATCH 01/16] Implement into documentation --- docs/intro/README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/intro/README.md diff --git a/docs/intro/README.md b/docs/intro/README.md new file mode 100644 index 00000000..0fac0cee --- /dev/null +++ b/docs/intro/README.md @@ -0,0 +1,46 @@ +# Introduction + +## Preliminary + +### 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. + +## 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. + +Here’s a glance at some of the key features of Ethermint: + +* Web3 compatibility +* High throughput +* Horizontal scalability +* Transaction finality + +Ethermint achieves these key features by implementing Tendermint's ABCI application +interface, leveraging modules and mechanisms implemented by the Cosmos SDK, utilizing +[Geth](https://github.com/ethereum/go-ethereum) as a library by implementing all +necessary interfaces, and finally by exposing a fully compatible Web3 RPC layer +allowing developers to leverage existing Ethereum ecosystem tooling and software +to seamlessly deploy smart contracts and interact with the rest of the Cosmos +ecosystem! From 63c160977bd78da23180b92221258984bd3f55ef Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 20 Aug 2018 08:33:08 -0400 Subject: [PATCH 02/16] Add transaction doc/spec --- docs/spec/transactions/README.md | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/spec/transactions/README.md diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md new file mode 100644 index 00000000..924bc787 --- /dev/null +++ b/docs/spec/transactions/README.md @@ -0,0 +1,67 @@ +# 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 `Transaction.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 +embedded encoded type: `EmbeddedTx`. This structure is analogous to the Cosmos +SDK `sdk.StdTx`. If a client wishes to send an `EmbeddedTx`, it must first encode +the embedded transaction, and then encode the embedding `Transaction`. + +__Note__: The `Transaction` and `EmbeddedTx` types utilize the [Amino](https://github.com/tendermint/go-amino) object serialization protocol and as such, +the `Transaction` is not an exact replica of what will be found in Ethereum. 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. + +Being that Ethermint implements the ABCI application interface, as transactions +are sent 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. + +## Transactions & Messages + +The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`). +A `sdk.Tx` is a list of `sdk.Msg`s 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. + +The `EmbeddedTx`, being analogous to the Cosmos SDK `sdk.StdTx`, implements the +Cosmos SDK `sdk.Tx` interface. + +## Signatures + +Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) +signatures. However, just as in Cosmos, Ethermint will support multiple signers. +A client is expected to sign the `Transaction` just as in Ethereum, however, the +`EmbeddedTx` contains a canonical signature structure that itself contains the +signature and other information such as an account's sequence number. The sequence +number is expected to increment every time a message is signed by a given account. +This prevents "replay attacks", where the same message could be executed over and +over again. + +An `EmbeddedTx` 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 +`EmbeddedTx` is responsible for paying the fees and must also match the address of +the signer of the embedding `Transaction`. As such, there will be one duplicate +signature. + +## Gas & Fees + +TODO From 94b84b484c2fcbb42daf99b209c710c227ea9538 Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Mon, 20 Aug 2018 19:21:02 -0700 Subject: [PATCH 03/16] Change copy in intro/README --- docs/intro/README.md | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/intro/README.md b/docs/intro/README.md index 0fac0cee..61ef3acb 100644 --- a/docs/intro/README.md +++ b/docs/intro/README.md @@ -1,6 +1,34 @@ # Introduction -## Preliminary +## 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) @@ -20,27 +48,3 @@ 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. - -## 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. - -Here’s a glance at some of the key features of Ethermint: - -* Web3 compatibility -* High throughput -* Horizontal scalability -* Transaction finality - -Ethermint achieves these key features by implementing Tendermint's ABCI application -interface, leveraging modules and mechanisms implemented by the Cosmos SDK, utilizing -[Geth](https://github.com/ethereum/go-ethereum) as a library by implementing all -necessary interfaces, and finally by exposing a fully compatible Web3 RPC layer -allowing developers to leverage existing Ethereum ecosystem tooling and software -to seamlessly deploy smart contracts and interact with the rest of the Cosmos -ecosystem! From 32504a866f641a5a2dc762028dd9ee2d4662f5f5 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 9 Aug 2018 03:36:47 -0700 Subject: [PATCH 04/16] Start RPC API implementation --- Gopkg.lock | 12 +++++ Makefile | 12 ++--- cmd/{ethermintcli => emintcli}/main.go | 0 cmd/{ethermintd => emintd}/main.go | 0 server/rpc/apis.go | 39 ++++++++++++++ server/rpc/apis_test.go | 55 +++++++++++++++++++ server/rpc/config.go | 16 ++++++ server/rpc/rpc.go | 33 ++++++++++++ server/rpc/rpc_test.go | 74 ++++++++++++++++++++++++++ server/start.go | 1 - version/version.go | 13 +++++ 11 files changed, 248 insertions(+), 7 deletions(-) rename cmd/{ethermintcli => emintcli}/main.go (100%) rename cmd/{ethermintd => emintd}/main.go (100%) create mode 100644 server/rpc/apis.go create mode 100644 server/rpc/apis_test.go create mode 100644 server/rpc/config.go create mode 100644 server/rpc/rpc.go create mode 100644 server/rpc/rpc_test.go delete mode 100644 server/start.go diff --git a/Gopkg.lock b/Gopkg.lock index 4b8fd799..c40e628a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -237,6 +237,7 @@ packages = [ "assert", "require", + "suite", ] pruneopts = "T" revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" @@ -275,6 +276,14 @@ pruneopts = "T" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" +[[projects]] + branch = "master" + digest = "1:3a6bdd02e7f2585c860e368467c5989310740af6206a1ada85cfa19c712e5afd" + name = "github.com/tendermint/ethermint" + packages = ["version"] + pruneopts = "T" + revision = "c1e6ebf80a6cc9119bc178faee18ef13490d707a" + [[projects]] digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245" name = "github.com/tendermint/go-amino" @@ -437,6 +446,7 @@ "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/hexutil", "github.com/ethereum/go-ethereum/common/math", "github.com/ethereum/go-ethereum/consensus", "github.com/ethereum/go-ethereum/consensus/ethash", @@ -455,6 +465,8 @@ "github.com/hashicorp/golang-lru", "github.com/pkg/errors", "github.com/stretchr/testify/require", + "github.com/stretchr/testify/suite", + "github.com/tendermint/ethermint/version", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", 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/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/server/rpc/apis.go b/server/rpc/apis.go new file mode 100644 index 00000000..ae30f0f9 --- /dev/null +++ b/server/rpc/apis.go @@ -0,0 +1,39 @@ +// Package rpc contains RPC handler methods and utilities to start +// Ethermint's Web3-compatibly JSON-RPC server. +package rpc + +import ( + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rpc" + "github.com/cosmos/ethermint/version" +) + +// 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(), + }, + } +} + +// PublicWeb3API is the web3_ prefixed set of APIs in the WEB3 JSON-RPC spec. +type PublicWeb3API struct { +} + +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..f58c2a38 --- /dev/null +++ b/server/rpc/apis_test.go @@ -0,0 +1,55 @@ +package rpc + +import ( + "context" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "github.com/cosmos/ethermint/version" + "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 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..c79f58df --- /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/rpc.go b/server/rpc/rpc.go new file mode 100644 index 00000000..641f19f2 --- /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/version/version.go b/version/version.go index 44d4e60f..ae4e0650 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,13 @@ 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 @@ -8,3 +16,8 @@ const Version = "0.0.0" // 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()) +} From 712d1d7c34b2c10e272c1fc685dd237edb616002 Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Tue, 21 Aug 2018 15:22:50 -0700 Subject: [PATCH 05/16] Update README --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) 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 From 38c228cc1a2b238270a46a2ca3f452e7912a9fa8 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 22 Aug 2018 09:53:27 -0400 Subject: [PATCH 06/16] Address PR reviews in regards to transaction sigs --- docs/spec/transactions/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md index 924bc787..2121cee4 100644 --- a/docs/spec/transactions/README.md +++ b/docs/spec/transactions/README.md @@ -48,19 +48,19 @@ Cosmos SDK `sdk.Tx` interface. ## Signatures Ethermint supports [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) -signatures. However, just as in Cosmos, Ethermint will support multiple signers. -A client is expected to sign the `Transaction` just as in Ethereum, however, the -`EmbeddedTx` contains a canonical signature structure that itself contains the -signature and other information such as an account's sequence number. The sequence -number is expected to increment every time a message is signed by a given account. -This prevents "replay attacks", where the same message could be executed over and -over again. +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 `EmbeddedTx` Cosmos routed transactions. Signatures over the +`Transaction` type are identical to Ethereum. However, the `EmbeddedTx` contains +a canonical signature structure that contains the signature itself and other +information such as an account's sequence number. The sequence number is expected +to increment every time a message is signed by a given account. This, in addition +to the chain ID, prevents "replay attacks", where the same message could be +executed over and over again. -An `EmbeddedTx` 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 -`EmbeddedTx` is responsible for paying the fees and must also match the address of -the signer of the embedding `Transaction`. As such, there will be one duplicate -signature. +An `EmbeddedTx` 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 `EmbeddedTx` is responsible for paying the fees. ## Gas & Fees From cd593e3e3758766b3564f9fb0c917444dcce77ca Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 24 Aug 2018 08:29:10 -0700 Subject: [PATCH 07/16] Remove EmbeddedTx and related logic --- Gopkg.lock | 54 ++++++------- Gopkg.toml | 8 +- handlers/ante.go | 62 +++++++-------- types/tx.go | 158 ++----------------------------------- types/tx_test.go | 186 ++++---------------------------------------- types/utils.go | 11 +++ types/utils_test.go | 8 +- types/wire.go | 1 - 8 files changed, 94 insertions(+), 394 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c40e628a..4cbe6077 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,19 +3,19 @@ [[projects]] branch = "master" - digest = "1:fcdf62d2d7e43c2565d6f8707ab4eae54dac702ed4bafb194b85139f0508929f" + digest = "1:79b02529d2120af44eaad5f7fee9ff7e003739d469e2c2767008b797d290e9fd" name = "github.com/aristanetworks/goarista" packages = ["monotime"] pruneopts = "T" - revision = "b2d71c282dc706f4b4f6c15b65810e1202ecd53f" + revision = "18b896026201d8e1758468d9c5d5a558c69c5e9b" [[projects]] branch = "master" - digest = "1:d4d66abd43dbb9b5f5e6a176c5ed279c289f8db734904c047d95113a04aa2e60" + digest = "1:8ad24ea05e770b745b5c286b4f94cf73d5be87005680e36b2d0dd1de0a2f9fbf" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "T" - revision = "cf05f92c3f815bbd5091ed6c73eff51f7b1945e8" + revision = "d81d8877b8f327112e94e814937143a71d1692a7" [[projects]] digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533" @@ -25,7 +25,7 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:36773b598dec105de46a87978ae14e64c8d2c45aa556b8e0ddfc62d6abc7c47e" + digest = "1:bc28e755cf6a9fd8e65497514d20c4907973e7a6a6409d30ead3fd37bfeb19a9" name = "github.com/cosmos/cosmos-sdk" packages = [ "baseapp", @@ -36,16 +36,16 @@ "x/auth", ] pruneopts = "T" - revision = "23e3d5ac12145c02fcb4b4767d7dfccad782aee5" - version = "v0.23.1" + revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9" + version = "v0.24.2" [[projects]] - digest = "1:52f195ad0e20a92d8604c1ba3cd246c61644c03eaa454b5acd41be89841e0d10" + digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "T" - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] branch = "master" @@ -224,12 +224,12 @@ version = "v0.0.3" [[projects]] - digest = "1:6de2f73eb31e80d74f84ce1c861e4c0c8f00ca5fb41a25901f987e63a0647c28" + digest = "1:9ba911fe3884995431690e7eb180cf848da0d637ba5f61711783b795d031793f" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "T" - revision = "583c0c0531f06d5278b7d917446061adc344b5cd" - version = "v1.0.1" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" [[projects]] digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e" @@ -245,7 +245,7 @@ [[projects]] branch = "master" - digest = "1:7d44c4d11eb65cfdc78c76040f37ef305b16474c019c98a8a7cf188fece2d574" + digest = "1:ee395d0d8c1719b5a1407f34af93953b4763bacb19a8961aba5b6d312824da41" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -262,7 +262,7 @@ "leveldb/util", ] pruneopts = "T" - revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" [[projects]] branch = "master" @@ -276,14 +276,6 @@ pruneopts = "T" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" -[[projects]] - branch = "master" - digest = "1:3a6bdd02e7f2585c860e368467c5989310740af6206a1ada85cfa19c712e5afd" - name = "github.com/tendermint/ethermint" - packages = ["version"] - pruneopts = "T" - revision = "c1e6ebf80a6cc9119bc178faee18ef13490d707a" - [[projects]] digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245" name = "github.com/tendermint/go-amino" @@ -301,7 +293,7 @@ version = "v0.9.2" [[projects]] - digest = "1:9f6704ae2aedbadf616e5850375c504909d46b6ea57d4679de2b7cbc715f08e1" + digest = "1:5a60cb048b401c0263c227baf8778ecaf038be531707057607949540486874ef" name = "github.com/tendermint/tendermint" packages = [ "abci/server", @@ -321,16 +313,16 @@ "types", ] pruneopts = "T" - revision = "d542d2c3945116697f60451e6a407082c41c3cc9" - version = "v0.22.8" + revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e" + version = "v0.23.1-rc0" [[projects]] branch = "master" - digest = "1:2cbe8758697d867fcebf73bcc69dff8e8abaa7fd65e5704e0744e522ccff4e6a" + digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd" name = "golang.org/x/crypto" packages = ["ripemd160"] pruneopts = "T" - revision = "f027049dab0ad238e394a753dba2d14753473a04" + revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" [[projects]] digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7" @@ -372,11 +364,12 @@ version = "v0.3.0" [[projects]] - digest = "1:8cfa91d1b7f6b66fa9b1a738a4bc1325837b861e63fb9a2919931d68871bb770" + branch = "master" + digest = "1:960f1fa3f12667fe595c15c12523718ed8b1b5428c83d70da54bb014da9a4c1a" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "T" - revision = "7fd901a49ba6a7f87732eb344f6e3c5b19d1b200" + revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" [[projects]] digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770" @@ -466,7 +459,6 @@ "github.com/pkg/errors", "github.com/stretchr/testify/require", "github.com/stretchr/testify/suite", - "github.com/tendermint/ethermint/version", "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", diff --git a/Gopkg.toml b/Gopkg.toml index 4a6a8e23..77ce1e80 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/handlers/ante.go b/handlers/ante.go index 09ad13c1..a1b69d24 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -26,11 +26,10 @@ 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. +// 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) sdk.AnteHandler { return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) { var ( @@ -42,7 +41,7 @@ func AnteHandler(am auth.AccountMapper) sdk.AnteHandler { case types.Transaction: gasLimit = int64(tx.Data.GasLimit) handler = handleEthTx - case types.EmbeddedTx: + case auth.StdTx: gasLimit = tx.Fee.Gas handler = handleEmbeddedTx default: @@ -107,23 +106,23 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk.Cont // 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) + signerAcc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) if err.Code() != sdk.CodeOK { return sdkCtx, err.Result(), false } @@ -136,18 +135,18 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk 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") } @@ -156,8 +155,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() @@ -167,28 +166,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/types/tx.go b/types/tx.go index a7043d63..d377779b 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,17 +1,13 @@ 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" @@ -26,13 +22,9 @@ const ( 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 + // 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. // @@ -47,9 +39,7 @@ type ( 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 defines internal Ethereum transaction information TxData struct { AccountNonce uint64 `json:"nonce"` Price sdk.Int `json:"gasPrice"` @@ -204,6 +194,8 @@ func (tx Transaction) GetMsgs() []sdk.Msg { // 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. +// +// TODO: To be removed in #470 func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { gethTx := ethtypes.NewTransaction( tx.Data.AccountNonce, *tx.Data.Recipient, tx.Data.Amount.BigInt(), @@ -221,139 +213,16 @@ func (tx Transaction) ConvertTx(chainID *big.Int) ethtypes.Transaction { 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 { +// attempts to decode them into a valid sdk.Tx. +func TxDecoder(codec *wire.Codec) sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, sdk.Error) { - var tx = Transaction{} - if len(txBytes) == 0 { return nil, sdk.ErrTxDecode("txBytes are empty") } + var tx sdk.Tx + // The given codec should have all the appropriate message types // registered. err := codec.UnmarshalBinary(txBytes, &tx) @@ -361,17 +230,6 @@ func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder { 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 } } diff --git a/types/tx_test.go b/types/tx_test.go index b186f7b9..7a75caa9 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -3,14 +3,12 @@ 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" ) @@ -23,64 +21,42 @@ var ( testAddr1 = PrivKeyToEthAddress(testPrivKey1) testAddr2 = PrivKeyToEthAddress(testPrivKey2) - - testSDKAddress = GenerateEthAddress() ) func newTestCodec() *wire.Codec { codec := wire.NewCodec() RegisterWire(codec) + codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil) codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) + wire.RegisterCrypto(codec) return codec } func newStdFee() auth.StdFee { - return auth.NewStdFee(5000, sdk.NewCoin("photon", 150)) + return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) } -func newTestEmbeddedTx( +func newTestStdTx( chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, accNums []int64, seqs []int64, 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, newStdFee()} - - signBytes, err := signEtx.Bytes() - if err != nil { - panic(err) - } + signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), 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} -} - -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 + return auth.NewStdTx(msgs, fee, sigs, "") } func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Address) []Transaction { @@ -99,63 +75,6 @@ func newTestEthTxs(chainID sdk.Int, pKeys []*ecdsa.PrivateKey, addrs []ethcmn.Ad 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, @@ -193,47 +112,6 @@ 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(), - ) - - 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, @@ -246,7 +124,7 @@ func TestTransactionGetMsgs(t *testing.T) { require.Equal(t, ethTxs[0], msgs[0]) expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} - etx := newTestEmbeddedTx( + etx := newTestStdTx( testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1}, []int64{0}, []int64{0}, newStdFee(), ) @@ -256,21 +134,10 @@ func TestTransactionGetMsgs(t *testing.T) { 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()))} + txDecoder := TxDecoder(testCodec) + msgs := []sdk.Msg{sdk.NewTestMsg()} // create a non-SDK Ethereum transaction emintTx := NewTransaction( @@ -284,43 +151,20 @@ func TestTxDecoder(t *testing.T) { require.NoError(t, err) require.Equal(t, emintTx, tx) - // create embedded transaction and encode - etx := newTestEmbeddedTx( + // create a SDK (auth.StdTx) transaction and encode + stdTx := newTestStdTx( 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) + txBytes = testCodec.MustMarshalBinary(stdTx) tx, err = txDecoder(txBytes) require.NoError(t, err) - require.Equal(t, expectedEtx, tx) + require.Equal(t, stdTx, 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) } diff --git a/types/utils.go b/types/utils.go index 01f8261d..9c4f5328 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" @@ -39,3 +42,11 @@ func ValidateSigner(signBytes, sig []byte, signer ethcmn.Address) error { 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 cb12c841..39c9e291 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, newStdFee(), msgs, "") // require signing not to fail sig, err := ethcrypto.Sign(signBytes, testPrivKey1) diff --git a/types/wire.go b/types/wire.go index 5414fcdc..b596a3db 100644 --- a/types/wire.go +++ b/types/wire.go @@ -18,5 +18,4 @@ func RegisterWire(codec *wire.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) } From 7d4b3ef51e4767f4b496b4567a4e471b4205cc31 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 24 Aug 2018 08:37:18 -0700 Subject: [PATCH 08/16] Update ethermint app to no longer use SDK address --- app/ethermint.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/ethermint.go b/app/ethermint.go index 575452eb..e98536f8 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -9,7 +9,6 @@ import ( "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" @@ -43,20 +42,17 @@ type ( // 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, +func NewEthermintApp(logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, opts ...Options, ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, codec, logger, db), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec)), 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) From e99e02908fd51ec785a16a9c463ffbf401c21ffa Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 24 Aug 2018 08:46:58 -0700 Subject: [PATCH 09/16] Update tx spec doc --- docs/spec/transactions/README.md | 50 ++++++++++---------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md index 2121cee4..040d00bd 100644 --- a/docs/spec/transactions/README.md +++ b/docs/spec/transactions/README.md @@ -6,34 +6,20 @@ 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 `Transaction.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. +the Cosmos hub. We attempt to achieve this by mimicking [Geth's](https://github.com/ethereum/go-ethereum) `Transaction` structure to handle +Ethereum transactions and utilizing the SDK's `auth.StdTx` for Cosmos +transactions. Both of these structures are registered with an [Amino](https://github.com/tendermint/go-amino) codec, so the `TxDecoder` that in invoked +during the `BaseApp#runTx`, will be able to decode raw transaction bytes into the +appropriate transaction type which will then be passed onto handlers downstream. -For Cosmos routed transactions, the `Transaction.Payload` will contain an -embedded encoded type: `EmbeddedTx`. This structure is analogous to the Cosmos -SDK `sdk.StdTx`. If a client wishes to send an `EmbeddedTx`, it must first encode -the embedded transaction, and then encode the embedding `Transaction`. - -__Note__: The `Transaction` and `EmbeddedTx` types utilize the [Amino](https://github.com/tendermint/go-amino) object serialization protocol and as such, -the `Transaction` is not an exact replica of what will be found in Ethereum. 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. - -Being that Ethermint implements the ABCI application interface, as transactions -are sent 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). ## Transactions & Messages The SDK distinguishes between transactions (`sdk.Tx`) and messages (`sdk.Msg`). -A `sdk.Tx` is a list of `sdk.Msg`s wrapped with authentication and fee data. Users +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. @@ -42,25 +28,21 @@ It addition, it implements the Cosmos SDK `sdk.Msg` interface for the sole purpo of being to perform basic validation checks in the `BaseApp`. It, however, has no distinction between transactions and messages. -The `EmbeddedTx`, being analogous to the Cosmos SDK `sdk.StdTx`, implements the -Cosmos SDK `sdk.Tx` interface. - ## 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 `EmbeddedTx` Cosmos routed transactions. Signatures over the -`Transaction` type are identical to Ethereum. However, the `EmbeddedTx` contains +signers for `auth.StdTx` Cosmos routed transactions. Signatures over the +`Transaction` type are identical to Ethereum. However, the `auth.StdTx` contains a canonical signature structure that contains the signature itself and other -information such as an account's sequence number. The sequence number is expected -to increment every time a message is signed by a given account. This, in addition -to the chain ID, prevents "replay attacks", where the same message could be -executed over and over again. +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 `EmbeddedTx` list of signatures must much the unique list of addresses returned +An `auth.StdTx` 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 `EmbeddedTx` is responsible for paying the fees. +the `auth.StdTx` is responsible for paying the fees. ## Gas & Fees From f93135f0f80545b416add41feac7cd422ee2c14a Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 24 Aug 2018 11:56:43 -0700 Subject: [PATCH 10/16] Implement initial ABCI logic --- app/ethermint.go | 158 +++++++++++++++++++++++++++++++++++++++-------- app/genesis.go | 47 ++++++++++++++ handlers/ante.go | 2 +- types/account.go | 53 ++++++++++++++++ types/tx.go | 2 + types/wire.go | 1 + 6 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 app/genesis.go create mode 100644 types/account.go diff --git a/app/ethermint.go b/app/ethermint.go index e98536f8..ad9bbd56 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -5,12 +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" 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" @@ -27,52 +34,146 @@ 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, opts ...Options, +func NewEthermintApp( + logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, baseAppOptions ...func(*bam.BaseApp), ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec)), - codec: codec, - accountKey: sdk.NewKVStoreKey("accounts"), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec), baseAppOptions...), + 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.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 @@ -80,7 +181,12 @@ func (app *EthermintApp) seal() { 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/handlers/ante.go b/handlers/ante.go index a1b69d24..473e1abb 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -30,7 +30,7 @@ type internalAnteHandler func( // 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) sdk.AnteHandler { +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 ( gasLimit int64 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/tx.go b/types/tx.go index d377779b..3c3b7dd2 100644 --- a/types/tx.go +++ b/types/tx.go @@ -17,6 +17,8 @@ import ( "github.com/pkg/errors" ) +var _ sdk.Tx = (*Transaction)(nil) + const ( // TypeTxEthereum reflects an Ethereum Transaction type. TypeTxEthereum = "Ethereum" diff --git a/types/wire.go b/types/wire.go index b596a3db..23f69aeb 100644 --- a/types/wire.go +++ b/types/wire.go @@ -15,6 +15,7 @@ func init() { // codec. func RegisterWire(codec *wire.Codec) { sdk.RegisterWire(codec) + codec.RegisterConcrete(&Account{}, "types/Account", nil) codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil) codec.RegisterConcrete(TxData{}, "types/TxData", nil) codec.RegisterConcrete(Transaction{}, "types/Transaction", nil) From 97b68eaa6e6bb65b5881422b6e191e9fc811fe31 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 24 Aug 2018 12:59:37 -0700 Subject: [PATCH 11/16] Fix dep version mismatch --- Gopkg.lock | 26 +++++++++++++++++++++----- Gopkg.toml | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 4cbe6077..ab790281 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,11 +11,11 @@ [[projects]] branch = "master" - digest = "1:8ad24ea05e770b745b5c286b4f94cf73d5be87005680e36b2d0dd1de0a2f9fbf" + digest = "1:b9f5e0f033febe59a62d01e78486c0dd9e4afc9ac5d240aee6ce78a927142e8b" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "T" - revision = "d81d8877b8f327112e94e814937143a71d1692a7" + revision = "79e00513b1011888b1e675157ab89f527f901cae" [[projects]] digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533" @@ -34,6 +34,16 @@ "version", "wire", "x/auth", + "x/bank", + "x/gov", + "x/gov/tags", + "x/mock", + "x/params", + "x/slashing", + "x/stake", + "x/stake/keeper", + "x/stake/tags", + "x/stake/types", ] pruneopts = "T" revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9" @@ -232,7 +242,7 @@ version = "v1.0.2" [[projects]] - digest = "1:e95496462101745805bd4e041a5b841e108c7cf761264d53648246308de2761e" + digest = "1:8f39978e4fb2a11d43cc954f2ab458cb38995d4c1557b6d3a7c8cafe0ec2277c" name = "github.com/stretchr/testify" packages = [ "assert", @@ -240,8 +250,8 @@ "suite", ] pruneopts = "T" - revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" - version = "v1.2.2" + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" [[projects]] branch = "master" @@ -438,6 +448,11 @@ "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/ethereum/go-ethereum/common", "github.com/ethereum/go-ethereum/common/hexutil", "github.com/ethereum/go-ethereum/common/math", @@ -459,6 +474,7 @@ "github.com/pkg/errors", "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", diff --git a/Gopkg.toml b/Gopkg.toml index 77ce1e80..1bd7ac6c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,7 +24,7 @@ [[constraint]] name = "github.com/stretchr/testify" - version = "=1.2.2" + version = "=1.2.1" [[constraint]] name = "github.com/pkg/errors" From e3f37126762e78511c1636134484718f3dbcc8af Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Thu, 9 Aug 2018 03:36:47 -0700 Subject: [PATCH 12/16] Stub out other RPC endpoints --- Gopkg.lock | 98 +++++++++++++++++++- server/rpc/apis.go | 10 +- server/rpc/apis_test.go | 12 ++- server/rpc/config.go | 8 +- server/rpc/eth_api.go | 196 ++++++++++++++++++++++++++++++++++++++++ server/rpc/rpc.go | 2 +- version/version.go | 3 + 7 files changed, 320 insertions(+), 9 deletions(-) create mode 100644 server/rpc/eth_api.go diff --git a/Gopkg.lock b/Gopkg.lock index ab790281..1a4101a0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -69,6 +69,12 @@ 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", @@ -86,16 +92,25 @@ "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", + "signer/core", "trie", ] pruneopts = "T" @@ -150,6 +165,7 @@ name = "github.com/golang/protobuf" packages = [ "proto", + "protoc-gen-go/descriptor", "ptypes", "ptypes/any", "ptypes/duration", @@ -177,6 +193,22 @@ 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" @@ -185,6 +217,14 @@ 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" @@ -193,6 +233,14 @@ 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" @@ -201,6 +249,14 @@ 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" @@ -217,6 +273,14 @@ revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" +[[projects]] + digest = "1:6afc433cbb471b1d32de92131245fb1f76000c9d88940265df4a512572a51b51" + name = "github.com/rjeczalik/notify" + packages = ["."] + pruneopts = "T" + revision = "52ae50d8490436622a8941bd70c3dbe0acdd4bbf" + version = "v0.9.0" + [[projects]] digest = "1:6cae6970d70fc5fe75bf83c48ee33e9c4c561a62d0b033254bee8dd5942b815a" name = "github.com/rs/cors" @@ -330,7 +394,12 @@ branch = "master" digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd" name = "golang.org/x/crypto" - packages = ["ripemd160"] + packages = [ + "pbkdf2", + "ripemd160", + "scrypt", + "ssh/terminal", + ] pruneopts = "T" revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" @@ -339,6 +408,9 @@ name = "golang.org/x/net" packages = [ "context", + "html", + "html/atom", + "html/charset", "http/httpguts", "http2", "http2/hpack", @@ -350,18 +422,41 @@ pruneopts = "T" revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" +[[projects]] + branch = "master" + digest = "1:7560b3b54e7e9d901c46cd919d77da3c4373652ee2b0a13b15b80ea02ad94a56" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows", + ] + pruneopts = "T" + revision = "d99a578cf41bfccdeaf48b0845c823a4b8b0ad5e" + [[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", @@ -469,6 +564,7 @@ "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", diff --git a/server/rpc/apis.go b/server/rpc/apis.go index ae30f0f9..d0f7bb38 100644 --- a/server/rpc/apis.go +++ b/server/rpc/apis.go @@ -3,10 +3,10 @@ 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" - "github.com/cosmos/ethermint/version" ) // returns the master list of public APIs for use with StartHTTPEndpoint @@ -17,13 +17,19 @@ func GetRPCAPIs() []rpc.API { 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. +// 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{} } diff --git a/server/rpc/apis_test.go b/server/rpc/apis_test.go index f58c2a38..d0bec724 100644 --- a/server/rpc/apis_test.go +++ b/server/rpc/apis_test.go @@ -2,9 +2,9 @@ package rpc import ( "context" + "github.com/cosmos/ethermint/version" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/cosmos/ethermint/version" "testing" ) @@ -37,6 +37,16 @@ func (s *apisTestSuite) TestPublicWeb3APISha3() { 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)) } diff --git a/server/rpc/config.go b/server/rpc/config.go index c79f58df..2c030f1b 100644 --- a/server/rpc/config.go +++ b/server/rpc/config.go @@ -4,13 +4,13 @@ package rpc // behavior of the RPC HTTP server. type Config struct { // EnableRPC defines whether or not to enable the RPC server - EnableRPC bool + EnableRPC bool // RPCAddr defines the IP address to listen on - RPCAddr string + RPCAddr string // RPCPort defines the port to listen on - RPCPort int + 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 + 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 index 641f19f2..df621eb6 100644 --- a/server/rpc/rpc.go +++ b/server/rpc/rpc.go @@ -4,7 +4,7 @@ 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. diff --git a/version/version.go b/version/version.go index ae4e0650..015a5b96 100644 --- a/version/version.go +++ b/version/version.go @@ -14,6 +14,9 @@ const AppName = "Ethermint" // 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 = "" From 4f211af49a48726c5aa80dccc42a35953e524b5f Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 30 Aug 2018 14:32:32 -0400 Subject: [PATCH 13/16] Update ethermint appt to accept an SDK address --- app/ethermint.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/ethermint.go b/app/ethermint.go index ad9bbd56..be17045f 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -15,6 +15,7 @@ import ( "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" @@ -58,12 +59,13 @@ type ( // NewEthermintApp returns a reference to a new initialized Ethermint // application. func NewEthermintApp( - logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, baseAppOptions ...func(*bam.BaseApp), + logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, + sdkAddr ethcmn.Address, baseAppOpts ...func(*bam.BaseApp), ) *EthermintApp { codec := CreateCodec() app := &EthermintApp{ - BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec), baseAppOptions...), + BaseApp: bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec, sdkAddr), baseAppOpts...), codec: codec, accountKey: sdk.NewKVStoreKey("acc"), mainKey: sdk.NewKVStoreKey("main"), @@ -177,7 +179,7 @@ func (app *EthermintApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) } // 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() From 562124266829ba1d0eb325c1ec229ebf6a03893d Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 30 Aug 2018 17:36:14 -0400 Subject: [PATCH 14/16] Revert tx logic to use embedded txs with dedicated addr --- handlers/ante.go | 34 +++-- types/test_common.go | 121 +++++++++++++++ types/tx.go | 339 +++++++++++++++++++++++++------------------ types/tx_test.go | 158 +++++++++----------- types/utils.go | 2 +- types/utils_test.go | 16 +- types/wire.go | 3 - 7 files changed, 419 insertions(+), 254 deletions(-) create mode 100644 types/test_common.go diff --git a/handlers/ante.go b/handlers/ante.go index 473e1abb..f9ff96a2 100644 --- a/handlers/ante.go +++ b/handlers/ante.go @@ -9,8 +9,6 @@ import ( "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" - - ethtypes "github.com/ethereum/go-ethereum/core/types" ) const ( @@ -39,7 +37,7 @@ func AnteHandler(am auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHand switch tx := tx.(type) { case types.Transaction: - gasLimit = int64(tx.Data.GasLimit) + gasLimit = int64(tx.Data().GasLimit) handler = handleEthTx case auth.StdTx: gasLimit = tx.Fee.Gas @@ -91,16 +89,28 @@ 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 - gethTx := ethTx.ConvertTx(chainID) - signer := ethtypes.NewEIP155Signer(chainID) + sdkCtx.GasMeter().ConsumeGas(verifySigCost, "ante: verify Ethereum signature") - _, err := signer.Sender(&gethTx) + addr, err := ethTx.VerifySig(chainID) if err != nil { return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true } - return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data.GasLimit)}, false + 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("invalid account nonce; expected: %d", seq)).Result(), true + } + + err = acc.SetSequence(seq + 1) + if err != nil { + return sdkCtx, sdk.ErrInternal(err.Error()).Result(), true + } + + am.SetAccount(sdkCtx, acc) + return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false } // handleEmbeddedTx implements an ante handler for an SDK transaction. It @@ -122,15 +132,15 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, am auth.AccountMapper) (sdk for i, sig := range stdTx.Signatures { signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes()) - signerAcc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) + acc, err := validateSignature(sdkCtx, stdTx, signer, sig, am) if err.Code() != sdk.CodeOK { - return sdkCtx, err.Result(), false + 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) diff --git a/types/test_common.go b/types/test_common.go new file mode 100644 index 00000000..6dc76eb6 --- /dev/null +++ b/types/test_common.go @@ -0,0 +1,121 @@ +// nolint +package types + +import ( + "crypto/ecdsa" + "math/big" + + 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" +) + +var ( + TestSDKAddr = GenerateEthAddress() + TestChainID = big.NewInt(3) + + TestPrivKey1, _ = ethcrypto.GenerateKey() + TestPrivKey2, _ = ethcrypto.GenerateKey() + + TestAddr1 = PrivKeyToEthAddress(TestPrivKey1) + TestAddr2 = PrivKeyToEthAddress(TestPrivKey2) +) + +func NewTestCodec() *wire.Codec { + codec := wire.NewCodec() + + RegisterWire(codec) + auth.RegisterWire(codec) + wire.RegisterCrypto(codec) + codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) + + return codec +} + +func NewTestStdFee() auth.StdFee { + return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) +} + +func NewTestStdTx( + chainID *big.Int, msgs []sdk.Msg, accNums []int64, seqs []int64, pKeys []*ecdsa.PrivateKey, fee auth.StdFee, +) sdk.Tx { + + sigs := make([]auth.StdSignature, len(pKeys)) + + for i, priv := range pKeys { + signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], NewTestStdFee(), msgs, "") + + sig, err := ethcrypto.Sign(signBytes, priv) + if err != nil { + panic(err) + } + + sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + + return auth.NewStdTx(msgs, fee, sigs, "") +} + +func NewTestGethTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*ethtypes.Transaction { + + txs := make([]*ethtypes.Transaction, len(pKeys)) + + for i, privKey := range pKeys { + ethTx := ethtypes.NewTransaction( + uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{}, + ) + + signer := ethtypes.NewEIP155Signer(chainID) + + ethTx, err := ethtypes.SignTx(ethTx, signer, privKey) + if err != nil { + panic(err) + } + + txs[i] = ethTx + } + + return txs +} + +func NewTestEthTxs( + chainID *big.Int, seqs []int64, addrs []ethcmn.Address, pKeys []*ecdsa.PrivateKey, +) []*Transaction { + + txs := make([]*Transaction, len(pKeys)) + + for i, privKey := range pKeys { + ethTx := NewTransaction( + uint64(seqs[i]), addrs[i], big.NewInt(10), 1000, big.NewInt(100), []byte{}, + ) + + ethTx.Sign(chainID, privKey) + txs[i] = ethTx + } + + return txs +} + +func NewTestSDKTxs( + 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)) + stdTx := NewTestStdTx(chainID, msgs, accNums, seqs, pKeys, fee) + payload := codec.MustMarshalBinary(stdTx) + + for i, privKey := range pKeys { + ethTx := NewTransaction(uint64(seqs[i]), to, big.NewInt(10), 1000, big.NewInt(100), payload) + + ethTx.Sign(chainID, privKey) + txs[i] = ethTx + } + + return txs +} diff --git a/types/tx.go b/types/tx.go index 3c3b7dd2..00a696c9 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,8 +1,10 @@ package types import ( + "bytes" "crypto/ecdsa" "fmt" + "io" "math/big" "sync/atomic" @@ -13,17 +15,20 @@ import ( 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" ) -var _ sdk.Tx = (*Transaction)(nil) - const ( // TypeTxEthereum reflects an Ethereum Transaction type. TypeTxEthereum = "Ethereum" ) +// ---------------------------------------------------------------------------- +// Ethereum transaction +// ---------------------------------------------------------------------------- + +var _ sdk.Tx = (*Transaction)(nil) + type ( // Transaction implements the Ethereum transaction structure as an exact // replica. It implements the Cosmos sdk.Tx interface. Due to the private @@ -33,7 +38,7 @@ type ( // 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 @@ -41,96 +46,136 @@ type ( from atomic.Value } - // TxData defines internal Ethereum transaction information + // 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"` + 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 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) } @@ -147,13 +192,48 @@ 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 + // call is not the same as used current, invalidate + // the cache. + if sigCache.signer.Equal(signer) { + return sigCache.from, nil + } + } + + // do not allow recovery for transactions with an unprotected chainID + if chainID.Sign() == 0 { + return ethcmn.Address{}, errors.New("invalid chainID") + } + + txHash := tx.SigHash(chainID) + sig := recoverEthSig(tx.data.R, tx.data.S, tx.data.V, chainID) + + pub, err := ethcrypto.Ecrecover(txHash[:], sig) + if err != nil { + return ethcmn.Address{}, err + } + + var addr ethcmn.Address + copy(addr[:], ethcrypto.Keccak256(pub[1:])[12:]) + + tx.from.Store(sigCache{signer: signer, from: addr}) + return addr, nil } // Type implements the sdk.Msg interface. It returns the type of the @@ -165,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") } @@ -192,44 +272,57 @@ 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. -// -// TODO: To be removed in #470 -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()) } -// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes, -// attempts to decode them into a valid sdk.Tx. -func TxDecoder(codec *wire.Codec) sdk.TxDecoder { +// 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) (sdk.Tx, sdk.Error) { + var etx sdk.Tx + + err := codec.UnmarshalBinary(tx.data.Payload, &etx) + if err != nil { + return etx, sdk.ErrTxDecode("failed to decode embedded transaction") + } + + return etx, nil +} + +// ---------------------------------------------------------------------------- +// Utilities +// ---------------------------------------------------------------------------- + +// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an +// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or +// returning an error if decoding fails. +func TxDecoder(codec *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") } - var tx sdk.Tx - - // 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 SDK transaction. + if tx.hasEmbeddedTx(sdkAddress) { + etx, err := tx.GetEmbeddedTx(codec) + if err != nil { + return nil, err + } + + return etx, nil } return tx, nil @@ -237,20 +330,20 @@ func TxDecoder(codec *wire.Codec) 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) } @@ -259,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 7a75caa9..0df8a8a7 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -3,83 +3,57 @@ 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" - ethcrypto "github.com/ethereum/go-ethereum/crypto" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" ) -var ( - testChainID = sdk.NewInt(3) +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}) - testPrivKey1, _ = ethcrypto.GenerateKey() - testPrivKey2, _ = ethcrypto.GenerateKey() + txRLP, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) - testAddr1 = PrivKeyToEthAddress(testPrivKey1) - testAddr2 = PrivKeyToEthAddress(testPrivKey2) -) + gtxRLP, err := rlp.EncodeToBytes(gtxs[0]) + require.NoError(t, err) -func newTestCodec() *wire.Codec { - codec := wire.NewCodec() - - RegisterWire(codec) - codec.RegisterConcrete(auth.StdTx{}, "test/StdTx", nil) - codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil) - wire.RegisterCrypto(codec) - - return codec + require.Equal(t, gtxRLP, txRLP) } -func newStdFee() auth.StdFee { - return auth.NewStdFee(5000, sdk.NewCoin("photon", sdk.NewInt(150))) -} +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}) -func newTestStdTx( - chainID sdk.Int, msgs []sdk.Msg, pKeys []*ecdsa.PrivateKey, - accNums []int64, seqs []int64, fee auth.StdFee, -) sdk.Tx { + txRLP, err := rlp.EncodeToBytes(txs[0]) + require.NoError(t, err) - sigs := make([]auth.StdSignature, len(pKeys)) + gtxRLP, err := rlp.EncodeToBytes(gtxs[0]) + require.NoError(t, err) - for i, priv := range pKeys { - signBytes := GetStdTxSignBytes(chainID.String(), accNums[i], seqs[i], newStdFee(), msgs, "") + var ( + decodedTx Transaction + decodedGtx ethtypes.Transaction + ) - sig, err := ethcrypto.Sign(signBytes, priv) - if err != nil { - panic(err) - } + err = rlp.DecodeBytes(txRLP, &decodedTx) + require.NoError(t, err) - sigs[i] = auth.StdSignature{Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} - } + err = rlp.DecodeBytes(gtxRLP, &decodedGtx) + require.NoError(t, err) - return auth.NewStdTx(msgs, fee, sigs, "") -} - -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 + require.Equal(t, decodedGtx.Hash(), decodedTx.Hash()) } func TestValidation(t *testing.T) { - ethTxs := newTestEthTxs( - testChainID, - []*ecdsa.PrivateKey{testPrivKey1}, - []ethcmn.Address{testAddr1}, + ethTxs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) testCases := []struct { @@ -89,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}, } @@ -112,59 +86,57 @@ func TestValidation(t *testing.T) { } } -func TestTransactionGetMsgs(t *testing.T) { - ethTxs := newTestEthTxs( - testChainID, - []*ecdsa.PrivateKey{testPrivKey1}, - []ethcmn.Address{testAddr1}, +func TestTransactionVerifySig(t *testing.T) { + txs := NewTestEthTxs( + TestChainID, []int64{0}, []ethcmn.Address{TestAddr1}, []*ecdsa.PrivateKey{TestPrivKey1}, ) - msgs := ethTxs[0].GetMsgs() - require.Len(t, msgs, 1) - require.Equal(t, ethTxs[0], msgs[0]) + addr, err := txs[0].VerifySig(TestChainID) + require.NoError(t, err) + require.Equal(t, TestAddr1, addr) - expectedMsgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} - etx := newTestStdTx( - testChainID, expectedMsgs, []*ecdsa.PrivateKey{testPrivKey1}, - []int64{0}, []int64{0}, newStdFee(), - ) - - msgs = etx.GetMsgs() - require.Len(t, msgs, len(expectedMsgs)) - require.Equal(t, expectedMsgs, msgs) + addr, err = txs[0].VerifySig(big.NewInt(100)) + require.Error(t, err) + require.NotEqual(t, TestAddr1, addr) } func TestTxDecoder(t *testing.T) { - testCodec := newTestCodec() - txDecoder := TxDecoder(testCodec) + testCodec := NewTestCodec() + 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 a SDK (auth.StdTx) transaction and encode - stdTx := newTestStdTx( - testChainID, msgs, []*ecdsa.PrivateKey{testPrivKey1}, - []int64{0}, []int64{0}, newStdFee(), + txs = NewTestSDKTxs( + testCodec, TestChainID, TestSDKAddr, msgs, []int64{0}, []int64{0}, + []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee(), ) - // require the transaction to properly decode into a Transaction - txBytes = testCodec.MustMarshalBinary(stdTx) - tx, err = txDecoder(txBytes) + txBytes, err = rlp.EncodeToBytes(txs[0]) require.NoError(t, err) - require.Equal(t, stdTx, tx) + + // require the transaction to properly decode into a Transaction + stdTx := NewTestStdTx(TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{TestPrivKey1}, NewTestStdFee()) + decodedTx, err = txDecoder(txBytes) + require.NoError(t, err) + require.IsType(t, auth.StdTx{}, decodedTx) + require.Equal(t, stdTx, decodedTx) // require the decoding to fail when no transaction bytes are given - tx, err = txDecoder([]byte{}) + decodedTx, err = txDecoder([]byte{}) require.Error(t, err) - require.Nil(t, tx) + require.Nil(t, decodedTx) } diff --git a/types/utils.go b/types/utils.go index 9c4f5328..7135a583 100644 --- a/types/utils.go +++ b/types/utils.go @@ -35,7 +35,7 @@ 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) } diff --git a/types/utils_test.go b/types/utils_test.go index 39c9e291..12e27d8f 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -10,23 +10,27 @@ import ( ) func TestValidateSigner(t *testing.T) { - msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(testAddr1.Bytes()))} + msgs := []sdk.Msg{sdk.NewTestMsg(sdk.AccAddress(TestAddr1.Bytes()))} // create message signing structure and bytes - signBytes := GetStdTxSignBytes(testChainID.String(), 0, 0, newStdFee(), msgs, "") + signBytes := GetStdTxSignBytes(TestChainID.String(), 0, 0, NewTestStdFee(), msgs, "") // require signing not to fail - sig, err := ethcrypto.Sign(signBytes, testPrivKey1) + sig, err := ethcrypto.Sign(signBytes, TestPrivKey1) require.NoError(t, err) // require signature to be valid - err = ValidateSigner(signBytes, sig, testAddr1) + err = ValidateSigner(signBytes, sig, TestAddr1) require.NoError(t, err) - sig, err = ethcrypto.Sign(signBytes, testPrivKey2) + sig, err = ethcrypto.Sign(signBytes, TestPrivKey2) require.NoError(t, err) // require signature to be invalid - err = ValidateSigner(signBytes, sig, testAddr1) + 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 23f69aeb..07e27c6f 100644 --- a/types/wire.go +++ b/types/wire.go @@ -16,7 +16,4 @@ func init() { func RegisterWire(codec *wire.Codec) { sdk.RegisterWire(codec) codec.RegisterConcrete(&Account{}, "types/Account", nil) - codec.RegisterConcrete(&EthSignature{}, "types/EthSignature", nil) - codec.RegisterConcrete(TxData{}, "types/TxData", nil) - codec.RegisterConcrete(Transaction{}, "types/Transaction", nil) } From 8ca5a1c04302eb255b0b469f6db9e781880a3701 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 30 Aug 2018 17:55:45 -0400 Subject: [PATCH 15/16] Update tx spec doc --- docs/spec/transactions/README.md | 35 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/spec/transactions/README.md b/docs/spec/transactions/README.md index 040d00bd..7fe16367 100644 --- a/docs/spec/transactions/README.md +++ b/docs/spec/transactions/README.md @@ -6,15 +6,30 @@ 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 to handle -Ethereum transactions and utilizing the SDK's `auth.StdTx` for Cosmos -transactions. Both of these structures are registered with an [Amino](https://github.com/tendermint/go-amino) codec, so the `TxDecoder` that in invoked -during the `BaseApp#runTx`, will be able to decode raw transaction bytes into the -appropriate transaction type which will then be passed onto handlers downstream. +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 @@ -33,16 +48,16 @@ no distinction between transactions and messages. 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 `auth.StdTx` Cosmos routed transactions. Signatures over the -`Transaction` type are identical to Ethereum. However, the `auth.StdTx` contains +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 `auth.StdTx` 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 `auth.StdTx` is responsible for paying the fees. +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 From 4add2e7df7070b00628d79c80d52350d7cc59555 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 31 Aug 2018 08:35:23 -0400 Subject: [PATCH 16/16] Remove ChainConfig from ethermint constructor --- app/ethermint.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/ethermint.go b/app/ethermint.go index be17045f..b911cd76 100644 --- a/app/ethermint.go +++ b/app/ethermint.go @@ -16,7 +16,6 @@ import ( "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" @@ -59,8 +58,7 @@ type ( // NewEthermintApp returns a reference to a new initialized Ethermint // application. func NewEthermintApp( - logger tmlog.Logger, db dbm.DB, ethChainCfg *ethparams.ChainConfig, - sdkAddr ethcmn.Address, baseAppOpts ...func(*bam.BaseApp), + logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address, baseAppOpts ...func(*bam.BaseApp), ) *EthermintApp { codec := CreateCodec()