Skip to content

Commit

Permalink
eth_sendTransaction method and the corresponding tests (UT and e2e)
Browse files Browse the repository at this point in the history
  • Loading branch information
novosandara committed Mar 27, 2024
1 parent 9d88475 commit c7d258b
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 85 deletions.
7 changes: 6 additions & 1 deletion consensus/polybft/wallet/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ func (a *Account) Save(secretsManager secrets.SecretsManager) (err error) {
}

func (a *Account) GetEcdsaPrivateKey() (*ecdsa.PrivateKey, error) {
ecdsaRaw, err := a.Ecdsa.MarshallPrivateKey()
return AdaptECDSAPrivKey(a.Ecdsa)
}

// AdaptECDSAPrivKey converts ecdsa private key from wallet.Key to ecdsa.PrivateKey instance
func AdaptECDSAPrivKey(ecdsaKey *crypto.ECDSAKey) (*ecdsa.PrivateKey, error) {
ecdsaRaw, err := ecdsaKey.MarshallPrivateKey()
if err != nil {
return nil, err
}
Expand Down
22 changes: 22 additions & 0 deletions e2e-polybft/e2e/jsonrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,4 +286,26 @@ func TestE2E_JsonRPC(t *testing.T) {
require.NoError(t, err)
require.NotEqual(t, types.ZeroHash, hash)
})

t.Run("eth_sendTransaction", func(t *testing.T) {
receiver := types.StringToAddress("0xDEADFFFF")
gasPrice, err := newEthClient.GasPrice()
require.NoError(t, err)

_, newAccountAddr := tests.GenerateKeyAndAddr(t)

txn := types.NewTx(
types.NewLegacyTx(
types.WithNonce(0),
types.WithFrom(newAccountAddr),
types.WithTo(&receiver),
types.WithValue(ethgo.Gwei(1)),
types.WithGas(21000),
types.WithGasPrice(new(big.Int).SetUint64(gasPrice)),
))

hash, err := newEthClient.SendTransaction(txn)
require.NoError(t, err)
require.NotEqual(t, types.ZeroHash, hash)
})
}
9 changes: 9 additions & 0 deletions helper/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ func BigMin(x, y *big.Int) *big.Int {
return x
}

// BigMax returns the larger of x or y.
func BigMax(x, y *big.Int) *big.Int {
if x.Cmp(y) > 0 {
return x
}

return y
}

