From 4f9aa1dba94677c9a5d002feccddf0494925f580 Mon Sep 17 00:00:00 2001 From: Dario Anongba Varela Date: Wed, 15 Sep 2021 17:53:09 +0200 Subject: [PATCH] feat: Implement EIP-1559 JSON-RPC (#314) --- cmd/migrate.go | 9 +- go.mod | 1 + go.sum | 20 ++ pkg/ethereum/args.go | 100 +++++--- pkg/ethereum/call_msg.go | 62 +++-- pkg/ethereum/caller_eth.go | 19 +- pkg/ethereum/crypto.go | 30 --- pkg/ethereum/hashing.go | 50 ---- pkg/ethereum/mock/caller_eth.go | 80 +++--- pkg/ethereum/tx_data.go | 28 --- src/nodes/interceptor/eth_send_transaction.go | 198 +++++++++++---- .../interceptor/eth_send_transaction_test.go | 228 +++++++++++------- src/nodes/interceptor/eth_sign_transaction.go | 17 +- .../interceptor/eth_sign_transaction_test.go | 14 +- src/stores/api/types/ethereum.go | 2 +- tests/config.go | 1 + tests/e2e/json_rpc_test.go | 69 +++++- 17 files changed, 559 insertions(+), 369 deletions(-) delete mode 100644 pkg/ethereum/crypto.go delete mode 100644 pkg/ethereum/hashing.go delete mode 100644 pkg/ethereum/tx_data.go diff --git a/cmd/migrate.go b/cmd/migrate.go index c80325972..ecae3894a 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -29,9 +29,6 @@ func newMigrateCommand() *cobra.Command { }, } - flags.LoggerFlags(migrateCmd.Flags()) - flags.PGFlags(migrateCmd.Flags()) - // Register Up command upCmd := &cobra.Command{ Use: "up [target]", @@ -42,6 +39,8 @@ func newMigrateCommand() *cobra.Command { }, } migrateCmd.AddCommand(upCmd) + flags.LoggerFlags(upCmd.Flags()) + flags.PGFlags(upCmd.Flags()) // Register Down command downCmd := &cobra.Command{ @@ -52,6 +51,8 @@ func newMigrateCommand() *cobra.Command { }, } migrateCmd.AddCommand(downCmd) + flags.LoggerFlags(downCmd.Flags()) + flags.PGFlags(downCmd.Flags()) // Register Reset command resetCmd := &cobra.Command{ @@ -62,6 +63,8 @@ func newMigrateCommand() *cobra.Command { }, } migrateCmd.AddCommand(resetCmd) + flags.LoggerFlags(resetCmd.Flags()) + flags.PGFlags(resetCmd.Flags()) return migrateCmd } diff --git a/go.mod b/go.mod index 9dab9ad9c..dadf01b3f 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 + github.com/swaggo/swag v1.7.1 go.elastic.co/ecszap v1.0.0 go.uber.org/atomic v1.8.0 // indirect go.uber.org/multierr v1.7.0 // indirect diff --git a/go.sum b/go.sum index 6c21398b3..7dee5be71 100644 --- a/go.sum +++ b/go.sum @@ -86,12 +86,16 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -259,12 +263,19 @@ github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= +github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-pg/pg/v10 v10.10.1 h1:82lLX4KGs2wOFOvVVIICoU0Si1fLu6Aitniu73HaDuM= github.com/go-pg/pg/v10 v10.10.1/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= @@ -493,6 +504,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -553,6 +566,8 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -751,6 +766,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s= +github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -870,6 +887,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1076,6 +1094,7 @@ golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1213,6 +1232,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= diff --git a/pkg/ethereum/args.go b/pkg/ethereum/args.go index 86c2c4d80..4e216ccc1 100644 --- a/pkg/ethereum/args.go +++ b/pkg/ethereum/args.go @@ -61,27 +61,56 @@ func (args *PrivateArgs) WithPrivacyGroupID(id string) *PrivateArgs { // TODO: Delete usage of unnecessary pointers: https://app.zenhub.com/workspaces/orchestrate-5ea70772b186e10067f57842/issues/consensys/quorum-key-manager/96 type SendTxMsg struct { - From ethcommon.Address - To *ethcommon.Address - Gas *uint64 - GasPrice *big.Int - Value *big.Int - Nonce *uint64 - Data *[]byte + From ethcommon.Address + To *ethcommon.Address + Gas *uint64 + GasPrice *big.Int + Value *big.Int + Nonce *uint64 + Data *[]byte + GasFeeCap *big.Int + GasTipCap *big.Int + AccessList types.AccessList PrivateArgs } func (msg *SendTxMsg) IsPrivate() bool { - return msg.PrivateArgs != PrivateArgs{} -} - -func (msg *SendTxMsg) TxData() *types.Transaction { - if msg.To == nil { - return types.NewContractCreation(*msg.Nonce, msg.Value, *msg.Gas, msg.GasPrice, *msg.Data) + return msg.PrivateArgs != PrivateArgs{} && msg.PrivateArgs.PrivateFrom != nil +} + +func (msg *SendTxMsg) IsLegacy() bool { + return msg.GasPrice != nil +} + +func (msg *SendTxMsg) TxData(txType int, chainID *big.Int) *types.Transaction { + var txData types.TxData + + switch txType { + case types.LegacyTxType: + txData = &types.LegacyTx{ + Nonce: *msg.Nonce, + GasPrice: msg.GasPrice, + Gas: *msg.Gas, + To: msg.To, + Value: msg.Value, + Data: *msg.Data, + } + case types.DynamicFeeTxType: + txData = &types.DynamicFeeTx{ + ChainID: chainID, + Nonce: *msg.Nonce, + GasTipCap: msg.GasTipCap, + GasFeeCap: msg.GasFeeCap, + Gas: *msg.Gas, + To: msg.To, + Value: msg.Value, + Data: *msg.Data, + AccessList: msg.AccessList, + } } - return types.NewTransaction(*msg.Nonce, *msg.To, msg.Value, *msg.Gas, msg.GasPrice, *msg.Data) + return types.NewTx(txData) } // TODO: Delete this function and use only go-quorum types when @@ -95,14 +124,17 @@ func (msg *SendTxMsg) TxDataQuorum() *quorumtypes.Transaction { // TODO: Delete usage of unnecessary pointers: https://app.zenhub.com/workspaces/orchestrate-5ea70772b186e10067f57842/issues/consensys/quorum-key-manager/96 type jsonSendTxMsg struct { - From ethcommon.Address `json:"from,omitempty"` - To *ethcommon.Address `json:"to,omitempty"` - Gas *hexutil.Uint64 `json:"gas,omitempty"` - GasPrice *hexutil.Big `json:"gasPrice,omitempty"` - Value *hexutil.Big `json:"value,omitempty"` - Nonce *hexutil.Uint64 `json:"nonce,omitempty"` - Data *hexutil.Bytes `json:"data,omitempty"` - Input *hexutil.Bytes `json:"input,omitempty"` + From ethcommon.Address `json:"from,omitempty"` + To *ethcommon.Address `json:"to,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + GasPrice *hexutil.Big `json:"gasPrice,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + Nonce *hexutil.Uint64 `json:"nonce,omitempty"` + Data *hexutil.Bytes `json:"data,omitempty"` + Input *hexutil.Bytes `json:"input,omitempty"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + AccessList types.AccessList `json:"accessList,omitempty"` PrivateArgs } @@ -123,6 +155,9 @@ func (msg *SendTxMsg) UnmarshalJSON(b []byte) error { Nonce: (*uint64)(raw.Nonce), PrivateArgs: raw.PrivateArgs, Data: (*[]byte)(raw.Input), + GasTipCap: (*big.Int)(raw.GasTipCap), + GasFeeCap: (*big.Int)(raw.GasFeeCap), + AccessList: raw.AccessList, } if raw.Data != nil { @@ -141,6 +176,9 @@ func (msg *SendTxMsg) MarshalJSON() ([]byte, error) { Value: (*hexutil.Big)(msg.Value), Nonce: (*hexutil.Uint64)(msg.Nonce), Data: (*hexutil.Bytes)(msg.Data), + GasTipCap: (*hexutil.Big)(msg.GasTipCap), + GasFeeCap: (*hexutil.Big)(msg.GasFeeCap), + AccessList: msg.AccessList, PrivateArgs: msg.PrivateArgs, }) } @@ -159,20 +197,18 @@ type SendEEATxMsg struct { func (msg *SendEEATxMsg) TxData() *types.Transaction { var data []byte - var nonce uint64 if msg.Data != nil { data = *msg.Data } - if msg.Nonce != nil { - nonce = *msg.Nonce - } - - if msg.To == nil { - return types.NewContractCreation(nonce, nil, 0, nil, data) - } - - return types.NewTransaction(nonce, *msg.To, nil, *msg.Gas, msg.GasPrice, data) + return types.NewTx(&types.LegacyTx{ + Nonce: *msg.Nonce, + GasPrice: msg.GasPrice, + Gas: *msg.Gas, + To: msg.To, + Value: nil, + Data: data, + }) } // TODO: Delete usage of unnecessary pointers: https://app.zenhub.com/workspaces/orchestrate-5ea70772b186e10067f57842/issues/consensys/quorum-key-manager/96 diff --git a/pkg/ethereum/call_msg.go b/pkg/ethereum/call_msg.go index 18d8e6c58..720bb5cc3 100644 --- a/pkg/ethereum/call_msg.go +++ b/pkg/ethereum/call_msg.go @@ -4,18 +4,23 @@ import ( "encoding/json" "math/big" + "github.com/ethereum/go-ethereum/core/types" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ) // CallMsg contains parameters for contract calls. type CallMsg struct { - From *ethcommon.Address // the sender of the 'transaction' - To *ethcommon.Address // the destination contract (nil for contract creation) - Gas *uint64 // if 0, the call executes with near-infinite gas - GasPrice *big.Int // wei <-> gas exchange ratio - Value *big.Int // amount of wei sent along with the call - Data *[]byte // input data, usually an ABI-encoded contract method invocation + From *ethcommon.Address // the sender of the 'transaction' + To *ethcommon.Address // the destination contract (nil for contract creation) + Gas *uint64 // if 0, the call executes with near-infinite gas + GasPrice *big.Int // wei <-> gas exchange ratio + Value *big.Int // amount of wei sent along with the call + Data *[]byte // input data, usually an ABI-encoded contract method invocation + GasFeeCap *big.Int + GasTipCap *big.Int + AccessList types.AccessList } func (msg *CallMsg) WithFrom(addr ethcommon.Address) *CallMsg { @@ -53,12 +58,15 @@ func (msg *CallMsg) WithData(d []byte) *CallMsg { } type jsonCallMsg struct { - From *ethcommon.Address `json:"from,omitempty"` - To *ethcommon.Address `json:"to,omitempty"` - Gas *hexutil.Uint64 `json:"gas,omitempty"` - GasPrice *hexutil.Big `json:"gasPrice,omitempty"` - Value *hexutil.Big `json:"value,omitempty"` - Data *hexutil.Bytes `json:"data,omitempty"` + From *ethcommon.Address `json:"from,omitempty"` + To *ethcommon.Address `json:"to,omitempty"` + Gas *hexutil.Uint64 `json:"gas,omitempty"` + GasPrice *hexutil.Big `json:"gasPrice,omitempty"` + Value *hexutil.Big `json:"value,omitempty"` + Data *hexutil.Bytes `json:"data,omitempty"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + AccessList types.AccessList `json:"accessList,omitempty"` } func (msg *CallMsg) UnmarshalJSON(b []byte) error { @@ -69,12 +77,15 @@ func (msg *CallMsg) UnmarshalJSON(b []byte) error { } *msg = CallMsg{ - From: raw.From, - To: raw.To, - Gas: (*uint64)(raw.Gas), - GasPrice: (*big.Int)(raw.GasPrice), - Value: (*big.Int)(raw.Value), - Data: (*[]byte)(raw.Data), + From: raw.From, + To: raw.To, + Gas: (*uint64)(raw.Gas), + GasPrice: (*big.Int)(raw.GasPrice), + Value: (*big.Int)(raw.Value), + Data: (*[]byte)(raw.Data), + GasTipCap: (*big.Int)(raw.GasTipCap), + GasFeeCap: (*big.Int)(raw.GasFeeCap), + AccessList: raw.AccessList, } if raw.Data != nil { @@ -86,11 +97,14 @@ func (msg *CallMsg) UnmarshalJSON(b []byte) error { func (msg *CallMsg) MarshalJSON() ([]byte, error) { return json.Marshal(&jsonCallMsg{ - From: msg.From, - To: msg.To, - Gas: (*hexutil.Uint64)(msg.Gas), - GasPrice: (*hexutil.Big)(msg.GasPrice), - Value: (*hexutil.Big)(msg.Value), - Data: (*hexutil.Bytes)(msg.Data), + From: msg.From, + To: msg.To, + Gas: (*hexutil.Uint64)(msg.Gas), + GasPrice: (*hexutil.Big)(msg.GasPrice), + Value: (*hexutil.Big)(msg.Value), + Data: (*hexutil.Bytes)(msg.Data), + GasTipCap: (*hexutil.Big)(msg.GasTipCap), + GasFeeCap: (*hexutil.Big)(msg.GasFeeCap), + AccessList: msg.AccessList, }) } diff --git a/pkg/ethereum/caller_eth.go b/pkg/ethereum/caller_eth.go index 8529fa09a..b082fab10 100644 --- a/pkg/ethereum/caller_eth.go +++ b/pkg/ethereum/caller_eth.go @@ -4,6 +4,8 @@ import ( "context" "math/big" + "github.com/ethereum/go-ethereum/core/types" + "github.com/consensys/quorum-key-manager/pkg/jsonrpc" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -28,14 +30,16 @@ type ethService struct { EstimateGas func(jsonrpc.Client) func(context.Context, *CallMsg) (*hexutil.Uint64, error) `namespace:"eth"` SendRawTransaction func(jsonrpc.Client) func(context.Context, hexutil.Bytes) (ethcommon.Hash, error) `namespace:"eth"` SendRawPrivateTransaction func(jsonrpc.Client) func(context.Context, hexutil.Bytes, *PrivateArgs) (ethcommon.Hash, error) `namespace:"eth"` + GetBlockByNumber func(jsonrpc.Client) func(context.Context, BlockNumber, bool) (*types.Header, error) `method:"eth_getBlockByNumber"` } //go:generate mockgen -source=caller_eth.go -destination=mock/caller_eth.go -package=mock -// EthCaller is a JSON-RPC client to a Ethereum client using eth namespace +// EthCaller is a JSON-RPC client to an Ethereum client using eth namespace type EthCaller interface { ChainID(context.Context) (*big.Int, error) GasPrice(context.Context) (*big.Int, error) + BaseFeePerGas(context.Context, BlockNumber) (*big.Int, error) GetTransactionCount(context.Context, ethcommon.Address, BlockNumber) (uint64, error) EstimateGas(context.Context, *CallMsg) (uint64, error) SendRawTransaction(context.Context, []byte) (ethcommon.Hash, error) @@ -83,9 +87,18 @@ func (c *ethCaller) EstimateGas(ctx context.Context, msg *CallMsg) (uint64, erro } func (c *ethCaller) SendRawTransaction(ctx context.Context, raw []byte) (ethcommon.Hash, error) { - return ethSrv.SendRawTransaction(c.client)(ctx, hexutil.Bytes(raw)) + return ethSrv.SendRawTransaction(c.client)(ctx, raw) } func (c *ethCaller) SendRawPrivateTransaction(ctx context.Context, raw []byte, privArgs *PrivateArgs) (ethcommon.Hash, error) { - return ethSrv.SendRawPrivateTransaction(c.client)(ctx, hexutil.Bytes(raw), privArgs) + return ethSrv.SendRawPrivateTransaction(c.client)(ctx, raw, privArgs) +} + +func (c *ethCaller) BaseFeePerGas(ctx context.Context, blockNumber BlockNumber) (*big.Int, error) { + header, err := ethSrv.GetBlockByNumber(c.client)(ctx, blockNumber, false) + if err != nil { + return nil, err + } + + return header.BaseFee, nil } diff --git a/pkg/ethereum/crypto.go b/pkg/ethereum/crypto.go deleted file mode 100644 index 953187f58..000000000 --- a/pkg/ethereum/crypto.go +++ /dev/null @@ -1,30 +0,0 @@ -package ethereum - -import ( - "crypto/ecdsa" - "math/big" - - "github.com/ethereum/go-ethereum/crypto" -) - -const EthSignatureLength = 65 - -func VerifySignature(signature, msg, privKeyB []byte) (bool, error) { - privKey, err := crypto.ToECDSA(privKeyB) - if err != nil { - return false, err - } - - if len(signature) == EthSignatureLength { - retrievedPubkey, err := crypto.SigToPub(msg, signature) - if err != nil { - return false, err - } - - return privKey.PublicKey.Equal(retrievedPubkey), nil - } - - r := new(big.Int).SetBytes(signature[0:32]) - s := new(big.Int).SetBytes(signature[32:64]) - return ecdsa.Verify(&privKey.PublicKey, msg, r, s), nil -} diff --git a/pkg/ethereum/hashing.go b/pkg/ethereum/hashing.go deleted file mode 100644 index de5a04721..000000000 --- a/pkg/ethereum/hashing.go +++ /dev/null @@ -1,50 +0,0 @@ -package ethereum - -import ( - "fmt" - "math/big" - - ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" -) - -func RLPHash(x interface{}) (h ethcommon.Hash) { - hw := sha3.NewLegacyKeccak256() - err := rlp.Encode(hw, x) - if err != nil { - panic(fmt.Sprintf("can not rlp encode: %v", x)) - } - hw.Sum(h[:0]) - return h -} - -func FrontierHash(tx *TxData) ethcommon.Hash { - return RLPHash([]interface{}{ - tx.Nonce, - tx.GasPrice, - tx.GasLimit, - tx.To, - tx.Value, - tx.Data, - }) -} - -func EIP155Hash(tx *TxData, chainID *big.Int) ethcommon.Hash { - return RLPHash([]interface{}{ - tx.Nonce, - tx.GasPrice, - tx.GasLimit, - tx.To, - tx.Value, - tx.Data, - chainID, - uint(0), - uint(0), - }) -} - -func EEAHash(tx *TxData, chainID *big.Int, args *PrivateArgs) ethcommon.Hash { - // TODO: to be implemented - return ethcommon.Hash{} -} diff --git a/pkg/ethereum/mock/caller_eth.go b/pkg/ethereum/mock/caller_eth.go index c320e51a8..90cbb5f79 100644 --- a/pkg/ethereum/mock/caller_eth.go +++ b/pkg/ethereum/mock/caller_eth.go @@ -6,38 +6,37 @@ package mock import ( context "context" - big "math/big" - reflect "reflect" - ethereum "github.com/consensys/quorum-key-manager/pkg/ethereum" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" + big "math/big" + reflect "reflect" ) -// MockEthCaller is a mock of EthCaller interface. +// MockEthCaller is a mock of EthCaller interface type MockEthCaller struct { ctrl *gomock.Controller recorder *MockEthCallerMockRecorder } -// MockEthCallerMockRecorder is the mock recorder for MockEthCaller. +// MockEthCallerMockRecorder is the mock recorder for MockEthCaller type MockEthCallerMockRecorder struct { mock *MockEthCaller } -// NewMockEthCaller creates a new mock instance. +// NewMockEthCaller creates a new mock instance func NewMockEthCaller(ctrl *gomock.Controller) *MockEthCaller { mock := &MockEthCaller{ctrl: ctrl} mock.recorder = &MockEthCallerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. +// EXPECT returns an object that allows the caller to indicate expected use func (m *MockEthCaller) EXPECT() *MockEthCallerMockRecorder { return m.recorder } -// ChainID mock base method. +// ChainID mocks base method func (m *MockEthCaller) ChainID(arg0 context.Context) (*big.Int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ChainID", arg0) @@ -46,43 +45,43 @@ func (m *MockEthCaller) ChainID(arg0 context.Context) (*big.Int, error) { return ret0, ret1 } -// ChainID indicates an expected call of ChainID. +// ChainID indicates an expected call of ChainID func (mr *MockEthCallerMockRecorder) ChainID(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainID", reflect.TypeOf((*MockEthCaller)(nil).ChainID), arg0) } -// EstimateGas mock base method. -func (m *MockEthCaller) EstimateGas(arg0 context.Context, arg1 *ethereum.CallMsg) (uint64, error) { +// GasPrice mocks base method +func (m *MockEthCaller) GasPrice(arg0 context.Context) (*big.Int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EstimateGas", arg0, arg1) - ret0, _ := ret[0].(uint64) + ret := m.ctrl.Call(m, "GasPrice", arg0) + ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(error) return ret0, ret1 } -// EstimateGas indicates an expected call of EstimateGas. -func (mr *MockEthCallerMockRecorder) EstimateGas(arg0, arg1 interface{}) *gomock.Call { +// GasPrice indicates an expected call of GasPrice +func (mr *MockEthCallerMockRecorder) GasPrice(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockEthCaller)(nil).EstimateGas), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasPrice", reflect.TypeOf((*MockEthCaller)(nil).GasPrice), arg0) } -// GasPrice mock base method. -func (m *MockEthCaller) GasPrice(arg0 context.Context) (*big.Int, error) { +// BaseFeePerGas mocks base method +func (m *MockEthCaller) BaseFeePerGas(arg0 context.Context, arg1 ethereum.BlockNumber) (*big.Int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GasPrice", arg0) + ret := m.ctrl.Call(m, "BaseFeePerGas", arg0, arg1) ret0, _ := ret[0].(*big.Int) ret1, _ := ret[1].(error) return ret0, ret1 } -// GasPrice indicates an expected call of GasPrice. -func (mr *MockEthCallerMockRecorder) GasPrice(arg0 interface{}) *gomock.Call { +// BaseFeePerGas indicates an expected call of BaseFeePerGas +func (mr *MockEthCallerMockRecorder) BaseFeePerGas(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasPrice", reflect.TypeOf((*MockEthCaller)(nil).GasPrice), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BaseFeePerGas", reflect.TypeOf((*MockEthCaller)(nil).BaseFeePerGas), arg0, arg1) } -// GetTransactionCount mock base method. +// GetTransactionCount mocks base method func (m *MockEthCaller) GetTransactionCount(arg0 context.Context, arg1 common.Address, arg2 ethereum.BlockNumber) (uint64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetTransactionCount", arg0, arg1, arg2) @@ -91,28 +90,28 @@ func (m *MockEthCaller) GetTransactionCount(arg0 context.Context, arg1 common.Ad return ret0, ret1 } -// GetTransactionCount indicates an expected call of GetTransactionCount. +// GetTransactionCount indicates an expected call of GetTransactionCount func (mr *MockEthCallerMockRecorder) GetTransactionCount(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionCount", reflect.TypeOf((*MockEthCaller)(nil).GetTransactionCount), arg0, arg1, arg2) } -// SendRawPrivateTransaction mock base method. -func (m *MockEthCaller) SendRawPrivateTransaction(arg0 context.Context, arg1 []byte, arg2 *ethereum.PrivateArgs) (common.Hash, error) { +// EstimateGas mocks base method +func (m *MockEthCaller) EstimateGas(arg0 context.Context, arg1 *ethereum.CallMsg) (uint64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendRawPrivateTransaction", arg0, arg1, arg2) - ret0, _ := ret[0].(common.Hash) + ret := m.ctrl.Call(m, "EstimateGas", arg0, arg1) + ret0, _ := ret[0].(uint64) ret1, _ := ret[1].(error) return ret0, ret1 } -// SendRawPrivateTransaction indicates an expected call of SendRawPrivateTransaction. -func (mr *MockEthCallerMockRecorder) SendRawPrivateTransaction(arg0, arg1, arg2 interface{}) *gomock.Call { +// EstimateGas indicates an expected call of EstimateGas +func (mr *MockEthCallerMockRecorder) EstimateGas(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawPrivateTransaction", reflect.TypeOf((*MockEthCaller)(nil).SendRawPrivateTransaction), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EstimateGas", reflect.TypeOf((*MockEthCaller)(nil).EstimateGas), arg0, arg1) } -// SendRawTransaction mock base method. +// SendRawTransaction mocks base method func (m *MockEthCaller) SendRawTransaction(arg0 context.Context, arg1 []byte) (common.Hash, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendRawTransaction", arg0, arg1) @@ -121,8 +120,23 @@ func (m *MockEthCaller) SendRawTransaction(arg0 context.Context, arg1 []byte) (c return ret0, ret1 } -// SendRawTransaction indicates an expected call of SendRawTransaction. +// SendRawTransaction indicates an expected call of SendRawTransaction func (mr *MockEthCallerMockRecorder) SendRawTransaction(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawTransaction", reflect.TypeOf((*MockEthCaller)(nil).SendRawTransaction), arg0, arg1) } + +// SendRawPrivateTransaction mocks base method +func (m *MockEthCaller) SendRawPrivateTransaction(arg0 context.Context, arg1 []byte, arg2 *ethereum.PrivateArgs) (common.Hash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendRawPrivateTransaction", arg0, arg1, arg2) + ret0, _ := ret[0].(common.Hash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendRawPrivateTransaction indicates an expected call of SendRawPrivateTransaction +func (mr *MockEthCallerMockRecorder) SendRawPrivateTransaction(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawPrivateTransaction", reflect.TypeOf((*MockEthCaller)(nil).SendRawPrivateTransaction), arg0, arg1, arg2) +} diff --git a/pkg/ethereum/tx_data.go b/pkg/ethereum/tx_data.go deleted file mode 100644 index 237ce3cef..000000000 --- a/pkg/ethereum/tx_data.go +++ /dev/null @@ -1,28 +0,0 @@ -package ethereum - -import ( - "math/big" - - ethcommon "github.com/ethereum/go-ethereum/common" -) - -type TxData struct { - Nonce uint64 - To *ethcommon.Address - Value *big.Int - GasPrice *big.Int - GasLimit uint64 - Data []byte -} - -func (tx *TxData) SetDefault() *TxData { - if tx.Value == nil { - tx.Value = big.NewInt(0) - } - - if tx.GasPrice == nil { - tx.GasPrice = big.NewInt(0) - } - - return tx -} diff --git a/src/nodes/interceptor/eth_send_transaction.go b/src/nodes/interceptor/eth_send_transaction.go index 750455297..9a6b7a17b 100644 --- a/src/nodes/interceptor/eth_send_transaction.go +++ b/src/nodes/interceptor/eth_send_transaction.go @@ -2,9 +2,9 @@ package interceptor import ( "context" + "math/big" "github.com/consensys/quorum-key-manager/pkg/errors" - "github.com/consensys/quorum-key-manager/pkg/ethereum" "github.com/consensys/quorum-key-manager/pkg/jsonrpc" proxynode "github.com/consensys/quorum-key-manager/src/nodes/node/proxy" @@ -12,9 +12,22 @@ import ( ) func (i *Interceptor) ethSendTransaction(ctx context.Context, msg *ethereum.SendTxMsg) (*ethcommon.Hash, error) { - i.logger.Debug("sending ETH transaction") + switch { + case msg.IsPrivate(): + return i.sendPrivateTx(ctx, msg) + case msg.IsLegacy(): + return i.sendLegacyTx(ctx, msg) + default: + // If the node is a pre-london fork, the tx will be reverted to legacy, and we will retrieve the gasPrice from the node + // If the node is a post-london fork, the tx will be filled with a default value for maxFeePerGas (currently maxFee = baseFee) + // pre-london = 'baseFeePerGas' field does not exist in the block header. + return i.sendTx(ctx, msg) + } +} + +func (i *Interceptor) sendPrivateTx(ctx context.Context, msg *ethereum.SendTxMsg) (*ethcommon.Hash, error) { + i.logger.Debug("sending Quorum private transaction") - // Get ChainID from Node sess := proxynode.SessionFromContext(ctx) if msg.GasPrice == nil { @@ -27,52 +40,67 @@ func (i *Interceptor) ethSendTransaction(ctx context.Context, msg *ethereum.Send msg.GasPrice = gasPrice } - if msg.Gas == nil { - callMsg := ðereum.CallMsg{ - From: &msg.From, - To: msg.To, - GasPrice: msg.GasPrice, - Value: msg.Value, - Data: msg.Data, - } - gas, err := sess.EthCaller().Eth().EstimateGas(ctx, callMsg) - if err != nil { - i.logger.WithError(err).Error("failed to estimate gas") - return nil, errors.BlockchainNodeError(err.Error()) - } + err := i.fillGas(ctx, sess, msg) + if err != nil { + return nil, err + } - msg.Gas = &gas + err = i.fillNonce(ctx, sess, msg) + if err != nil { + return nil, err } - if msg.Nonce == nil { - n, err := sess.EthCaller().Eth().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber) - if err != nil { - i.logger.WithError(err).Error("failed to fetch nonce", "from_account", msg.From) - return nil, errors.BlockchainNodeError(err.Error()) - } + if msg.Data == nil { + msg.Data = new([]byte) + } - msg.Nonce = &n + // Store payload on Tessera + key, err := sess.ClientPrivTxManager().StoreRaw(ctx, *msg.Data, *msg.PrivateFrom) + if err != nil { + i.logger.WithError(err).Error("failed to store raw payload on Tessera", "private_from", *msg.PrivateFrom) + return nil, errors.BlockchainNodeError(err.Error()) } + // Switch message data + *msg.Data = key - if msg.IsPrivate() { - if msg.Data == nil { - msg.Data = new([]byte) - } + raw, err := i.ethSignTransaction(ctx, msg) + if err != nil { + return nil, err + } - var privateFrom string - if msg.PrivateFrom != nil { - privateFrom = *msg.PrivateFrom - } + hash, err := sess.EthCaller().Eth().SendRawPrivateTransaction(ctx, *raw, &msg.PrivateArgs) + if err != nil { + i.logger.WithError(err).Error("failed to send raw quorum private transaction") + return nil, errors.BlockchainNodeError(err.Error()) + } - // Store payload on Tessera - key, err := sess.ClientPrivTxManager().StoreRaw(ctx, *msg.Data, privateFrom) + i.logger.Info("quorum private transaction sent successfully", "tx_hash", hash) + return &hash, nil +} + +func (i *Interceptor) sendLegacyTx(ctx context.Context, msg *ethereum.SendTxMsg) (*ethcommon.Hash, error) { + i.logger.Debug("sending ETH legacy transaction") + + sess := proxynode.SessionFromContext(ctx) + + if msg.GasPrice == nil { + gasPrice, err := sess.EthCaller().Eth().GasPrice(ctx) if err != nil { - i.logger.WithError(err).Error("failed to store raw payload on Tessera", "private_from", privateFrom) + i.logger.WithError(err).Error("failed to fetch gas price") return nil, errors.BlockchainNodeError(err.Error()) } - // Switch message data - *msg.Data = key + msg.GasPrice = gasPrice + } + + err := i.fillGas(ctx, sess, msg) + if err != nil { + return nil, err + } + + err = i.fillNonce(ctx, sess, msg) + if err != nil { + return nil, err } raw, err := i.ethSignTransaction(ctx, msg) @@ -80,15 +108,61 @@ func (i *Interceptor) ethSendTransaction(ctx context.Context, msg *ethereum.Send return nil, err } - var hash ethcommon.Hash - if msg.IsPrivate() { - hash, err = sess.EthCaller().Eth().SendRawPrivateTransaction(ctx, *raw, &msg.PrivateArgs) - } else { - hash, err = sess.EthCaller().Eth().SendRawTransaction(ctx, *raw) + hash, err := sess.EthCaller().Eth().SendRawTransaction(ctx, *raw) + if err != nil { + i.logger.WithError(err).Error("failed to send raw legacy transaction") + return nil, errors.BlockchainNodeError(err.Error()) + } + + i.logger.Info("legacy transaction sent successfully", "tx_hash", hash) + return &hash, nil +} + +func (i *Interceptor) sendTx(ctx context.Context, msg *ethereum.SendTxMsg) (*ethcommon.Hash, error) { + i.logger.Debug("sending ETH transaction") + + sess := proxynode.SessionFromContext(ctx) + + baseFee, err := sess.EthCaller().Eth().BaseFeePerGas(ctx, ethereum.LatestBlockNumber) + if err != nil { + i.logger.WithError(err).Error("failed to retrieve base fee from latest block") + return nil, errors.BlockchainNodeError(err.Error()) + } + + if baseFee == nil { + i.logger.Warn("cannot send a dynamic fee transaction to a pre-London node, reverting to legacy tx") + return i.sendLegacyTx(ctx, msg) + } + + if msg.GasFeeCap == nil { + var maxPriorityFeePerGas = big.NewInt(0) + if msg.GasTipCap != nil { + maxPriorityFeePerGas = msg.GasTipCap + } + msg.GasFeeCap = new(big.Int).Add(baseFee, maxPriorityFeePerGas) + i.logger. + With("max_fee_per_gas", msg.GasFeeCap, "base_fee", baseFee, "max_priority_fee_per_gas", maxPriorityFeePerGas). + Debug("'maxFeePerGas' set with previous block 'baseFeePerGas' + miner tip") + } + + err = i.fillGas(ctx, sess, msg) + if err != nil { + return nil, err + } + + err = i.fillNonce(ctx, sess, msg) + if err != nil { + return nil, err + } + + raw, err := i.ethSignTransaction(ctx, msg) + if err != nil { + return nil, err } + hash, err := sess.EthCaller().Eth().SendRawTransaction(ctx, *raw) if err != nil { - i.logger.WithError(err).Error("failed to store raw transaction") + i.logger.WithError(err).Error("failed to send raw transaction") return nil, errors.BlockchainNodeError(err.Error()) } @@ -96,6 +170,44 @@ func (i *Interceptor) ethSendTransaction(ctx context.Context, msg *ethereum.Send return &hash, nil } +func (i *Interceptor) fillGas(ctx context.Context, sess proxynode.Session, msg *ethereum.SendTxMsg) error { + if msg.Gas == nil { + callMsg := ðereum.CallMsg{ + From: &msg.From, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + GasPrice: msg.GasPrice, + GasTipCap: msg.GasTipCap, + GasFeeCap: msg.GasFeeCap, + AccessList: msg.AccessList, + } + gas, err := sess.EthCaller().Eth().EstimateGas(ctx, callMsg) + if err != nil { + i.logger.WithError(err).With("gas_price", msg.GasPrice).Error("failed to estimate gas") + return errors.BlockchainNodeError(err.Error()) + } + + msg.Gas = &gas + } + + return nil +} + +func (i *Interceptor) fillNonce(ctx context.Context, sess proxynode.Session, msg *ethereum.SendTxMsg) error { + if msg.Nonce == nil { + n, err := sess.EthCaller().Eth().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber) + if err != nil { + i.logger.WithError(err).Error("failed to fetch nonce", "from_account", msg.From) + return errors.BlockchainNodeError(err.Error()) + } + + msg.Nonce = &n + } + + return nil +} + func (i *Interceptor) EthSendTransaction() jsonrpc.Handler { h, _ := jsonrpc.MakeHandler(i.ethSendTransaction) return h diff --git a/src/nodes/interceptor/eth_send_transaction_test.go b/src/nodes/interceptor/eth_send_transaction_test.go index dabc56baa..5e7a2ba16 100644 --- a/src/nodes/interceptor/eth_send_transaction_test.go +++ b/src/nodes/interceptor/eth_send_transaction_test.go @@ -5,13 +5,17 @@ import ( "math/big" "testing" + "github.com/consensys/quorum-key-manager/src/auth/authenticator" + "github.com/consensys/quorum-key-manager/src/auth/types" + "github.com/consensys/quorum-key-manager/src/infra/log/testutils" + mockaccounts "github.com/consensys/quorum-key-manager/src/stores/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/consensys/quorum-key-manager/pkg/ethereum" mockethereum "github.com/consensys/quorum-key-manager/pkg/ethereum/mock" mocktessera "github.com/consensys/quorum-key-manager/pkg/tessera/mock" - "github.com/consensys/quorum-key-manager/src/auth/authenticator" - "github.com/consensys/quorum-key-manager/src/auth/types" proxynode "github.com/consensys/quorum-key-manager/src/nodes/node/proxy" - mockaccounts "github.com/consensys/quorum-key-manager/src/stores/mock" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/golang/mock/gomock" ) @@ -20,106 +24,150 @@ func TestEthSendTransaction(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - i, stores := newInterceptor(ctrl) + session := proxynode.NewMockSession(ctrl) + caller := mockethereum.NewMockCaller(ctrl) + ethCaller := mockethereum.NewMockEthCaller(ctrl) + tesseraClient := mocktessera.NewMockClient(ctrl) accountsStore := mockaccounts.NewMockEthStore(ctrl) + storesManager := mockaccounts.NewMockManager(ctrl) + from := ethcommon.HexToAddress("0x78e6e236592597c09d5c137c2af40aecd42d12a2") userInfo := &types.UserInfo{ Username: "username", Roles: []string{"role1", "role2"}, Permissions: []types.Permission{"write:key", "read:key", "sign:key"}, } - session := proxynode.NewMockSession(ctrl) ctx := proxynode.WithSession(context.TODO(), session) ctx = authenticator.WithUserContext(ctx, &authenticator.UserContext{ UserInfo: userInfo, }) + gasPrice := big.NewInt(38) + chainID := big.NewInt(1) + value := big.NewInt(45) - cller := mockethereum.NewMockCaller(ctrl) - ethCaller := mockethereum.NewMockEthCaller(ctrl) - cller.EXPECT().Eth().Return(ethCaller).AnyTimes() - session.EXPECT().EthCaller().Return(cller).AnyTimes() - - tesseraClient := mocktessera.NewMockClient(ctrl) + caller.EXPECT().Eth().Return(ethCaller).AnyTimes() + session.EXPECT().EthCaller().Return(caller).AnyTimes() session.EXPECT().ClientPrivTxManager().Return(tesseraClient).AnyTimes() + storesManager.EXPECT().GetEthStoreByAddr(gomock.Any(), from, userInfo).Return(accountsStore, nil).AnyTimes() + + i := New(storesManager, testutils.NewMockLogger(ctrl)) + + t.Run("should send a private tx successfully", func(t *testing.T) { + privateArgs := (ðereum.PrivateArgs{}). + WithPrivateFrom("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="). + WithPrivateFor([]string{"KkOjNLmCI6r+mICrC6l+XuEDjFEzQllaMQMpWLl4y1s=", "eLb69r4K8/9WviwlfDiZ4jf97P9czyS3DkKu0QYGLjg="}) + msg := ðereum.SendTxMsg{ + From: from, + PrivateArgs: *privateArgs, + } + expectedEstimateGasCall := ðereum.CallMsg{ + From: &msg.From, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + GasPrice: gasPrice, + } + expectedData := new([]byte) + expectedSignedTx := []byte("mysignature") + expectedHash := ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778") + + ethCaller.EXPECT().GasPrice(ctx).Return(gasPrice, nil) + ethCaller.EXPECT().EstimateGas(ctx, expectedEstimateGasCall).Return(uint64(21000), nil) + ethCaller.EXPECT().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber).Return(uint64(0), nil) + tesseraClient.EXPECT().StoreRaw(ctx, *expectedData, *msg.PrivateFrom).Return(ethcommon.FromHex("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778"), nil) + ethCaller.EXPECT().ChainID(gomock.Any()).Return(chainID, nil) + accountsStore.EXPECT().SignPrivate(ctx, msg.From, gomock.Any()).Return(expectedSignedTx, nil) + ethCaller.EXPECT().SendRawPrivateTransaction(ctx, expectedSignedTx, privateArgs).Return(expectedHash, nil) + + hash, err := i.ethSendTransaction(ctx, msg) + require.NoError(t, err) + + assert.Equal(t, hash.Hex(), expectedHash.Hex()) + }) - tests := []*testHandlerCase{ - { - desc: "Public Transaction ", - handler: i.handler, - reqBody: []byte(`{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0x78e6e236592597c09d5c137c2af40aecd42d12a2","data":"0x5208"}]}`), - ctx: ctx, - prepare: func() { - expectedFrom := ethcommon.HexToAddress("0x78e6e236592597c09d5c137c2af40aecd42d12a2") - // Get accounts - stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) - - // Get Gas price - ethCaller.EXPECT().GasPrice(gomock.Any()).Return(big.NewInt(1000000000), nil) - - // Get Gas Limit - expectedCallMsg := (ðereum.CallMsg{ - From: &expectedFrom, - GasPrice: big.NewInt(1000000000), - }).WithData(ethcommon.FromHex("0x5208")) - - ethCaller.EXPECT().EstimateGas(gomock.Any(), expectedCallMsg).Return(uint64(21000), nil) - - // Get Nonce - ethCaller.EXPECT().GetTransactionCount(gomock.Any(), expectedFrom, ethereum.PendingBlockNumber).Return(uint64(5), nil) - - // Get ChainID - ethCaller.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1998), nil) - - // Sign - accountsStore.EXPECT().SignTransaction(gomock.Any(), expectedFrom, big.NewInt(1998), gomock.Any()).Return(ethcommon.FromHex("0xa6122e27"), nil) - - // SendRawTransaction - ethCaller.EXPECT().SendRawTransaction(gomock.Any(), ethcommon.FromHex("0xa6122e27")).Return(ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778"), nil) - }, - expectedRespBody: []byte(`{"jsonrpc":"2.0","result":"0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778","error":null,"id":null}`), - }, - { - desc: "Transaction private transaction", - handler: i.handler, - reqBody: []byte(`{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0x78e6e236592597c09d5c137c2af40aecd42d12a2","data":"0x5208","privateFrom":"GGilEkXLaQ9yhhtbpBT03Me9iYa7U/mWXxrJhnbl1XY=","privateFor":["KkOjNLmCI6r+mICrC6l+XuEDjFEzQllaMQMpWLl4y1s=","eLb69r4K8/9WviwlfDiZ4jf97P9czyS3DkKu0QYGLjg="]}]}`), - ctx: ctx, - prepare: func() { - expectedFrom := ethcommon.HexToAddress("0x78e6e236592597c09d5c137c2af40aecd42d12a2") - // Get accounts - stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) - - // Get Gas price - ethCaller.EXPECT().GasPrice(gomock.Any()).Return(big.NewInt(1000000000), nil) - - // Get Gas Limit - expectedCallMsg := (ðereum.CallMsg{ - From: &expectedFrom, - GasPrice: big.NewInt(1000000000), - }).WithData(ethcommon.FromHex("0x5208")) - - ethCaller.EXPECT().EstimateGas(gomock.Any(), expectedCallMsg).Return(uint64(21000), nil) - - // Get Nonce - ethCaller.EXPECT().GetTransactionCount(gomock.Any(), expectedFrom, ethereum.PendingBlockNumber).Return(uint64(5), nil) - - tesseraClient.EXPECT().StoreRaw(gomock.Any(), ethcommon.FromHex("0x5208"), "GGilEkXLaQ9yhhtbpBT03Me9iYa7U/mWXxrJhnbl1XY=").Return(ethcommon.FromHex("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778"), nil) - - // Get ChainID - ethCaller.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1998), nil) - - // Sign - accountsStore.EXPECT().SignPrivate(gomock.Any(), expectedFrom, gomock.Any()).Return(ethcommon.FromHex("0xa6122e27"), nil) - - expectedPrivateArgs := (ðereum.PrivateArgs{}).WithPrivateFrom("GGilEkXLaQ9yhhtbpBT03Me9iYa7U/mWXxrJhnbl1XY=").WithPrivateFor([]string{"KkOjNLmCI6r+mICrC6l+XuEDjFEzQllaMQMpWLl4y1s=", "eLb69r4K8/9WviwlfDiZ4jf97P9czyS3DkKu0QYGLjg="}) - ethCaller.EXPECT().SendRawPrivateTransaction(gomock.Any(), ethcommon.FromHex("0xa6122e27"), expectedPrivateArgs).Return(ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778"), nil) - }, - expectedRespBody: []byte(`{"jsonrpc":"2.0","result":"0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778","error":null,"id":null}`), - }, - } + t.Run("should send a legacy tx successfully", func(t *testing.T) { + msg := ðereum.SendTxMsg{ + From: from, + GasPrice: gasPrice, + } + expectedEstimateGasCall := ðereum.CallMsg{ + From: &msg.From, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + GasPrice: gasPrice, + } + expectedSignedTx := []byte("mysignature") + expectedHash := ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778") + + ethCaller.EXPECT().EstimateGas(ctx, expectedEstimateGasCall).Return(uint64(21000), nil) + ethCaller.EXPECT().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber).Return(uint64(0), nil) + ethCaller.EXPECT().ChainID(gomock.Any()).Return(chainID, nil) + accountsStore.EXPECT().SignTransaction(ctx, msg.From, chainID, gomock.Any()).Return(expectedSignedTx, nil) + ethCaller.EXPECT().SendRawTransaction(ctx, expectedSignedTx).Return(expectedHash, nil) + + hash, err := i.ethSendTransaction(ctx, msg) + require.NoError(t, err) + + assert.Equal(t, hash.Hex(), expectedHash.Hex()) + }) - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - assertHandlerScenario(t, tt) - }) - } + t.Run("should send a dynamic fee tx successfully", func(t *testing.T) { + msg := ðereum.SendTxMsg{ + From: from, + Value: value, + } + expectedEstimateGasCall := ðereum.CallMsg{ + From: &msg.From, + To: msg.To, + Value: value, + Data: msg.Data, + GasFeeCap: new(big.Int).Add(gasPrice, big.NewInt(0)), + GasTipCap: msg.GasTipCap, + AccessList: msg.AccessList, + } + expectedSignedTx := []byte("mysignature") + expectedHash := ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778") + + ethCaller.EXPECT().BaseFeePerGas(ctx, ethereum.LatestBlockNumber).Return(gasPrice, nil) + ethCaller.EXPECT().EstimateGas(ctx, expectedEstimateGasCall).Return(uint64(21000), nil) + ethCaller.EXPECT().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber).Return(uint64(0), nil) + ethCaller.EXPECT().ChainID(gomock.Any()).Return(chainID, nil) + accountsStore.EXPECT().SignTransaction(ctx, msg.From, chainID, gomock.Any()).Return(expectedSignedTx, nil) + ethCaller.EXPECT().SendRawTransaction(ctx, expectedSignedTx).Return(expectedHash, nil) + + hash, err := i.ethSendTransaction(ctx, msg) + require.NoError(t, err) + + assert.Equal(t, hash.Hex(), expectedHash.Hex()) + }) + + t.Run("should revert to legacy tx if baseFeePerGas is nil", func(t *testing.T) { + msg := ðereum.SendTxMsg{ + From: from, + Value: value, + } + expectedEstimateGasCall := ðereum.CallMsg{ + From: &msg.From, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + GasPrice: gasPrice, + } + expectedSignedTx := []byte("mysignature") + expectedHash := ethcommon.HexToHash("0x6052dd2131667ef3e0a0666f2812db2defceaec91c470bb43de92268e8306778") + + ethCaller.EXPECT().BaseFeePerGas(ctx, ethereum.LatestBlockNumber).Return(nil, nil) + ethCaller.EXPECT().GasPrice(ctx).Return(gasPrice, nil) + ethCaller.EXPECT().EstimateGas(ctx, expectedEstimateGasCall).Return(uint64(21000), nil) + ethCaller.EXPECT().GetTransactionCount(ctx, msg.From, ethereum.PendingBlockNumber).Return(uint64(0), nil) + ethCaller.EXPECT().ChainID(gomock.Any()).Return(chainID, nil) + accountsStore.EXPECT().SignTransaction(ctx, msg.From, chainID, gomock.Any()).Return(expectedSignedTx, nil) + ethCaller.EXPECT().SendRawTransaction(ctx, expectedSignedTx).Return(expectedHash, nil) + + hash, err := i.ethSendTransaction(ctx, msg) + require.NoError(t, err) + + assert.Equal(t, hash.Hex(), expectedHash.Hex()) + }) } diff --git a/src/nodes/interceptor/eth_sign_transaction.go b/src/nodes/interceptor/eth_sign_transaction.go index c8166a7a9..4c082277a 100644 --- a/src/nodes/interceptor/eth_sign_transaction.go +++ b/src/nodes/interceptor/eth_sign_transaction.go @@ -3,6 +3,8 @@ package interceptor import ( "context" + "github.com/ethereum/go-ethereum/core/types" + "github.com/consensys/quorum-key-manager/pkg/errors" "github.com/consensys/quorum-key-manager/src/auth/authenticator" @@ -21,12 +23,6 @@ func (i *Interceptor) ethSignTransaction(ctx context.Context, msg *ethereum.Send return nil, jsonrpc.InvalidParamsError(errors.InvalidParameterError(errMessage)) } - if msg.GasPrice == nil { - errMessage := "gasPrice not specified" - i.logger.Error(errMessage) - return nil, jsonrpc.InvalidParamsError(errors.InvalidParameterError(errMessage)) - } - if msg.Nonce == nil { errMessage := "nonce not specified" i.logger.Error(errMessage) @@ -54,10 +50,13 @@ func (i *Interceptor) ethSignTransaction(ctx context.Context, msg *ethereum.Send // Sign var sig []byte - if msg.IsPrivate() { + switch { + case msg.IsPrivate(): sig, err = store.SignPrivate(ctx, msg.From, msg.TxDataQuorum()) - } else { - sig, err = store.SignTransaction(ctx, msg.From, chainID, msg.TxData()) + case msg.IsLegacy(): + sig, err = store.SignTransaction(ctx, msg.From, chainID, msg.TxData(types.LegacyTxType, chainID)) + default: + sig, err = store.SignTransaction(ctx, msg.From, chainID, msg.TxData(types.DynamicFeeTxType, chainID)) } if err != nil { return nil, err diff --git a/src/nodes/interceptor/eth_sign_transaction_test.go b/src/nodes/interceptor/eth_sign_transaction_test.go index eeac88fd1..7ee20e394 100644 --- a/src/nodes/interceptor/eth_sign_transaction_test.go +++ b/src/nodes/interceptor/eth_sign_transaction_test.go @@ -47,13 +47,9 @@ func TestEthSignTransaction(t *testing.T) { ctx: ctx, prepare: func() { expectedFrom := ethcommon.HexToAddress("0x78e6e236592597c09d5c137c2af40aecd42d12a2") - // Get accounts - stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) - // Get ChainID + stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) ethCaller.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1998), nil) - - // Sign accountsStore.EXPECT().SignTransaction(gomock.Any(), expectedFrom, big.NewInt(1998), gomock.Any()).Return(ethcommon.FromHex("0xa6122e27"), nil) }, reqBody: []byte(`{"jsonrpc":"2.0","method":"eth_signTransaction","params":[{"from":"0x78e6e236592597c09d5c137c2af40aecd42d12a2","gas":"0x5208","gasPrice":"0x9172a000","nonce":"0x5","data":"0x5208","value":"0x1"}]}`), @@ -65,16 +61,12 @@ func TestEthSignTransaction(t *testing.T) { ctx: ctx, prepare: func() { expectedFrom := ethcommon.HexToAddress("0x78e6e236592597c09d5c137c2af40aecd42d12a2") - // Get accounts - stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) - // Get ChainID + stores.EXPECT().GetEthStoreByAddr(gomock.Any(), expectedFrom, userInfo).Return(accountsStore, nil) ethCaller.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1998), nil) - - // Sign accountsStore.EXPECT().SignPrivate(gomock.Any(), expectedFrom, gomock.Any()).Return(ethcommon.FromHex("0xa6122e27"), nil) }, - reqBody: []byte(`{"jsonrpc":"2.0","method":"eth_signTransaction","params":[{"from":"0x78e6e236592597c09d5c137c2af40aecd42d12a2","gas":"0x5208","gasPrice":"0x9184e72a000","nonce":"0x5","data":"0x5208","value":"0x1","privateFor":["KkOjNLmCI6r+mICrC6l+XuEDjFEzQllaMQMpWLl4y1s=","eLb69r4K8/9WviwlfDiZ4jf97P9czyS3DkKu0QYGLjg="]}]}`), + reqBody: []byte(`{"jsonrpc":"2.0","method":"eth_signTransaction","params":[{"from":"0x78e6e236592597c09d5c137c2af40aecd42d12a2","gas":"0x5208","gasPrice":"0x9184e72a000","nonce":"0x5","data":"0x5208","value":"0x1","privateFrom":"KkOjNLmCI6r+mICrC6l+XuEDjFEzQllaMQMpWLl4y1s="}]}`), expectedRespBody: []byte(`{"jsonrpc":"2.0","result":"0xa6122e27","error":null,"id":null}`), }, } diff --git a/src/stores/api/types/ethereum.go b/src/stores/api/types/ethereum.go index 1a298a5dc..efb603d4d 100644 --- a/src/stores/api/types/ethereum.go +++ b/src/stores/api/types/ethereum.go @@ -67,7 +67,7 @@ type SignETHTransactionRequest struct { ChainID hexutil.Big `json:"chainID" validate:"required" example:"0x1 (mainnet)" swaggertype:"string"` GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty" example:"0x5208" swaggertype:"string"` GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty" example:"0x5208" swaggertype:"string"` - AccessList types.AccessList `json:"accessList,omitempty"` + AccessList types.AccessList `json:"accessList,omitempty" swaggertype:"string"` } type SignQuorumPrivateTransactionRequest struct { diff --git a/tests/config.go b/tests/config.go index fbe51243a..d4185ad6c 100644 --- a/tests/config.go +++ b/tests/config.go @@ -22,6 +22,7 @@ type Config struct { EthStores []string `json:"eth_stores"` QuorumNodeID string `json:"quorum_node_id"` BesuNodeID string `json:"besu_node_id"` + GethNodeID string `json:"geth_node_id"` Postgres *pgclient.Config `json:"postgres"` } diff --git a/tests/e2e/json_rpc_test.go b/tests/e2e/json_rpc_test.go index e911700e4..fc47bc875 100644 --- a/tests/e2e/json_rpc_test.go +++ b/tests/e2e/json_rpc_test.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/ethereum/go-ethereum/common/hexutil" "net/http" "os" "strconv" @@ -32,6 +33,7 @@ type jsonRPCTestSuite struct { storeName string QuorumNodeID string BesuNodeID string + GethNodeID string } func TestJSONRpcHTTP(t *testing.T) { @@ -52,19 +54,20 @@ func TestJSONRpcHTTP(t *testing.T) { } var token string - token, s.err = generateJWT("./certificates/client.key","*:*", "e2e|json_rpc_test") + token, s.err = generateJWT("./certificates/client.key", "*:*", "e2e|json_rpc_test") if s.err != nil { t.Errorf("failed to generate jwt. %s", s.err) return } s.keyManagerClient = client.NewHTTPClient(&http.Client{ - Transport: NewTestHttpTransport(token, "",nil), + Transport: NewTestHttpTransport(token, "", nil), }, &client.Config{ URL: cfg.KeyManagerURL, }) s.BesuNodeID = cfg.BesuNodeID s.QuorumNodeID = cfg.QuorumNodeID + s.GethNodeID = cfg.GethNodeID s.storeName = cfg.EthStores[0] suite.Run(t, s) } @@ -74,8 +77,14 @@ func (s *jsonRPCTestSuite) SetupSuite() { s.T().Error(s.err) } - s.acc, s.err = s.keyManagerClient.CreateEthAccount(s.ctx, s.storeName, &types.CreateEthAccountRequest{ - KeyID: fmt.Sprintf("test-eth-sign-%d", common.RandInt(1000)), + privKey, err := hexutil.Decode("0x56202652fdffd802b7252a456dbd8f3ecc0352bbde76c23b40afe8aebd714e2e") + if s.err != nil { + s.T().Error(err) + } + + s.acc, s.err = s.keyManagerClient.ImportEthAccount(s.ctx, s.storeName, &types.ImportEthAccountRequest{ + KeyID: fmt.Sprintf("test-eth-sign-%d", common.RandInt(1000)), + PrivateKey: privKey, }) if s.err != nil { @@ -137,7 +146,7 @@ func (s *jsonRPCTestSuite) TestEthSign() { } func (s *jsonRPCTestSuite) TestEthSignTransaction() { - s.Run("should call eth_signTransaction successfully", func() { + s.Run("should call eth_signTransaction successfully; legacy tx", func() { resp, err := s.keyManagerClient.Call(s.ctx, s.QuorumNodeID, "eth_signTransaction", map[string]interface{}{ "data": "0xa2", "from": s.acc.Address, @@ -150,6 +159,20 @@ func (s *jsonRPCTestSuite) TestEthSignTransaction() { require.Nil(s.T(), resp.Error) }) + s.Run("should call eth_signTransaction successfully; dynamic fee tx", func() { + resp, err := s.keyManagerClient.Call(s.ctx, s.QuorumNodeID, "eth_signTransaction", map[string]interface{}{ + "data": "0xa2", + "from": s.acc.Address, + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "nonce": "0x1", + "gas": "0x989680", + "maxFeePerGas": "0x10000", + "maxPriorityFeePerGas": "0x1000", + }) + require.NoError(s.T(), err) + require.Nil(s.T(), resp.Error) + }) + s.Run("should call eth_signTransaction and fail to sign with an invalid account", func() { resp, err := s.keyManagerClient.Call(s.ctx, s.QuorumNodeID, "eth_sign", map[string]interface{}{ "data": "0xa2", @@ -178,12 +201,14 @@ func (s *jsonRPCTestSuite) TestEthSignTransaction() { func (s *jsonRPCTestSuite) TestEthSendTransaction() { toAddr := "0xd46e8dd67c5d32be8058bb8eb970870f07244567" - s.Run("should call eth_sendTransaction, successfully", func() { - resp, err := s.keyManagerClient.Call(s.ctx, s.QuorumNodeID, "eth_sendTransaction", map[string]interface{}{ - "data": "0xa2", - "from": s.acc.Address, - "to": toAddr, - "gas": "0x989680", + + s.Run("should call eth_sendTransaction successfully: legacy tx", func() { + resp, err := s.keyManagerClient.Call(s.ctx, s.GethNodeID, "eth_sendTransaction", map[string]interface{}{ + "data": "0xa2", + "from": s.acc.Address, + "to": toAddr, + "gas": "0x989680", + "gasPrice": "0x7", }) require.NoError(s.T(), err) @@ -192,7 +217,27 @@ func (s *jsonRPCTestSuite) TestEthSendTransaction() { var result string err = json.Unmarshal(resp.Result.(json.RawMessage), &result) assert.NoError(s.T(), err) - tx, err := s.retrieveTransaction(s.ctx, s.QuorumNodeID, result) + tx, err := s.retrieveTransaction(s.ctx, s.GethNodeID, result) + require.NoError(s.T(), err) + assert.Equal(s.T(), strings.ToLower(tx.To().String()), toAddr) + }) + + s.Run("should call eth_sendTransaction successfully: dynamic fee tx", func() { + resp, err := s.keyManagerClient.Call(s.ctx, s.GethNodeID, "eth_sendTransaction", map[string]interface{}{ + "data": "0xa2", + "from": s.acc.Address, + "to": toAddr, + "gas": "0x989680", + "value": "0x1", + }) + + require.NoError(s.T(), err) + require.Nil(s.T(), resp.Error) + + var result string + err = json.Unmarshal(resp.Result.(json.RawMessage), &result) + assert.NoError(s.T(), err) + tx, err := s.retrieveTransaction(s.ctx, s.GethNodeID, result) require.NoError(s.T(), err) assert.Equal(s.T(), strings.ToLower(tx.To().String()), toAddr) })