From a42bb6add87cfc741e3af4f2b852e50700585bdd Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Thu, 25 Jan 2024 19:32:21 +0200 Subject: [PATCH] Implement eth_getBlockByNumber RPC endpoint Also adds a decoder for evm.BlockExecutedEvent payloads and storing these to the in-memory storage. --- api/api.go | 18 +++- api/api_test.go | 99 ++++++++++++++++-- api/fixtures/eth_json_rpc_requests.json | 1 - api/fixtures/eth_json_rpc_responses.json | 1 - api/server_test.go | 28 ++++++ cmd/server/main.go | 7 +- storage/store.go | 122 ++++++++++++++++++++--- 7 files changed, 245 insertions(+), 31 deletions(-) diff --git a/api/api.go b/api/api.go index beed8eb7..a0d31b12 100644 --- a/api/api.go +++ b/api/api.go @@ -374,26 +374,34 @@ func (s *BlockChainAPI) GetBlockByNumber( fullTx bool, ) (map[string]interface{}, error) { block := map[string]interface{}{} + + blockExecutedPayload, err := s.Store.GetBlockByNumber(ctx, uint64(blockNumber)) + if err != nil { + return block, err + } + + block["number"] = hexutil.Uint64(blockExecutedPayload.Height) + block["hash"] = blockExecutedPayload.Hash + block["parentHash"] = blockExecutedPayload.ParentBlockHash + block["receiptsRoot"] = blockExecutedPayload.ReceiptRoot + block["transactions"] = blockExecutedPayload.TransactionHashes + block["difficulty"] = "0x4ea3f27bc" block["extraData"] = "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32" block["gasLimit"] = "0x1388" block["gasUsed"] = "0x0" - block["hash"] = "0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae" block["logsBloom"] = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" block["miner"] = "0xbb7b8287f3f0a933474a79eae42cbca977791171" block["mixHash"] = "0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843" block["nonce"] = "0x689056015818adbe" - block["number"] = "0x1b4" - block["parentHash"] = "0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54" - block["receiptsRoot"] = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" block["sha3Uncles"] = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" block["size"] = "0x220" block["stateRoot"] = "0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d" block["timestamp"] = "0x55ba467c" block["totalDifficulty"] = "0x78ed983323d" - block["transactions"] = []string{} block["transactionsRoot"] = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" block["uncles"] = []string{} + return block, nil } diff --git a/api/api_test.go b/api/api_test.go index 1773637c..56efab5a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -46,8 +46,37 @@ func TestBlockChainAPI(t *testing.T) { t.Run("BlockNumber", func(t *testing.T) { blockNumber := blockchainAPI.BlockNumber() - assert.Equal(t, hexutil.Uint64(0), blockNumber) + + event := blockExecutedEvent( + 1, + "0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9", + 7766279631452241920, + "0xe81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + []string{"0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9"}, + ) + store := blockchainAPI.Store + err := store.StoreBlock(context.Background(), event) + require.NoError(t, err) + + blockNumber = blockchainAPI.BlockNumber() + assert.Equal(t, hexutil.Uint64(1), blockNumber) + + event = blockExecutedEvent( + 2, + "0xaae4530246e61ae58479824ab0863f99ca50414d27aec0c269ae6a7cfc4c7f5b", + 7766279631452241920, + "0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9", + "0x0000000000000000000000000000000000000000000000000000000000000000", + []string{"0xaae4530246e61ae58479824ab0863f99ca50414d27aec0c269ae6a7cfc4c7f5b"}, + ) + + err = store.StoreBlock(context.Background(), event) + require.NoError(t, err) + + blockNumber = blockchainAPI.BlockNumber() + assert.Equal(t, hexutil.Uint64(2), blockNumber) }) t.Run("Syncing", func(t *testing.T) { @@ -370,32 +399,45 @@ func TestBlockChainAPI(t *testing.T) { }) t.Run("GetBlockByNumber", func(t *testing.T) { + event := blockExecutedEvent( + 1, + "0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9", + 7766279631452241920, + "0xe81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + []string{"0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9"}, + ) + store := blockchainAPI.Store + err := store.StoreBlock(context.Background(), event) + require.NoError(t, err) + block, err := blockchainAPI.GetBlockByNumber( context.Background(), - rpc.PendingBlockNumber, + rpc.BlockNumber(1), false, ) require.NoError(t, err) expectedBlock := map[string]interface{}{} + expectedBlock["number"] = hexutil.Uint64(1) + expectedBlock["hash"] = "0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9" + expectedBlock["parentHash"] = "0xe81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0" + expectedBlock["receiptsRoot"] = "0x0000000000000000000000000000000000000000000000000000000000000000" + expectedBlock["transactions"] = []string{"0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9"} + expectedBlock["difficulty"] = "0x4ea3f27bc" expectedBlock["extraData"] = "0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32" expectedBlock["gasLimit"] = "0x1388" expectedBlock["gasUsed"] = "0x0" - expectedBlock["hash"] = "0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae" expectedBlock["logsBloom"] = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" expectedBlock["miner"] = "0xbb7b8287f3f0a933474a79eae42cbca977791171" expectedBlock["mixHash"] = "0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843" expectedBlock["nonce"] = "0x689056015818adbe" - expectedBlock["number"] = "0x1b4" - expectedBlock["parentHash"] = "0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54" - expectedBlock["receiptsRoot"] = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" expectedBlock["sha3Uncles"] = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" expectedBlock["size"] = "0x220" expectedBlock["stateRoot"] = "0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d" expectedBlock["timestamp"] = "0x55ba467c" expectedBlock["totalDifficulty"] = "0x78ed983323d" - expectedBlock["transactions"] = []string{} expectedBlock["transactionsRoot"] = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" expectedBlock["uncles"] = []string{} @@ -802,3 +844,46 @@ func transactionExecutedEvent( }, } } + +func blockExecutedEvent( + blockHeight uint64, + blockHash string, + totalSupply uint64, + parentBlockHash string, + receiptRoot string, + transactionHashes []string, +) cadence.Event { + hashes := make([]cadence.Value, len(transactionHashes)) + for i, hash := range transactionHashes { + hashes[i] = cadence.String(hash) + } + + return cadence.Event{ + EventType: cadence.NewEventType( + stdlib.FlowLocation{}, + "evm.BlockExecuted", + []cadence.Field{ + cadence.NewField("blockHeight", cadence.UInt64Type{}), + cadence.NewField("blockHash", cadence.StringType{}), + cadence.NewField("totalSupply", cadence.UInt64Type{}), + cadence.NewField("parentBlockHash", cadence.StringType{}), + cadence.NewField("receiptRoot", cadence.StringType{}), + cadence.NewField( + "transactionHashes", + cadence.NewVariableSizedArrayType(cadence.StringType{}), + ), + }, + nil, + ), + Fields: []cadence.Value{ + cadence.NewUInt64(blockHeight), + cadence.String(blockHash), + cadence.NewUInt64(totalSupply), + cadence.String(parentBlockHash), + cadence.String(receiptRoot), + cadence.NewArray(hashes).WithType( + cadence.NewVariableSizedArrayType(cadence.StringType{}), + ), + }, + } +} diff --git a/api/fixtures/eth_json_rpc_requests.json b/api/fixtures/eth_json_rpc_requests.json index 2166e21c..070c48d7 100644 --- a/api/fixtures/eth_json_rpc_requests.json +++ b/api/fixtures/eth_json_rpc_requests.json @@ -13,7 +13,6 @@ {"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]} {"jsonrpc":"2.0","id":1,"method":"eth_getBlockTransactionCountByHash","params":["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"]} {"jsonrpc":"2.0","id":1,"method":"eth_getBlockTransactionCountByNumber","params":["0xe8"]} {"jsonrpc":"2.0","id":1,"method":"eth_getUncleCountByBlockHash","params":["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"]} diff --git a/api/fixtures/eth_json_rpc_responses.json b/api/fixtures/eth_json_rpc_responses.json index 5543f85a..57315767 100644 --- a/api/fixtures/eth_json_rpc_responses.json +++ b/api/fixtures/eth_json_rpc_responses.json @@ -13,7 +13,6 @@ {"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":[]}} {"jsonrpc":"2.0","id":1,"result":"0x188aa"} {"jsonrpc":"2.0","id":1,"result":"0x20a"} {"jsonrpc":"2.0","id":1,"result":"0x0"} diff --git a/api/server_test.go b/api/server_test.go index 49ab6b01..4304baa6 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,32 @@ func TestServerJSONRPCOveHTTPHandler(t *testing.T) { assert.Equal(t, expectedResponse, strings.TrimSuffix(string(content), "\n")) } + + t.Run("eth_getBlockByNumber", func(t *testing.T) { + request := `{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x1",false]}` + expectedResponse := `{"jsonrpc":"2.0","id":1,"result":{"difficulty":"0x4ea3f27bc","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","gasLimit":"0x1388","gasUsed":"0x0","hash":"0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","mixHash":"0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843","nonce":"0x689056015818adbe","number":"0x1","parentHash":"0xe81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","stateRoot":"0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d","timestamp":"0x55ba467c","totalDifficulty":"0x78ed983323d","transactions":["0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9"],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}` + + event := blockExecutedEvent( + 1, + "0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9", + 7766279631452241920, + "0xe81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + []string{"0xf31ee13dad8f38431fd31278b12be62e6b77e6923f0b7a446eb1affb61f21fc9"}, + ) + err := store.StoreBlock(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 d26a2581..7227d9a6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -133,15 +133,16 @@ func runIndexer(ctx context.Context, store *storage.Store, logger zerolog.Logger } logger.Info().Msgf("block %d %s:", response.Height, response.BlockID) - if len(response.Events) > 0 { - store.StoreBlockHeight(ctx, response.Height) - } + for _, event := range response.Events { logger.Info().Msgf(" %s", event.Value) if event.Type == "flow.evm.TransactionExecuted" { store.UpdateAccountNonce(ctx, event.Value) store.StoreLog(ctx, event.Value) } + if event.Type == "flow.evm.BlockExecuted" { + store.StoreBlock(ctx, event.Value) + } } lastHeight = response.Height diff --git a/storage/store.go b/storage/store.go index e212dd26..38c949e4 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,88 @@ import ( "github.com/onflow/cadence" ) +type BlockExecutedPayload struct { + Height uint64 + Hash string + TotalSupply uint64 + ParentBlockHash string + ReceiptRoot string + TransactionHashes []string +} + +func NewBlockExecutedPayload( + blockExecutedEvent cadence.Event, +) (*BlockExecutedPayload, error) { + blockExecutedPayload := &BlockExecutedPayload{} + + heightFieldValue := blockExecutedEvent.GetFieldValues()[0] + heightCadenceValue, ok := heightFieldValue.(cadence.UInt64) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + height := heightCadenceValue.ToGoValue().(uint64) + blockExecutedPayload.Height = height + + hashFieldValue := blockExecutedEvent.GetFieldValues()[1] + hashCadenceValue, ok := hashFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + hash := hashCadenceValue.ToGoValue().(string) + blockExecutedPayload.Hash = hash + + totalSupplyFieldValue := blockExecutedEvent.GetFieldValues()[2] + totalSupplyCadenceValue, ok := totalSupplyFieldValue.(cadence.UInt64) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + totalSupply := totalSupplyCadenceValue.ToGoValue().(uint64) + blockExecutedPayload.TotalSupply = totalSupply + + parentBlockHashFieldValue := blockExecutedEvent.GetFieldValues()[3] + parentBlockHashCadenceValue, ok := parentBlockHashFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + parentBlockHash := parentBlockHashCadenceValue.ToGoValue().(string) + blockExecutedPayload.ParentBlockHash = parentBlockHash + + receiptRootFieldValue := blockExecutedEvent.GetFieldValues()[4] + receiptRootCadenceValue, ok := receiptRootFieldValue.(cadence.String) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + receiptRoot := receiptRootCadenceValue.ToGoValue().(string) + blockExecutedPayload.ReceiptRoot = receiptRoot + + transactionHashesFieldValue := blockExecutedEvent.GetFieldValues()[5] + transactionHashesCadenceValue, ok := transactionHashesFieldValue.(cadence.Array) + if !ok { + return nil, fmt.Errorf("unable to decode Cadence event") + } + + transactionHashes := make([]string, 0) + for _, cadenceValue := range transactionHashesCadenceValue.Values { + transactionHash := cadenceValue.ToGoValue().(string) + transactionHashes = append(transactionHashes, transactionHash) + + } + blockExecutedPayload.TransactionHashes = transactionHashes + + return blockExecutedPayload, nil +} + type Store struct { - mu sync.RWMutex - logsByTopic map[string][]*types.Log - latestHeight uint64 - accountNonce map[common.Address]uint64 + mu sync.RWMutex + logsByTopic map[string][]*types.Log + latestHeight uint64 + accountNonce map[common.Address]uint64 + blocksByNumber map[uint64]*BlockExecutedPayload } // NewStore returns a new in-memory Store implementation. @@ -26,8 +104,9 @@ type Store struct { // `latestHeight` in `NewStore`. func NewStore() *Store { return &Store{ - accountNonce: make(map[common.Address]uint64), - logsByTopic: make(map[string][]*types.Log), + accountNonce: make(map[common.Address]uint64), + logsByTopic: make(map[string][]*types.Log), + blocksByNumber: make(map[uint64]*BlockExecutedPayload), } } @@ -39,8 +118,8 @@ func (s *Store) LatestBlockHeight(ctx context.Context) (uint64, error) { } func (s *Store) GetAccountNonce(ctx context.Context, address common.Address) uint64 { - s.mu.Lock() - defer s.mu.Unlock() + s.mu.RLock() + defer s.mu.RUnlock() return s.accountNonce[address] } @@ -124,17 +203,32 @@ func (s *Store) LogsByTopic(topic string) []*types.Log { return s.logsByTopic[topic] } -func (s *Store) StoreBlockHeight(ctx context.Context, blockHeight uint64) error { +func (s *Store) StoreBlock(ctx context.Context, blockPayload cadence.Event) error { s.mu.Lock() defer s.mu.Unlock() - return s.storeBlockHeight(blockHeight) + blockExecutedPayload, err := NewBlockExecutedPayload(blockPayload) + if err != nil { + return err + } + + s.blocksByNumber[blockExecutedPayload.Height] = blockExecutedPayload + s.latestHeight = blockExecutedPayload.Height + + return nil } -func (s *Store) storeBlockHeight(blockHeight uint64) error { - if blockHeight > s.latestHeight { - s.latestHeight = blockHeight +func (s *Store) GetBlockByNumber( + ctx context.Context, + blockNumber uint64, +) (*BlockExecutedPayload, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + blockExecutedPayload, ok := s.blocksByNumber[blockNumber] + if !ok { + return nil, fmt.Errorf("unable to find block for number: %d", blockNumber) } - return nil + return blockExecutedPayload, nil }