Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the eth_sendRawTransaction JSON-RPC endpoint #26

Merged
merged 2 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ import (
"encoding/hex"
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/onflow/cadence"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/access"

sdkCrypto "github.com/onflow/flow-go-sdk/crypto"
)

const EthNamespace = "eth"
Expand All @@ -33,6 +36,9 @@ var (
//go:embed cadence/scripts/bridged_account_call.cdc
var bridgedAccountCall []byte

//go:embed cadence/transactions/evm_run.cdc
var evmRunTx []byte

func SupportedAPIs(blockChainAPI *BlockChainAPI) []rpc.API {
return []rpc.API{
{
Expand Down Expand Up @@ -101,7 +107,69 @@ func (api *BlockChainAPI) SendRawTransaction(
ctx context.Context,
input hexutil.Bytes,
) (common.Hash, error) {
return crypto.Keccak256Hash([]byte("hello world")), nil
gethTx := &types.Transaction{}
encodedLen := uint(len(input))
err := gethTx.DecodeRLP(
rlp.NewStream(
bytes.NewReader(input),
uint64(encodedLen),
),
)
if err != nil {
return common.Hash{}, err
}

block, err := api.FlowClient.GetLatestBlock(context.Background(), true)
if err != nil {
return common.Hash{}, err
}

privateKey, err := sdkCrypto.DecodePrivateKeyHex(sdkCrypto.ECDSA_P256, strings.Replace("2619878f0e2ff438d17835c2a4561cb87b4d24d72d12ec34569acd0dd4af7c21", "0x", "", 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you get this from ENV configuration

Copy link
Collaborator Author

@m-Peter m-Peter Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am planning to add a flow.json config file, to hold the account and private key that will be used for the gateway. Though I didn't get to it in this PR, to avoid cluttering things. Should we add it here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Just add a todo then maybe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing 👍 I also want to add the networks in there, where the indexer will connect to. And we'll see what other config makes sense to have in there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments in 4ad8061

if err != nil {
return common.Hash{}, err
}

account, err := api.FlowClient.GetAccount(context.Background(), flow.HexToAddress("0xf8d6e0586b0a20c7"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This as well

if err != nil {
return common.Hash{}, err
}
accountKey := account.Keys[0]
signer, err := sdkCrypto.NewInMemorySigner(privateKey, accountKey.HashAlgo)
if err != nil {
return common.Hash{}, err
}

tx := flow.NewTransaction().
SetScript(evmRunTx).
SetProposalKey(account.Address, accountKey.Index, accountKey.SequenceNumber).
SetReferenceBlockID(block.ID).
SetPayer(account.Address).
AddAuthorizer(account.Address)

decodedTx, err := hex.DecodeString(input.String()[2:])
if err != nil {
return common.Hash{}, err
}
cdcBytes := make([]cadence.Value, 0)
for _, bt := range decodedTx {
cdcBytes = append(cdcBytes, cadence.UInt8(bt))
}
encodedTx := cadence.NewArray(
cdcBytes,
).WithType(cadence.NewVariableSizedArrayType(cadence.TheUInt8Type))
tx.AddArgument(encodedTx)

err = tx.SignEnvelope(account.Address, accountKey.Index, signer)
if err != nil {
return common.Hash{}, err
}

err = api.FlowClient.SendTransaction(ctx, *tx)
if err != nil {
return common.Hash{}, err
}

return gethTx.Hash(), nil
}

// eth_createAccessList
Expand Down
40 changes: 38 additions & 2 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/ecdsa"
"encoding/hex"
"math/big"
"strings"
"testing"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -19,7 +20,9 @@ import (
"github.com/onflow/flow-evm-gateway/api"
"github.com/onflow/flow-evm-gateway/api/mocks"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/access/grpc"
sdkCrypto "github.com/onflow/flow-go-sdk/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -93,15 +96,48 @@ func TestBlockChainAPI(t *testing.T) {
})

t.Run("SendRawTransaction", func(t *testing.T) {
tx := "b88c02f88982029a01808083124f809499466ed2e37b892a2ee3e9cd55a98b68f5735db280a4c6888fa10000000000000000000000000000000000000000000000000000000000000006c001a0f84168f821b427dc158c4d8083bdc4b43e178cf0977a2c5eefbcbedcc4e351b0a066a747a38c6c266b9dc2136523cef04395918de37773db63d574aabde59c12eb"
txBytes, err := hex.DecodeString(tx)
require.NoError(t, err)

mockFlowClient := new(mocks.MockAccessClient)
blockchainAPI = api.NewBlockChainAPI(config, store, mockFlowClient)

block := &flow.Block{
BlockHeader: flow.BlockHeader{
ID: flow.EmptyID,
},
}
mockFlowClient.On("GetLatestBlock", mock.Anything, mock.Anything).Return(block, nil)

privateKey, err := sdkCrypto.DecodePrivateKeyHex(sdkCrypto.ECDSA_P256, strings.Replace("2619878f0e2ff438d17835c2a4561cb87b4d24d72d12ec34569acd0dd4af7c21", "0x", "", 1))
require.NoError(t, err)
key := &flow.AccountKey{
Index: 0,
PublicKey: privateKey.PublicKey(),
SigAlgo: privateKey.Algorithm(),
HashAlgo: sdkCrypto.SHA3_256,
Weight: 1000,
SequenceNumber: uint64(0),
Revoked: false,
}
account := &flow.Account{
Address: flow.HexToAddress("0xf8d6e0586b0a20c7"),
Keys: []*flow.AccountKey{key},
}
mockFlowClient.On("GetAccount", mock.Anything, mock.Anything).Return(account, nil)

mockFlowClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil)

hash, err := blockchainAPI.SendRawTransaction(
context.Background(),
hexutil.Bytes{},
hexutil.Bytes(txBytes),
)
require.NoError(t, err)

assert.Equal(
t,
common.HexToHash("0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"),
common.HexToHash("0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c"),
hash,
)
})
Expand Down
16 changes: 16 additions & 0 deletions api/cadence/transactions/evm_run.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// TODO(m-Peter): Use proper address for each network
import EVM from 0xf8d6e0586b0a20c7

transaction(encodedTx: [UInt8]) {
let bridgedAccount: &EVM.BridgedAccount

prepare(signer: auth(Storage) &Account) {
self.bridgedAccount = signer.storage.borrow<&EVM.BridgedAccount>(
from: /storage/evm
) ?? panic("Could not borrow reference to the bridged account!")
}

execute {
EVM.run(tx: encodedTx, coinbase: self.bridgedAccount.address())
}
}
1 change: 0 additions & 1 deletion api/fixtures/eth_json_rpc_requests.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params": []}
{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params": []}
{"jsonrpc":"2.0","id":1,"method":"eth_syncing","params": []}
{"jsonrpc":"2.0","id":1,"method":"eth_sendRawTransaction","params":["0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"]}
{"jsonrpc":"2.0","id":1,"method":"eth_gasPrice","params":[]}
{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0x407d73d8a49eeb85d32cf465507dd71d507100c1","latest"]}
{"jsonrpc":"2.0","id":1,"method":"eth_getCode","params":["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","0x2"]}
Expand Down
1 change: 0 additions & 1 deletion api/fixtures/eth_json_rpc_responses.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{"jsonrpc":"2.0","id":1,"result":"0x29a"}
{"jsonrpc":"2.0","id":1,"result":"0x0"}
{"jsonrpc":"2.0","id":1,"result":false}
{"jsonrpc":"2.0","id":1,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}
{"jsonrpc":"2.0","id":1,"result":"0x1dfd14000"}
{"jsonrpc":"2.0","id":1,"result":"0x65"}
{"jsonrpc":"2.0","id":1,"result":"0x600160008035811a818181146012578301005b601b6001356025565b8060005260206000f25b600060078202905091905056"}
Expand Down
45 changes: 45 additions & 0 deletions api/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
"github.com/onflow/flow-evm-gateway/api"
"github.com/onflow/flow-evm-gateway/api/mocks"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/access/grpc"
sdkCrypto "github.com/onflow/flow-go-sdk/crypto"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -147,6 +149,49 @@ func TestServerJSONRPCOveHTTPHandler(t *testing.T) {

assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n"))
})

t.Run("eth_sendRawTransaction", func(t *testing.T) {
request := `{"jsonrpc":"2.0","id":1,"method":"eth_sendRawTransaction","params":["0xb88c02f88982029a01808083124f809499466ed2e37b892a2ee3e9cd55a98b68f5735db280a4c6888fa10000000000000000000000000000000000000000000000000000000000000006c001a0f84168f821b427dc158c4d8083bdc4b43e178cf0977a2c5eefbcbedcc4e351b0a066a747a38c6c266b9dc2136523cef04395918de37773db63d574aabde59c12eb"]}`
expectedResponse := `{"jsonrpc":"2.0","id":1,"result":"0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c"}`

blockchainAPI = api.NewBlockChainAPI(config, store, mockFlowClient)

block := &flow.Block{
BlockHeader: flow.BlockHeader{
ID: flow.EmptyID,
},
}
mockFlowClient.On("GetLatestBlock", mock.Anything, mock.Anything).Return(block, nil)

privateKey, err := sdkCrypto.DecodePrivateKeyHex(sdkCrypto.ECDSA_P256, strings.Replace("2619878f0e2ff438d17835c2a4561cb87b4d24d72d12ec34569acd0dd4af7c21", "0x", "", 1))
require.NoError(t, err)
key := &flow.AccountKey{
Index: 0,
PublicKey: privateKey.PublicKey(),
SigAlgo: privateKey.Algorithm(),
HashAlgo: sdkCrypto.SHA3_256,
Weight: 1000,
SequenceNumber: uint64(0),
Revoked: false,
}
account := &flow.Account{
Address: flow.HexToAddress("0xf8d6e0586b0a20c7"),
Keys: []*flow.AccountKey{key},
}
mockFlowClient.On("GetAccount", mock.Anything, mock.Anything).Return(account, nil)

mockFlowClient.On("SendTransaction", mock.Anything, mock.Anything).Return(nil)

resp := rpcRequest(url, request, "origin", "test.com")
defer resp.Body.Close()

content, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}

assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n"))
})
}

func TestServerJSONRPCOveWebSocketHandler(t *testing.T) {
Expand Down
Loading