Skip to content

Commit

Permalink
Implement the eth_sendRawTransaction JSON-RPC endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
m-Peter committed Jan 31, 2024
1 parent cb7c919 commit 6f97c77
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 6 deletions.
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))
if err != nil {
return common.Hash{}, err
}

account, err := api.FlowClient.GetAccount(context.Background(), flow.HexToAddress("0xf8d6e0586b0a20c7"))
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

0 comments on commit 6f97c77

Please sign in to comment.