func ConvertUnmarshalledUint(x interface{}) (uint64, error) {
switch tx := x.(type) {
case float64:
Expand Down
2 changes: 1 addition & 1 deletion jsonrpc/debug_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (d *Debug) TraceCall(
return nil, ErrHeaderNotFound
}

tx, err := DecodeTxn(arg, header.Number, d.store, true)
tx, err := DecodeTxn(arg, d.store, true)
if err != nil {
return nil, err
}
Expand Down
22 changes: 11 additions & 11 deletions jsonrpc/debug_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,17 +593,17 @@ func TestTraceCall(t *testing.T) {
blockNumber = BlockNumber(testBlock10.Number())

txArg = &txnArgs{
From: &from,
To: &to,
Gas: &gas,
GasPrice: &gasPrice,
GasTipCap: &gasTipCap,
GasFeeCap: &gasFeeCap,
Value: &value,
Data: &data,
Input: &input,
Nonce: &nonce,
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
From: &from,
To: &to,
Gas: &gas,
GasPrice: &gasPrice,
MaxPriorityFeePerGas: &gasTipCap,
MaxFeePerGas: &gasFeeCap,
Value: &value,
Data: &data,
Input: &input,
Nonce: &nonce,
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
}
decodedTx = types.NewTx(types.NewDynamicFeeTx(
types.WithGasTipCap(new(big.Int).SetBytes([]byte(gasTipCap))),
Expand Down
3 changes: 3 additions & 0 deletions jsonrpc/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"
"unicode"

"github.com/0xPolygon/polygon-edge/crypto"
"github.com/0xPolygon/polygon-edge/secrets"
"github.com/armon/go-metrics"
"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -68,6 +69,7 @@ type dispatcherParams struct {
concurrentRequestsDebug uint64

secretsManager secrets.SecretsManager
txSigner crypto.TxSigner
}

func (dp dispatcherParams) isExceedingBatchLengthLimit(value uint64) bool {
Expand Down Expand Up @@ -106,6 +108,7 @@ func (d *Dispatcher) registerEndpoints(store JSONRPCStore) error {
d.params.secretsManager,
d.params.chainID,
d.params.priceLimit,
d.params.txSigner,
)
if err != nil {
return err
Expand Down
46 changes: 38 additions & 8 deletions jsonrpc/eth_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type Eth struct {
filterManager *FilterManager
priceLimit uint64
ecdsaKey *crypto.ECDSAKey
txSigner crypto.TxSigner
}

func NewEth(
Expand All @@ -117,7 +118,8 @@ func NewEth(
filterManager *FilterManager,
secretsManager secrets.SecretsManager,
chainID uint64,
priceLimit uint64) (*Eth, error) {
priceLimit uint64,
txSigner crypto.TxSigner) (*Eth, error) {
var (
ecdsaKey *crypto.ECDSAKey
err error
Expand All @@ -137,6 +139,7 @@ func NewEth(
ecdsaKey: ecdsaKey,
priceLimit: priceLimit,
filterManager: filterManager,
txSigner: txSigner,
}, nil
}

Expand Down Expand Up @@ -271,7 +274,7 @@ func (e *Eth) CreateAccessList(arg *txnArgs, filter BlockNumberOrHash) (interfac
return nil, err
}

transaction, err := DecodeTxn(arg, header.Number, e.store, true)
transaction, err := DecodeTxn(arg, e.store, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -376,10 +379,37 @@ func (e *Eth) SendRawTransaction(buf argBytes) (interface{}, error) {
return tx.Hash().String(), nil
}

// SendTransaction rejects eth_sendTransaction json-rpc call as we don't support wallet management
func (e *Eth) SendTransaction(_ *txnArgs) (interface{}, error) {
return nil, fmt.Errorf("request calls to eth_sendTransaction method are not supported," +
" use eth_sendRawTransaction instead")
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (e *Eth) SendTransaction(args *txnArgs) (interface{}, error) {
if e.ecdsaKey == nil {
return nil, errMissingPrivateKey
}

if err := args.setDefaults(e.priceLimit, e); err != nil {
return nil, err
}

tx, err := DecodeTxn(args, e.store, true)
if err != nil {
return nil, err
}

cryptoECDSAPrivKey, err := polyWallet.AdaptECDSAPrivKey(e.ecdsaKey)
if err != nil {
return nil, err
}

signedTx, err := e.txSigner.SignTx(tx, cryptoECDSAPrivKey)
if err != nil {
return nil, err
}

if err := e.store.AddTx(signedTx); err != nil {
return nil, err
}

return signedTx.Hash().String(), nil
}

// GetTransactionByHash returns a transaction by its hash.
Expand Down Expand Up @@ -625,7 +655,7 @@ func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *StateOve
return nil, err
}

transaction, err := DecodeTxn(arg, header.Number, e.store, true)
transaction, err := DecodeTxn(arg, e.store, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -680,7 +710,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error
}

// testTransaction should execute tx with nonce always set to the current expected nonce for the account
transaction, err := DecodeTxn(arg, header.Number, e.store, true)
transaction, err := DecodeTxn(arg, e.store, true)
if err != nil {
return nil, err
}
Expand Down
42 changes: 21 additions & 21 deletions jsonrpc/eth_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ func TestEth_DecodeTxn(t *testing.T) {
{
name: "should be successful",
arg: &txnArgs{
From: &addr1,
To: &addr2,
Gas: toArgUint64Ptr(21000),
GasTipCap: toArgBytesPtr(big.NewInt(10000).Bytes()),
GasFeeCap: toArgBytesPtr(big.NewInt(10000).Bytes()),
Value: toArgBytesPtr(oneEther.Bytes()),
Data: nil,
Nonce: toArgUint64Ptr(0),
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
From: &addr1,
To: &addr2,
Gas: toArgUint64Ptr(21000),
MaxPriorityFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()),
MaxFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()),
Value: toArgBytesPtr(oneEther.Bytes()),
Data: nil,
Nonce: toArgUint64Ptr(0),
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
},
res: types.NewTx(types.NewDynamicFeeTx(
types.WithGasTipCap(big.NewInt(10000)),
Expand Down Expand Up @@ -161,7 +161,7 @@ func TestEth_DecodeTxn(t *testing.T) {
store.SetAccount(addr, acc)
}

res, err := DecodeTxn(tt.arg, 1, store, false)
res, err := DecodeTxn(tt.arg, store, false)
assert.Equal(t, tt.res, res)
assert.Equal(t, tt.err, err)
})
Expand Down Expand Up @@ -257,16 +257,16 @@ func TestEth_TxnType(t *testing.T) {

// Setup Txn
args := &txnArgs{
From: &addr1,
To: &addr2,
Gas: toArgUint64Ptr(21000),
GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()),
GasTipCap: toArgBytesPtr(big.NewInt(10000).Bytes()),
GasFeeCap: toArgBytesPtr(big.NewInt(10000).Bytes()),
Value: toArgBytesPtr(oneEther.Bytes()),
Data: nil,
Nonce: toArgUint64Ptr(0),
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
From: &addr1,
To: &addr2,
Gas: toArgUint64Ptr(21000),
GasPrice: toArgBytesPtr(big.NewInt(10000).Bytes()),
MaxPriorityFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()),
MaxFeePerGas: toArgBytesPtr(big.NewInt(10000).Bytes()),
Value: toArgBytesPtr(oneEther.Bytes()),
Data: nil,
Nonce: toArgUint64Ptr(0),
Type: toArgUint64Ptr(uint64(types.DynamicFeeTxType)),
}

expectedRes := types.NewTx(types.NewDynamicFeeTx(
Expand All @@ -279,7 +279,7 @@ func TestEth_TxnType(t *testing.T) {
types.WithInput([]byte{}),
types.WithNonce(0),
))
res, err := DecodeTxn(args, 1, store, false)
res, err := DecodeTxn(args, store, false)

expectedRes.ComputeHash()
assert.NoError(t, err)
Expand Down
33 changes: 26 additions & 7 deletions jsonrpc/eth_txpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"math/big"
"testing"

"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/crypto"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -31,17 +33,30 @@ func TestEth_TxnPool_SendRawTransaction(t *testing.T) {
func TestEth_TxnPool_SendTransaction(t *testing.T) {
store := &mockStoreTxn{}
store.AddAccount(addr0)

eth := newTestEthEndpoint(store)
txToSend := types.NewTx(types.NewLegacyTx(
types.WithGasPrice(big.NewInt(1)),
types.WithFrom(addr0),
types.WithTo(argAddrPtr(addr0)),
types.WithNonce(0),
))
txToSend := &txnArgs{
From: &addr0,
To: &addr1,
Gas: argUintPtr(100000),
GasPrice: argBytesPtr([]byte{0x64}),
Value: argBytesPtr([]byte{0x64}),
Data: nil,
Nonce: argUintPtr(0),
}

contractKey, err := crypto.GenerateECDSAKey()
assert.NoError(t, err)

eth.ecdsaKey = contractKey

defaultChainID := uint64(100)
eth.txSigner = crypto.NewSigner(chain.AllForksEnabled.At(0), defaultChainID)

_, err := eth.SendRawTransaction(txToSend.MarshalRLP())
hash, err := eth.SendTransaction(txToSend)
assert.NoError(t, err)
assert.NotEqual(t, store.txn.Hash(), types.ZeroHash)
assert.NotNil(t, hash)
}

type mockStoreTxn struct {
Expand All @@ -62,6 +77,10 @@ func (m *mockStoreTxn) GetNonce(addr types.Address) uint64 {
return 1
}

func (m *mockStoreTxn) GetForksInTime(blockNumber uint64) chain.ForksInTime {
return chain.AllForksEnabled.At(0)
}

func (m *mockStoreTxn) AddAccount(addr types.Address) *mockAccount {
if m.accounts == nil {
m.accounts = map[types.Address]*mockAccount{}
Expand Down
18 changes: 11 additions & 7 deletions jsonrpc/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func GetNextNonce(address types.Address, number BlockNumber, store nonceGetter)
return acc.Nonce, nil
}

func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonce bool) (*types.Transaction, error) {
func DecodeTxn(arg *txnArgs, store nonceGetter, forceSetNonce bool) (*types.Transaction, error) {
if arg == nil {
return nil, errors.New("missing value for required argument 0")
}
Expand All @@ -207,12 +207,12 @@ func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonc
arg.GasPrice = argBytesPtr([]byte{})
}

if arg.GasTipCap == nil {
arg.GasTipCap = argBytesPtr([]byte{})
if arg.MaxPriorityFeePerGas == nil {
arg.MaxPriorityFeePerGas = argBytesPtr([]byte{})
}

if arg.GasFeeCap == nil {
arg.GasFeeCap = argBytesPtr([]byte{})
if arg.MaxFeePerGas == nil {
arg.MaxFeePerGas = argBytesPtr([]byte{})
}

var input []byte
Expand All @@ -234,6 +234,10 @@ func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonc
arg.Gas = argUintPtr(0)
}

if arg.ChainID == nil {
arg.ChainID = argBytesPtr([]byte{})
}

txType := types.LegacyTxType
if arg.Type != nil {
txType = types.TxType(*arg.Type)
Expand All @@ -245,8 +249,8 @@ func DecodeTxn(arg *txnArgs, blockNumber uint64, store nonceGetter, forceSetNonc
case types.LegacyTxType:
txn.SetGasPrice(new(big.Int).SetBytes(*arg.GasPrice))
case types.DynamicFeeTxType:
txn.SetGasTipCap(new(big.Int).SetBytes(*arg.GasTipCap))
txn.SetGasFeeCap(new(big.Int).SetBytes(*arg.GasFeeCap))
txn.SetGasTipCap(new(big.Int).SetBytes(*arg.MaxPriorityFeePerGas))
txn.SetGasFeeCap(new(big.Int).SetBytes(*arg.MaxFeePerGas))
}

txn.SetFrom(*arg.From)
Expand Down
Loading

0 comments on commit c7d258b

Please sign in to comment.