From f937095274a9234a115028bbbe96ec192e68ee9c Mon Sep 17 00:00:00 2001 From: tclemos Date: Tue, 24 Jan 2023 10:31:06 -0300 Subject: [PATCH 1/6] add support to pre-EIP155 txs --- pool/pool.go | 3 +- pool/pool_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++ state/crypto.go | 6 ++- state/helper.go | 55 ++++++++++++++++++-------- state/transaction.go | 70 +++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+), 20 deletions(-) diff --git a/pool/pool.go b/pool/pool.go index 927c388f3a..fc8bed2282 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -125,7 +125,8 @@ func (p *Pool) IsTxPending(ctx context.Context, hash common.Hash) (bool, error) func (p *Pool) validateTx(ctx context.Context, tx types.Transaction) error { // check chain id - if tx.ChainId().Uint64() != p.chainID { + txChainID := tx.ChainId().Uint64() + if txChainID != p.chainID && txChainID != 0 { return ErrInvalidChainID } diff --git a/pool/pool_test.go b/pool/pool_test.go index 9dc6f8bfe0..eef67b4f37 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -149,6 +149,98 @@ func Test_AddTx(t *testing.T) { assert.Equal(t, 1, c, "invalid number of txs in the pool") } +func Test_AddPreEIP155Tx(t *testing.T) { + initOrResetDB() + + stateSqlDB, err := db.NewSQLDB(stateDBCfg) + if err != nil { + panic(err) + } + defer stateSqlDB.Close() //nolint:gosec,errcheck + + poolSqlDB, err := db.NewSQLDB(poolDBCfg) + if err != nil { + t.Error(err) + } + defer poolSqlDB.Close() //nolint:gosec,errcheck + + st := newState(stateSqlDB) + + genesisBlock := state.Block{ + BlockNumber: 0, + BlockHash: state.ZeroHash, + ParentHash: state.ZeroHash, + ReceivedAt: time.Now(), + } + genesis := state.Genesis{ + Actions: []*state.GenesisAction{ + { + Address: "0xb48cA794d49EeC406A5dD2c547717e37b5952a83", + Type: int(merkletree.LeafTypeBalance), + Value: "1000000000000000000000", + }, + { + Address: "0x4d5Cf5032B2a844602278b01199ED191A86c93ff", + Type: int(merkletree.LeafTypeBalance), + Value: "200000000000000000000", + }, + }, + } + ctx := context.Background() + dbTx, err := st.BeginStateTransaction(ctx) + require.NoError(t, err) + _, err = st.SetGenesis(ctx, genesisBlock, genesis, dbTx) + require.NoError(t, err) + require.NoError(t, dbTx.Commit(ctx)) + + s, err := pgpoolstorage.NewPostgresPoolStorage(poolDBCfg) + if err != nil { + t.Error(err) + } + + const chainID = 2576980377 + cfg := pool.Config{ + FreeClaimGasLimit: 150000, + } + p := pool.NewPool(cfg, s, st, common.Address{}, chainID) + + batchL2Data := "0xe580843b9aca00830186a0941275fbb540c8efc58b812ba83b0d0b8b9917ae98808464fbb77c6b39bdc5f8e458aba689f2a1ff8c543a94e4817bda40f3fe34080c4ab26c1e3c2fc2cda93bc32f0a79940501fd505dcf48d94abfde932ebf1417f502cb0d9de81b" + b, err := hex.DecodeHex(batchL2Data) + require.NoError(t, err) + txs, _, err := state.DecodeTxs(b) + require.NoError(t, err) + + tx := txs[0] + + err = p.AddTx(ctx, tx) + require.NoError(t, err) + + rows, err := poolSqlDB.Query(ctx, "SELECT hash, encoded, decoded, status FROM pool.txs") + defer rows.Close() // nolint:staticcheck + require.NoError(t, err) + + c := 0 + for rows.Next() { + var hash, encoded, decoded, status string + err := rows.Scan(&hash, &encoded, &decoded, &status) + require.NoError(t, err) + + b, err := tx.MarshalBinary() + require.NoError(t, err) + + bJSON, err := tx.MarshalJSON() + require.NoError(t, err) + + assert.Equal(t, tx.Hash().String(), hash, "invalid hash") + assert.Equal(t, hex.EncodeToHex(b), encoded, "invalid encoded") + assert.JSONEq(t, string(bJSON), decoded, "invalid decoded") + assert.Equal(t, string(pool.TxStatusPending), status, "invalid tx status") + c++ + } + + assert.Equal(t, 1, c, "invalid number of txs in the pool") +} + func Test_GetPendingTxs(t *testing.T) { initOrResetDB() diff --git a/state/crypto.go b/state/crypto.go index 1e31420b95..50c3090b6a 100644 --- a/state/crypto.go +++ b/state/crypto.go @@ -16,8 +16,10 @@ var ( func CheckSignature(tx types.Transaction) error { // Check Signature v, r, s := tx.RawSignatureValues() - plainV := byte(v.Uint64() - 35 - 2*(tx.ChainId().Uint64())) - + plainV := byte(0) + if tx.ChainId().Uint64() != 0 { + plainV = byte(v.Uint64() - 35 - 2*(tx.ChainId().Uint64())) + } if !crypto.ValidateSignatureValues(plainV, r, s, false) { return ErrInvalidSig } diff --git a/state/helper.go b/state/helper.go index 58b54765f9..aea4d10608 100644 --- a/state/helper.go +++ b/state/helper.go @@ -12,7 +12,11 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -const ether155V = 27 +const ( + double = 2 + ether155V = 27 + etherNewV = 35 +) // EncodeTransactions RLP encodes the given transactions. func EncodeTransactions(txs []types.Transaction) ([]byte, error) { @@ -25,15 +29,22 @@ func EncodeTransactions(txs []types.Transaction) ([]byte, error) { nonce, gasPrice, gas, to, value, data, chainID := tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), tx.ChainId() log.Debug(nonce, " ", gasPrice, " ", gas, " ", to, " ", value, " ", len(data), " ", chainID) - txCodedRlp, err := rlp.EncodeToBytes([]interface{}{ + rlpFieldsToEncode := []interface{}{ nonce, gasPrice, gas, to, value, data, - chainID, uint(0), uint(0), - }) + } + + if tx.ChainId().Uint64() > 0 { + rlpFieldsToEncode = append(rlpFieldsToEncode, chainID) + rlpFieldsToEncode = append(rlpFieldsToEncode, uint(0)) + rlpFieldsToEncode = append(rlpFieldsToEncode, uint(0)) + } + + txCodedRlp, err := rlp.EncodeToBytes(rlpFieldsToEncode) if err != nil { return nil, err @@ -91,7 +102,7 @@ func EncodeUnsignedTransaction(tx types.Transaction, chainID uint64) ([]byte, er return txData, nil } -// DecodeTxs extracts Tansactions for its encoded form +// DecodeTxs extracts Transactions for its encoded form func DecodeTxs(txsData []byte) ([]types.Transaction, []byte, error) { // Process coded txs var pos int64 @@ -105,7 +116,6 @@ func DecodeTxs(txsData []byte) ([]types.Transaction, []byte, error) { ff = 255 // max value of rlp header shortRlp = 55 // length of the short rlp codification f7 = 247 // 192 + 55 = c0 + shortRlp - etherNewV = 35 mul2 = 2 ) txDataLength := len(txsData) @@ -132,26 +142,37 @@ func DecodeTxs(txsData []byte) ([]types.Transaction, []byte, error) { fullDataTx := txsData[pos : pos+len+rLength+sLength+vLength+headerByteLength] txInfo := txsData[pos : pos+len+headerByteLength] - r := txsData[pos+len+headerByteLength : pos+len+rLength+headerByteLength] - s := txsData[pos+len+rLength+headerByteLength : pos+len+rLength+sLength+headerByteLength] - v := txsData[pos+len+rLength+sLength+headerByteLength : pos+len+rLength+sLength+vLength+headerByteLength] + rData := txsData[pos+len+headerByteLength : pos+len+rLength+headerByteLength] + sData := txsData[pos+len+rLength+headerByteLength : pos+len+rLength+sLength+headerByteLength] + vData := txsData[pos+len+rLength+sLength+headerByteLength : pos+len+rLength+sLength+vLength+headerByteLength] pos = pos + len + rLength + sLength + vLength + headerByteLength - // Decode tx - var tx types.LegacyTx - err = rlp.DecodeBytes(txInfo, &tx) + // Decode rlpFields + var rlpFields [][]byte + err = rlp.DecodeBytes(txInfo, &rlpFields) if err != nil { log.Debug("error decoding tx bytes: ", err, ". fullDataTx: ", hex.EncodeToString(fullDataTx), "\n tx: ", hex.EncodeToString(txInfo), "\n Txs received: ", hex.EncodeToString(txsData)) return []types.Transaction{}, []byte{}, err } - //tx.V = v-27+chainId*2+35 - tx.V = new(big.Int).Add(new(big.Int).Sub(new(big.Int).SetBytes(v), big.NewInt(ether155V)), new(big.Int).Add(new(big.Int).Mul(tx.V, big.NewInt(mul2)), big.NewInt(etherNewV))) - tx.R = new(big.Int).SetBytes(r) - tx.S = new(big.Int).SetBytes(s) + legacyTx, chainID, err := RlpFieldsToLegacyTx(rlpFields) + if err != nil { + log.Debug("error creating tx from rlp fields: ", err, ". fullDataTx: ", hex.EncodeToString(fullDataTx), "\n tx: ", hex.EncodeToString(txInfo), "\n Txs received: ", hex.EncodeToString(txsData)) + return []types.Transaction{}, []byte{}, err + } + + if chainID != nil { + //v = v-27+chainId*2+35 + legacyTx.V = new(big.Int).Add(new(big.Int).Sub(new(big.Int).SetBytes(vData), big.NewInt(ether155V)), new(big.Int).Add(new(big.Int).Mul(chainID, big.NewInt(mul2)), big.NewInt(etherNewV))) + } else { + legacyTx.V = new(big.Int).SetBytes(vData) + } + legacyTx.R = new(big.Int).SetBytes(rData) + legacyTx.S = new(big.Int).SetBytes(sData) - txs = append(txs, *types.NewTx(&tx)) + tx := types.NewTx(legacyTx) + txs = append(txs, *tx) } return txs, txsData, nil } diff --git a/state/transaction.go b/state/transaction.go index b2fe99e562..e870f1cd34 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -1,6 +1,8 @@ package state import ( + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -14,3 +16,71 @@ func GetSender(tx types.Transaction) (common.Address, error) { } return sender, nil } + +// RlpFieldsToLegacyTx parses the rlp fields slice into a type.LegacyTx +// in this specific order: +// +// required fields: +// [0] Nonce uint64 +// [1] GasPrice *big.Int +// [2] Gas uint64 +// [3] To *common.Address +// [4] Value *big.Int +// [5] Data []byte +// +// optional fields: +// [6] V *big.Int +// [7] R *big.Int +// [8] S *big.Int +func RlpFieldsToLegacyTx(fields [][]byte) (tx *types.LegacyTx, chainID *big.Int, err error) { + const ( + fieldsSizeWithoutChainID = 6 + fieldsSizeWithV = 7 + fieldsSizeWithVR = 8 + fieldsSizeWithVRS = 9 + ) + + if len(fields) != fieldsSizeWithoutChainID && len(fields) != fieldsSizeWithV && len(fields) != fieldsSizeWithVRS { + return nil, nil, types.ErrTxTypeNotSupported + } + + nonce := big.NewInt(0).SetBytes(fields[0]).Uint64() + gasPrice := big.NewInt(0).SetBytes(fields[1]) + gas := big.NewInt(0).SetBytes(fields[2]).Uint64() + var to *common.Address + if fields[3] != nil { + tmp := common.BytesToAddress(fields[3]) + to = &tmp + } + value := big.NewInt(0).SetBytes(fields[4]) + data := fields[5] + + v := big.NewInt(0) + if len(fields) >= fieldsSizeWithV { + v = big.NewInt(0).SetBytes(fields[6]) + chainID = big.NewInt(0).Sub(v, big.NewInt(0).SetUint64(etherNewV)) + chainID = big.NewInt(0).Quo(chainID, big.NewInt(double)) + } + + r := big.NewInt(0) + if len(fields) >= fieldsSizeWithVR { + r = big.NewInt(0).SetBytes(fields[7]) + } + + s := big.NewInt(0) + if len(fields) >= fieldsSizeWithVRS { + s = big.NewInt(0).SetBytes(fields[8]) + } + + return &types.LegacyTx{ + Nonce: nonce, + GasPrice: gasPrice, + Gas: gas, + To: to, + Value: value, + Data: data, + V: v, + R: r, + S: s, + }, chainID, nil +} From b8b22bd57bd0aeab022243faab5de39828f22f5f Mon Sep 17 00:00:00 2001 From: tclemos Date: Wed, 25 Jan 2023 10:27:39 -0300 Subject: [PATCH 2/6] =) --- state/crypto.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/state/crypto.go b/state/crypto.go index 50c3090b6a..72e6a1f102 100644 --- a/state/crypto.go +++ b/state/crypto.go @@ -17,8 +17,9 @@ func CheckSignature(tx types.Transaction) error { // Check Signature v, r, s := tx.RawSignatureValues() plainV := byte(0) - if tx.ChainId().Uint64() != 0 { - plainV = byte(v.Uint64() - 35 - 2*(tx.ChainId().Uint64())) + chainID := tx.ChainId().Uint64() + if chainID != 0 { + plainV = byte(v.Uint64() - 35 - 2*(chainID)) } if !crypto.ValidateSignatureValues(plainV, r, s, false) { return ErrInvalidSig From 6a272b6d30830924365da5d19ab931712dc26be4 Mon Sep 17 00:00:00 2001 From: tclemos Date: Thu, 26 Jan 2023 11:01:54 -0300 Subject: [PATCH 3/6] naming things --- state/helper.go | 8 ++++---- state/transaction.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/state/helper.go b/state/helper.go index aea4d10608..51d7025c36 100644 --- a/state/helper.go +++ b/state/helper.go @@ -13,9 +13,9 @@ import ( ) const ( - double = 2 - ether155V = 27 - etherNewV = 35 + double = 2 + ether155V = 27 + etherPre155V = 35 ) // EncodeTransactions RLP encodes the given transactions. @@ -164,7 +164,7 @@ func DecodeTxs(txsData []byte) ([]types.Transaction, []byte, error) { if chainID != nil { //v = v-27+chainId*2+35 - legacyTx.V = new(big.Int).Add(new(big.Int).Sub(new(big.Int).SetBytes(vData), big.NewInt(ether155V)), new(big.Int).Add(new(big.Int).Mul(chainID, big.NewInt(mul2)), big.NewInt(etherNewV))) + legacyTx.V = new(big.Int).Add(new(big.Int).Sub(new(big.Int).SetBytes(vData), big.NewInt(ether155V)), new(big.Int).Add(new(big.Int).Mul(chainID, big.NewInt(mul2)), big.NewInt(etherPre155V))) } else { legacyTx.V = new(big.Int).SetBytes(vData) } diff --git a/state/transaction.go b/state/transaction.go index e870f1cd34..616ef27175 100644 --- a/state/transaction.go +++ b/state/transaction.go @@ -58,7 +58,7 @@ func RlpFieldsToLegacyTx(fields [][]byte) (tx *types.LegacyTx, chainID *big.Int, v := big.NewInt(0) if len(fields) >= fieldsSizeWithV { v = big.NewInt(0).SetBytes(fields[6]) - chainID = big.NewInt(0).Sub(v, big.NewInt(0).SetUint64(etherNewV)) + chainID = big.NewInt(0).Sub(v, big.NewInt(0).SetUint64(etherPre155V)) chainID = big.NewInt(0).Quo(chainID, big.NewInt(double)) } From 9a21a92e6a542fb04b0fd67e0b5e7e5e73458208 Mon Sep 17 00:00:00 2001 From: tclemos Date: Thu, 26 Jan 2023 11:45:08 -0300 Subject: [PATCH 4/6] add pre-EIP-155 tx e2e test --- ci/e2e-group1/preEIP155_test.go | 1 + test/docker-compose.yml | 2 +- test/e2e/preEIP155_test.go | 79 +++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 120000 ci/e2e-group1/preEIP155_test.go create mode 100644 test/e2e/preEIP155_test.go diff --git a/ci/e2e-group1/preEIP155_test.go b/ci/e2e-group1/preEIP155_test.go new file mode 120000 index 0000000000..3893978046 --- /dev/null +++ b/ci/e2e-group1/preEIP155_test.go @@ -0,0 +1 @@ +../../test/e2e/preEIP155_test.go \ No newline at end of file diff --git a/test/docker-compose.yml b/test/docker-compose.yml index f52ca4f966..e15e8295eb 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -297,7 +297,7 @@ services: ports: - 8545:8545 - 8546:8546 - command: ["--http", "--http.api", "admin,eth,debug,miner,net,txpool,personal,web3", "--http.addr", "0.0.0.0","--http.corsdomain", "*", "--http.vhosts" ,"*", "--ws", "--ws.origins", "*", "--ws.addr", "0.0.0.0", "--dev", "--datadir", "/geth_data", "--syncmode", "full"] + command: ["--http", "--http.api", "admin,eth,debug,miner,net,txpool,personal,web3", "--http.addr", "0.0.0.0","--http.corsdomain", "*", "--http.vhosts" ,"*", "--ws", "--ws.origins", "*", "--ws.addr", "0.0.0.0", "--dev", "--datadir", "/geth_data", "--syncmode", "full", "--rpc.allow-unprotected-txs"] zkevm-prover: container_name: zkevm-prover diff --git a/test/e2e/preEIP155_test.go b/test/e2e/preEIP155_test.go new file mode 100644 index 0000000000..d66c38ddb3 --- /dev/null +++ b/test/e2e/preEIP155_test.go @@ -0,0 +1,79 @@ +package e2e + +import ( + "context" + "strings" + "testing" + + "github.com/0xPolygonHermez/zkevm-node/hex" + "github.com/0xPolygonHermez/zkevm-node/log" + "github.com/0xPolygonHermez/zkevm-node/test/operations" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestPreEIP155Tx(t *testing.T) { + if testing.Short() { + t.Skip() + } + + var err error + err = operations.Teardown() + require.NoError(t, err) + + defer func() { + require.NoError(t, operations.Teardown()) + }() + + ctx := context.Background() + opsCfg := operations.GetDefaultOperationsConfig() + opsMan, err := operations.NewManager(ctx, opsCfg) + require.NoError(t, err) + err = opsMan.Setup() + require.NoError(t, err) + + for _, network := range networks { + log.Debugf(network.Name) + client := operations.MustGetClient(network.URL) + auth := operations.MustGetAuth(network.PrivateKey, network.ChainID) + + nonce, err := client.PendingNonceAt(ctx, auth.From) + require.NoError(t, err) + + gasPrice, err := client.SuggestGasPrice(ctx) + require.NoError(t, err) + + to := common.HexToAddress("0x1275fbb540c8efc58b812ba83b0d0b8b9917ae98") + data := hex.DecodeHexToBig("64fbb77c").Bytes() + + gas, err := client.EstimateGas(ctx, ethereum.CallMsg{ + From: auth.From, + To: &to, + Data: data, + }) + require.NoError(t, err) + + tx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &to, + GasPrice: gasPrice, + Gas: gas, + Data: data, + }) + + privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(network.PrivateKey, "0x")) + require.NoError(t, err) + + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey) + require.NoError(t, err) + + err = client.SendTransaction(ctx, signedTx) + require.NoError(t, err) + + err = operations.WaitTxToBeMined(ctx, client, signedTx, operations.DefaultTimeoutTxToBeMined) + require.NoError(t, err) + } +} From 7ce97507b5f9f9b7575bea0a2b11d40dc14785a2 Mon Sep 17 00:00:00 2001 From: tclemos Date: Thu, 26 Jan 2023 11:54:14 -0300 Subject: [PATCH 5/6] fix merge with develop --- test/e2e/debug_test.go | 7 +++++++ test/e2e/preEIP155_test.go | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/e2e/debug_test.go diff --git a/test/e2e/debug_test.go b/test/e2e/debug_test.go new file mode 100644 index 0000000000..4268e3a40d --- /dev/null +++ b/test/e2e/debug_test.go @@ -0,0 +1,7 @@ +package e2e + +import "testing" + +func TestDebugTraceTransaction(t *testing.T) { + +} diff --git a/test/e2e/preEIP155_test.go b/test/e2e/preEIP155_test.go index d66c38ddb3..aaf171b1ff 100644 --- a/test/e2e/preEIP155_test.go +++ b/test/e2e/preEIP155_test.go @@ -47,7 +47,7 @@ func TestPreEIP155Tx(t *testing.T) { require.NoError(t, err) to := common.HexToAddress("0x1275fbb540c8efc58b812ba83b0d0b8b9917ae98") - data := hex.DecodeHexToBig("64fbb77c").Bytes() + data := hex.DecodeBig("0x64fbb77c").Bytes() gas, err := client.EstimateGas(ctx, ethereum.CallMsg{ From: auth.From, From c6e8361116bf8d81c01b14ce54e328b47b633883 Mon Sep 17 00:00:00 2001 From: tclemos Date: Thu, 26 Jan 2023 12:58:20 -0300 Subject: [PATCH 6/6] add check for the vitualized batch when testing pre-EIP155 tx --- test/e2e/preEIP155_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/e2e/preEIP155_test.go b/test/e2e/preEIP155_test.go index aaf171b1ff..57ce1a536f 100644 --- a/test/e2e/preEIP155_test.go +++ b/test/e2e/preEIP155_test.go @@ -4,6 +4,7 @@ import ( "context" "strings" "testing" + "time" "github.com/0xPolygonHermez/zkevm-node/hex" "github.com/0xPolygonHermez/zkevm-node/log" @@ -75,5 +76,15 @@ func TestPreEIP155Tx(t *testing.T) { err = operations.WaitTxToBeMined(ctx, client, signedTx, operations.DefaultTimeoutTxToBeMined) require.NoError(t, err) + + receipt, err := client.TransactionReceipt(ctx, signedTx.Hash()) + require.NoError(t, err) + + // wait for l2 block to be virtualized + if network.ChainID == operations.DefaultL2ChainID { + log.Infof("waiting for the block number %v to be virtualized", receipt.BlockNumber.String()) + err = operations.WaitL2BlockToBeVirtualized(receipt.BlockNumber, 4*time.Minute) //nolint:gomnd + require.NoError(t, err) + } } }