From 82bf21857cbf70e1cae71e260334f4b4675f16ef Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 26 Jan 2024 16:01:26 +0200 Subject: [PATCH] Implement the eth_getTransactionReceipt JSON-RPC endpoint --- api/api.go | 73 +++++++++--- api/api_test.go | 60 +++++++--- api/fixtures/eth_json_rpc_requests.json | 1 - api/fixtures/eth_json_rpc_responses.json | 1 - api/server_test.go | 32 ++++++ cmd/server/main.go | 1 + storage/store.go | 137 +++++++++++++++++++++++ 7 files changed, 274 insertions(+), 31 deletions(-) diff --git a/api/api.go b/api/api.go index beed8eb72..0d4a379cc 100644 --- a/api/api.go +++ b/api/api.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "context" "encoding/hex" "fmt" @@ -12,6 +13,7 @@ import ( "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/flow-evm-gateway/storage" ) @@ -303,22 +305,65 @@ func (s *BlockChainAPI) GetTransactionReceipt( // defined interfaces for the storage & indexer services, so that // we can proceed with some tests, and get rid of these mock data. receipt := map[string]interface{}{} - txIndex := uint64(64) - blockHash := common.HexToHash("0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2") - receipt["blockHash"] = blockHash - receipt["blockNumber"] = (*hexutil.Big)(big.NewInt(6139707)) - receipt["contractAddress"] = nil + + txReceipt, err := s.Store.GetTransactionByHash(ctx, hash) + if err != nil { + return receipt, err + } + + receipt["blockNumber"] = (*hexutil.Big)(big.NewInt(int64(txReceipt.BlockHeight))) + receipt["transactionHash"] = common.HexToHash(txReceipt.TxHash) + + if txReceipt.Failed { + receipt["status"] = hexutil.Uint64(0) + } else { + receipt["status"] = hexutil.Uint64(1) + } + + receipt["type"] = hexutil.Uint64(txReceipt.TxType) + receipt["gasUsed"] = hexutil.Uint64(txReceipt.GasConsumed) + receipt["contractAddress"] = common.HexToAddress(txReceipt.DeployedContractAddress) + + logs := []*types.Log{} + decodedLogs, err := hex.DecodeString(txReceipt.Logs) + if err != nil { + return receipt, err + } + err = rlp.Decode(bytes.NewReader(decodedLogs), &logs) + if err != nil { + return receipt, err + } + receipt["logs"] = logs + receipt["logsBloom"] = hexutil.Bytes(types.LogsBloom(logs)) + + decodedTx, err := hex.DecodeString(txReceipt.Transaction) + if err != nil { + return receipt, err + } + tx := &types.Transaction{} + encodedLen := uint(len(txReceipt.Transaction)) + err = tx.DecodeRLP( + rlp.NewStream( + bytes.NewReader(decodedTx), + uint64(encodedLen), + ), + ) + if err != nil { + return receipt, err + } + from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx) + if err != nil { + return receipt, err + } + receipt["from"] = from + receipt["to"] = tx.To() + + txIndex := uint64(0) + receipt["transactionIndex"] = (*hexutil.Uint64)(&txIndex) + receipt["blockHash"] = common.HexToHash("0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2") receipt["cumulativeGasUsed"] = hexutil.Uint64(50000) receipt["effectiveGasPrice"] = (*hexutil.Big)(big.NewInt(20000000000)) - receipt["from"] = common.HexToAddress("0xa7d9ddbe1f17865597fbd27ec712455208b6b76d") - receipt["gasUsed"] = hexutil.Uint64(40000) - receipt["logs"] = []*types.Log{} - receipt["logsBloom"] = "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b" - receipt["status"] = hexutil.Uint64(1) - receipt["to"] = common.HexToAddress("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb") - receipt["transactionHash"] = common.HexToHash("0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b") - receipt["transactionIndex"] = (*hexutil.Uint64)(&txIndex) - receipt["type"] = hexutil.Uint64(2) + return receipt, nil } diff --git a/api/api_test.go b/api/api_test.go index 1773637cb..0acc0a901 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -298,29 +298,59 @@ func TestBlockChainAPI(t *testing.T) { }) t.Run("GetTransactionReceipt", func(t *testing.T) { + event := transactionExecutedEvent( + 3, + "0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c", + "b88c02f88982029a01808083124f809499466ed2e37b892a2ee3e9cd55a98b68f5735db280a4c6888fa10000000000000000000000000000000000000000000000000000000000000006c001a0f84168f821b427dc158c4d8083bdc4b43e178cf0977a2c5eefbcbedcc4e351b0a066a747a38c6c266b9dc2136523cef04395918de37773db63d574aabde59c12eb", + false, + 2, + 22514, + "0000000000000000000000000000000000000000", + "000000000000000000000000000000000000000000000000000000000000002a", + "f85af8589499466ed2e37b892a2ee3e9cd55a98b68f5735db2e1a024abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503daa0000000000000000000000000000000000000000000000000000000000000002a", + ) + + store := blockchainAPI.Store + store.StoreTransaction(context.Background(), event) + receipt, err := blockchainAPI.GetTransactionReceipt( context.Background(), - common.Hash{0, 1, 2}, + common.HexToHash("0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c"), ) require.NoError(t, err) expectedReceipt := map[string]interface{}{} - txIndex := uint64(64) - blockHash := common.HexToHash("0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2") - expectedReceipt["blockHash"] = blockHash - expectedReceipt["blockNumber"] = (*hexutil.Big)(big.NewInt(6139707)) - expectedReceipt["contractAddress"] = nil - expectedReceipt["cumulativeGasUsed"] = hexutil.Uint64(50000) - expectedReceipt["effectiveGasPrice"] = (*hexutil.Big)(big.NewInt(20000000000)) - expectedReceipt["from"] = common.HexToAddress("0xa7d9ddbe1f17865597fbd27ec712455208b6b76d") - expectedReceipt["gasUsed"] = hexutil.Uint64(40000) - expectedReceipt["logs"] = []*types.Log{} - expectedReceipt["logsBloom"] = "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b" + expectedReceipt["blockNumber"] = (*hexutil.Big)(big.NewInt(3)) + expectedReceipt["transactionHash"] = common.HexToHash("0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c") expectedReceipt["status"] = hexutil.Uint64(1) - expectedReceipt["to"] = common.HexToAddress("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb") - expectedReceipt["transactionHash"] = common.HexToHash("0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b") - expectedReceipt["transactionIndex"] = (*hexutil.Uint64)(&txIndex) expectedReceipt["type"] = hexutil.Uint64(2) + expectedReceipt["gasUsed"] = hexutil.Uint64(22514) + expectedReceipt["contractAddress"] = common.HexToAddress("0x0000000000000000000000000000000000000000") + expectedReceipt["from"] = common.HexToAddress("0x658Bdf435d810C91414eC09147DAA6DB62406379") + to := common.HexToAddress("0x99466ED2E37B892A2Ee3E9CD55a98b68f5735db2") + expectedReceipt["to"] = &to + + txIndex := uint64(0) + expectedReceipt["transactionIndex"] = (*hexutil.Uint64)(&txIndex) + expectedReceipt["blockHash"] = common.HexToHash("0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2") + expectedReceipt["cumulativeGasUsed"] = hexutil.Uint64(50000) + expectedReceipt["effectiveGasPrice"] = (*hexutil.Big)(big.NewInt(20000000000)) + + data, err := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000002a") + require.NoError(t, err) + log := &types.Log{ + Index: 0, + BlockNumber: 0, + BlockHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + TxHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + TxIndex: 0, + Address: common.HexToAddress("0x99466ed2e37b892a2ee3e9cd55a98b68f5735db2"), + Data: data, + Topics: []common.Hash{common.HexToHash("0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da")}, + } + logs := []*types.Log{log} + expectedReceipt["logs"] = logs + expectedReceipt["logsBloom"] = hexutil.Bytes(types.LogsBloom(logs)) assert.Equal(t, expectedReceipt, receipt) }) diff --git a/api/fixtures/eth_json_rpc_requests.json b/api/fixtures/eth_json_rpc_requests.json index 2166e21c8..6224448c1 100644 --- a/api/fixtures/eth_json_rpc_requests.json +++ b/api/fixtures/eth_json_rpc_requests.json @@ -10,7 +10,6 @@ {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionByHash","params":["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"]} {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionByBlockHashAndIndex","params":["0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b", "0x0"]} {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionByBlockNumberAndIndex","params":["0x29c", "0x0"]} -{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5"]} {"jsonrpc":"2.0","id":1,"method":"eth_coinbase"} {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByHash","params":["0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae",false]} {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x1b4",true]} diff --git a/api/fixtures/eth_json_rpc_responses.json b/api/fixtures/eth_json_rpc_responses.json index 5543f85ae..e8135723b 100644 --- a/api/fixtures/eth_json_rpc_responses.json +++ b/api/fixtures/eth_json_rpc_responses.json @@ -10,7 +10,6 @@ {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2","blockNumber":"0x5daf3b","from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d","gas":"0xc350","gasPrice":"0x4a817c800","hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","input":"0x3078363836353663366336663231","nonce":"0x15","to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb","transactionIndex":"0x40","value":"0xf3dbb76162000","type":"0x0","v":"0x25","r":"0x96","s":"0xfa"}} {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0xc6ef2fc5426d6ad6fd9e2a26abeab0aa2411b7ab17f30a99d3cb96aed1d1055b","blockNumber":"0x5daf3b","from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d","gas":"0xc350","gasPrice":"0x4a817c800","hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","input":"0x3078363836353663366336663231","nonce":"0x15","to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb","transactionIndex":"0x40","value":"0xf3dbb76162000","type":"0x0","v":"0x25","r":"0x96","s":"0xfa"}} {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2","blockNumber":"0x5daf3b","from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d","gas":"0xc350","gasPrice":"0x4a817c800","hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","input":"0x3078363836353663366336663231","nonce":"0x15","to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb","transactionIndex":"0x40","value":"0xf3dbb76162000","type":"0x0","v":"0x25","r":"0x96","s":"0xfa"}} -{"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2","blockNumber":"0x5daf3b","contractAddress":null,"cumulativeGasUsed":"0xc350","effectiveGasPrice":"0x4a817c800","from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d","gasUsed":"0x9c40","logs":[],"logsBloom":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","status":"0x1","to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb","transactionHash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","transactionIndex":"0x40","type":"0x2"}} {"jsonrpc":"2.0","id":1,"result":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb"} {"jsonrpc":"2.0","id":1,"result":{"difficulty":"0x4ea3f27bc","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","gasLimit":"0x1388","gasUsed":"0x0","hash":"0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","mixHash":"0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843","nonce":"0x689056015818adbe","number":"0x1b4","parentHash":"0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","stateRoot":"0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d","timestamp":"0x55ba467c","totalDifficulty":"0x78ed983323d","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}} {"jsonrpc":"2.0","id":1,"result":{"difficulty":"0x4ea3f27bc","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","gasLimit":"0x1388","gasUsed":"0x0","hash":"0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","mixHash":"0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843","nonce":"0x689056015818adbe","number":"0x1b4","parentHash":"0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","stateRoot":"0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d","timestamp":"0x55ba467c","totalDifficulty":"0x78ed983323d","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}} diff --git a/api/server_test.go b/api/server_test.go index 49ab6b01c..13fff1ad0 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -2,6 +2,7 @@ package api_test import ( "bytes" + "context" _ "embed" "io" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/onflow/flow-evm-gateway/storage" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) //go:embed fixtures/eth_json_rpc_requests.json @@ -53,6 +55,36 @@ func TestServerJSONRPCOveHTTPHandler(t *testing.T) { assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n")) } + + t.Run("eth_getTransactionReceipt", func(t *testing.T) { + request := `{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c"]}` + expectedResponse := `{"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2","blockNumber":"0x3","contractAddress":"0x0000000000000000000000000000000000000000","cumulativeGasUsed":"0xc350","effectiveGasPrice":"0x4a817c800","from":"0x658bdf435d810c91414ec09147daa6db62406379","gasUsed":"0x57f2","logs":[{"address":"0x99466ed2e37b892a2ee3e9cd55a98b68f5735db2","topics":["0x24abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503da"],"data":"0x000000000000000000000000000000000000000000000000000000000000002a","blockNumber":"0x0","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x0","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","logIndex":"0x0","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000020000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000","status":"0x1","to":"0x99466ed2e37b892a2ee3e9cd55a98b68f5735db2","transactionHash":"0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c","transactionIndex":"0x0","type":"0x2"}}` + + event := transactionExecutedEvent( + 3, + "0xb47d74ea64221eb941490bdc0c9a404dacd0a8573379a45c992ac60ee3e83c3c", + "b88c02f88982029a01808083124f809499466ed2e37b892a2ee3e9cd55a98b68f5735db280a4c6888fa10000000000000000000000000000000000000000000000000000000000000006c001a0f84168f821b427dc158c4d8083bdc4b43e178cf0977a2c5eefbcbedcc4e351b0a066a747a38c6c266b9dc2136523cef04395918de37773db63d574aabde59c12eb", + false, + 2, + 22514, + "0000000000000000000000000000000000000000", + "000000000000000000000000000000000000000000000000000000000000002a", + "f85af8589499466ed2e37b892a2ee3e9cd55a98b68f5735db2e1a024abdb5865df5079dcc5ac590ff6f01d5c16edbc5fab4e195d9febd1114503daa0000000000000000000000000000000000000000000000000000000000000002a", + ) + + err := store.StoreTransaction(context.Background(), event) + require.NoError(t, err) + + 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) { diff --git a/cmd/server/main.go b/cmd/server/main.go index d26a2581f..e203374d2 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -139,6 +139,7 @@ func runIndexer(ctx context.Context, store *storage.Store, logger zerolog.Logger for _, event := range response.Events { logger.Info().Msgf(" %s", event.Value) if event.Type == "flow.evm.TransactionExecuted" { + store.StoreTransaction(ctx, event.Value) store.UpdateAccountNonce(ctx, event.Value) store.StoreLog(ctx, event.Value) } diff --git a/storage/store.go b/storage/store.go index e212dd26d..8373daac3 100644 --- a/storage/store.go +++ b/storage/store.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/hex" + "fmt" "sync" "github.com/ethereum/go-ethereum/common" @@ -12,11 +13,113 @@ import ( "github.com/onflow/cadence" ) +type TransactionExecutedPayload struct { + BlockHeight uint64 + TxHash string + Transaction string + Failed bool + TxType uint8 + GasConsumed uint64 + DeployedContractAddress string + ReturnedValue string + Logs string +} + +func NewTransactionExecutedPayload( + transactionExecutedEvent cadence.Event, +) (*TransactionExecutedPayload, error) { + transactionExecutedPayload := &TransactionExecutedPayload{} + + blockHeightFieldValue := transactionExecutedEvent.GetFieldValues()[0] + blockHeightCadenceValue, ok := blockHeightFieldValue.(cadence.UInt64) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + blockHeight := blockHeightCadenceValue.ToGoValue().(uint64) + transactionExecutedPayload.BlockHeight = blockHeight + + txHashFieldValue := transactionExecutedEvent.GetFieldValues()[1] + txHashCadenceValue, ok := txHashFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + txHash := txHashCadenceValue.ToGoValue().(string) + transactionExecutedPayload.TxHash = txHash + + transactionFieldValue := transactionExecutedEvent.GetFieldValues()[2] + transactionCadenceValue, ok := transactionFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + transaction := transactionCadenceValue.ToGoValue().(string) + transactionExecutedPayload.Transaction = transaction + + failedFieldValue := transactionExecutedEvent.GetFieldValues()[3] + failedCadenceValue, ok := failedFieldValue.(cadence.Bool) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + failed := failedCadenceValue.ToGoValue().(bool) + transactionExecutedPayload.Failed = failed + + txTypeFieldValue := transactionExecutedEvent.GetFieldValues()[4] + txTypeCadenceValue, ok := txTypeFieldValue.(cadence.UInt8) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + txType := txTypeCadenceValue.ToGoValue().(uint8) + transactionExecutedPayload.TxType = txType + + gasConsumedFieldValue := transactionExecutedEvent.GetFieldValues()[5] + gasConsumedCadenceValue, ok := gasConsumedFieldValue.(cadence.UInt64) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + gasConsumed := gasConsumedCadenceValue.ToGoValue().(uint64) + transactionExecutedPayload.GasConsumed = gasConsumed + + deployedContractAddressFieldValue := transactionExecutedEvent.GetFieldValues()[6] + deployedContractAddressCadenceValue, ok := deployedContractAddressFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + deployedContractAddress := deployedContractAddressCadenceValue.ToGoValue().(string) + transactionExecutedPayload.DeployedContractAddress = deployedContractAddress + + returnedValueFieldValue := transactionExecutedEvent.GetFieldValues()[7] + returnedValueCadenceValue, ok := returnedValueFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + returnedValue := returnedValueCadenceValue.ToGoValue().(string) + transactionExecutedPayload.ReturnedValue = returnedValue + + logsFieldValue := transactionExecutedEvent.GetFieldValues()[8] + logsCadenceValue, ok := logsFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + logs := logsCadenceValue.ToGoValue().(string) + transactionExecutedPayload.Logs = logs + + return transactionExecutedPayload, nil +} + type Store struct { mu sync.RWMutex logsByTopic map[string][]*types.Log latestHeight uint64 accountNonce map[common.Address]uint64 + txByHash map[common.Hash]*TransactionExecutedPayload } // NewStore returns a new in-memory Store implementation. @@ -28,6 +131,7 @@ func NewStore() *Store { return &Store{ accountNonce: make(map[common.Address]uint64), logsByTopic: make(map[string][]*types.Log), + txByHash: make(map[common.Hash]*TransactionExecutedPayload), } } @@ -138,3 +242,36 @@ func (s *Store) storeBlockHeight(blockHeight uint64) error { return nil } + +func (s *Store) StoreTransaction( + ctx context.Context, + transactionPayload cadence.Event, +) error { + s.mu.Lock() + defer s.mu.Unlock() + + transactionExecutedPayload, err := NewTransactionExecutedPayload(transactionPayload) + if err != nil { + return err + } + + txHash := common.HexToHash(transactionExecutedPayload.TxHash) + s.txByHash[txHash] = transactionExecutedPayload + + return nil +} + +func (s *Store) GetTransactionByHash( + ctx context.Context, + txHash common.Hash, +) (*TransactionExecutedPayload, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + transactionExecutedPayload, ok := s.txByHash[txHash] + if !ok { + return nil, fmt.Errorf("unable to find transaction for hash: %s", txHash) + } + + return transactionExecutedPayload, nil +}