-
Notifications
You must be signed in to change notification settings - Fork 9
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
Add HTTP
and WebSocket
handlers for JSON-RPC
server
#11
Add HTTP
and WebSocket
handlers for JSON-RPC
server
#11
Conversation
914d550
to
7aa8509
Compare
7aa8509
to
4251feb
Compare
indexer/client.go
Outdated
@@ -0,0 +1,188 @@ | |||
package indexer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like this is copy/pasted from https://github.com/peterargue/execdata-client/blob/main/client/client.go
while I'm not personally offended by it, this is generally something to avoid with open source projects since it can invite licensing challenges.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, a more robust version of this logic will be included in the flow-go-sdk soon which is probably a better option for this project:
onflow/flow-go-sdk#417
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like this is copy/pasted from https://github.com/peterargue/execdata-client/blob/main/client/client.go
while I'm not personally offended by it, this is generally something to avoid with open source projects since it can invite licensing challenges.
My sincere apologies 🙏 I had no intention to shadow your work or even present it as mine. I have mentioned the source of this code in the flow-evm discord channel and on the regular syncs we have with the team working on Flow EVM. That's why I asked Jerome to add you as a reviewer 😇
My first approach was to add your repository as a module, but it simply wouldn't work, as this PR needs specific versions for go-ethereum
and flow-go
, e.g.:
github.com/ethereum/go-ethereum v1.12.0
github.com/onflow/flow-go v0.32.4-0.20231122162232-0ba81ff8d241
My main purpose was to use the code only for creating a PoC with the Event Streaming API, until proper support was added in flow-go-sdk
. As soon as onflow/flow-go-sdk#417 is included in a release, I will pull it in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good, and I'm not worried about it from a credit perspective. I wrote that library within my capacity at flow, so it's no problem.
I'm just calling it out because we need to be careful that code included in the onflow
org is following proper licensing practices, otherwise we could get into some legal issues. In this case, one option would be to fork the repo and make whatever changes were needed, or submitting a PR back to the original repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a fair point 👍
I started out that way (peterargue/execdata-client#3), I just got carried out after hours of troubleshooting 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@peterargue I have opened a PR on the upstream repository: peterargue/execdata-client#4. When it gets merged, I can remove all the relevant code from this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I merged that flow-go-sdk PR into master, so you can import it now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect 💯
I have completely replaced the indexer code with the functionality offered from flow-go-sdk
in 8545330.
log.Fatalf("could not subscribe to execution data: %v", err) | ||
} | ||
|
||
for { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should check in this loop that responses were received for all block heights, otherwise it's possible that a response was missed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would that check look like? 🤔
To share some more context, we want to keep track of the 2 following service events (a.k.a core events):
sub, err = execClient.SubscribeEvents(
ctx,
flow.ZeroID,
height,
indexer.EventFilter{
EventTypes: []string{
"flow.evm.BlockExecuted",
"flow.evm.TransactionExecuted",
},
},
)
and build some sort of state around the payloads of these 2 events.
These 2 events will start being emitted from a certain block height and onwards. That block height would most likely be the height in which the EVM
contract (and associated code) is deployed/introduced on testnet
& mainnet
. For this case we can have a utility script for back-filling.
My assumption here is that SubscribeEvents
will publish a response for every block that matches these EventFilter
criteria. Of course, not every block will contain such events. In the context of this Flow EVM Gateway, the latest block height is the block height which contained a flow.evm.BlockExecuted
, we are not interested in regular Flow blocks.
Could you elaborate on which case it is possible to miss a response which matched the EventFilter
criteria?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SubscribeEvents
endpoint has a heartbeatInterval
request parameter:
https://github.com/onflow/flow/blob/b7ed2c3e452b5e44561399afc6b5e868e8957117/protobuf/flow/executiondata/executiondata.proto#L140-L148
This is used to set how often the backend will return a response message. If you set it to 1, it will return a response for every block. If the block has no events, the list is empty. If you check the block height from each message increases sequentially, you can verify that you received all messages, and thus all blocks were searched.
The API is currently missing an important sequence number field, which would let you get the same guarantee with larger heartbeatIntervals
without receiving a response for every block.
Given missing any events may corrupt your index, I'd recommend using the 1 block heartbeat for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed explanation, I was not aware of the heartbeatInterval
request parameter.
I have added this parameter and the relevant check in 2b36693.
@@ -201,13 +278,30 @@ func (s *BlockChainAPI) GetTransactionReceipt( | |||
ctx context.Context, | |||
hash common.Hash, | |||
) (map[string]interface{}, error) { | |||
return map[string]interface{}{}, nil | |||
receipt := map[string]interface{}{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general point on this mocking of data. I would suggest you define an interface for the indexer (as discussed on our call), which you then implement one version as mock indexer that has these values. You should then use the indexer interface in the API so you don't get carried away with API containing any additional logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you intend to address this comment in this PR or in the future PR? If not in this PR add a TODO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR has already grown quite large, so I will tackle this in a future PR. Added a TODO: 6173b96
) | ||
}) | ||
|
||
t.Run("CreateAccessList", func(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mentioned above about having a indexer (or multiple modules) defined as interface and then used in the API, would also allow for better testing, as you would use a test mock (maybe using tools like mockery) with which you can then specify the values and assert the returned values. This way, like you made it now, it will soon break all the tests as you heavily rely on the mocked values in the API itself, which you will soon start replacing with real values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, this was mainly for my convenience, to see what kind of return values these methods require, and to possibly use these later on as fixtures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A suggestion for future PRs is to try to keep them more specific, I would break this one into 3, one to add mock data to the APi, the other to add indexer, and the third to add storage. This way it will be easier to review.
Makes sense, thanks for the suggestion 🙏 |
… response is received for each block on the indexer
indexer/client.go
Outdated
@@ -0,0 +1,188 @@ | |||
package indexer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I merged that flow-go-sdk PR into master, so you can import it now
"sync" | ||
) | ||
|
||
type Store struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this also store the data from events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly, that's the idea behind it. However, I have to try this out with the Emulator, because the following events:
flow.evm.BlockExecuted
,flow.evm.TransactionExecuted
are not currently available in neither testnet
nor mainnet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments, and I followed up on some from last time that weren't addressed, more specifically the interface suggestion.
Also, I wonder if we got to a conclusion on what to do about attribution/licensing of go-ethereum code? My advice is to include attributions as per their license, maybe in a comment on the top of the file, or just generally in the license/readme file.
@@ -0,0 +1,27 @@ | |||
name: CI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, in the future try to keep these kinds of changes in separate PRs so they are more focused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense 👍 Will do.
api/api.go
Outdated
|
||
// this is added to resolve the issue with chainhash ambiguous import, | ||
// the code is not used, but it's needed to force go.mod specify and retain chainhash version | ||
// workaround for issue: https://github.com/golang/go/issues/27899 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a TODO that once go-ethereum is updated this can go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch 👍
I removed it altogether in eb14c1e, since I am using the latest go-ethereum
version, and that's going to be used in flow-go
as well.
api/api.go
Outdated
return hexutil.Uint64(65848272) | ||
latestBlockHeight, err := api.Store.LatestBlockHeight(context.Background()) | ||
if err != nil { | ||
return hexutil.Uint64(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth panicking at this point or if nothing else logging an error, the way it's written now it might cause issues that will go unnoticed. If logging is not yet part of this PR maybe add TODO but also consider panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea 👍
I should definitely add a logger instance to BlockChainAPI
.
For now, I have added a TODO and a panic in df9553c
@@ -201,13 +278,30 @@ func (s *BlockChainAPI) GetTransactionReceipt( | |||
ctx context.Context, | |||
hash common.Hash, | |||
) (map[string]interface{}, error) { | |||
return map[string]interface{}{}, nil | |||
receipt := map[string]interface{}{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you intend to address this comment in this PR or in the future PR? If not in this PR add a TODO.
api/server_test.go
Outdated
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var requests = []string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be better to extract this to fixture files, you can create request.json
and then use embed to embed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice idea 👍
Fixed in e893a6f
cmd/server/main.go
Outdated
fmt.Printf("Latest Block Height: %d\n", latestBlockHeader.Height) | ||
fmt.Printf("Latest Block ID: %s\n", latestBlockHeader.ID) | ||
|
||
data, errChan, initErr := flowClient.SubscribeEventsByBlockHeight( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could just use the reconnect function so you don't duplicate the code here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea 👍
Updated in 328de2f
cmd/server/main.go
Outdated
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Printf("Latest Block Height: %d\n", latestBlockHeader.Height) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest you add a logging library and replace fmt outputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in f55dc7b
cmd/server/main.go
Outdated
if ctx.Err() != nil { | ||
return // graceful shutdown | ||
} | ||
fmt.Println("subscription closed - reconnecting") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the reconnect logic should have some timeout before reconnecting, otherwise a very vicious cycle can happen due to some issues in establishing connection. Also I would error log the connection failure so alarms can be set up in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea, I have added a simple time.Sleep
for now, and a TODO to add a proper library such as: https://github.com/sethvargo/go-retry .
Updated in 24cab8e
`{"jsonrpc":"2.0","id":4,"method":"eth_getBalance","params": ["0x407d73d8a49eeb85d32cf465507dd71d507100c1"]}`, | ||
`{"jsonrpc":"2.0","id":5,"method":"eth_getBlockTransactionCountByNumber","params": ["0x4E4ee"]}`, | ||
|
||
latestBlockHeader, err := flowClient.GetLatestBlockHeader(ctx, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a todo that the height from which the indexer starts should be retrieved from storage, not be equal to latest height
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍
Added in f55dc7b
We should probably have a command-line flag for this as well.
storage/store.go
Outdated
type Store struct { | ||
mu sync.RWMutex | ||
// highest block height | ||
blockHeight uint64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a comment suggests a better naming: latestHeight
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍
Updated in ed60231
… NewStore constructor
Also add a comment on the starting height to be used for the indexer
b7fa216
to
24cab8e
Compare
Added disclaimer regarding |
Closes: #1
Description
The above command, when run on the project's root directory, will start the RPC server, send some HTTP requests and print out the response.
It will also start the indexer, which uses the Event Streaming API, for these 2 events:
A.7e60df042a9c0868.FlowToken.TokensWithdrawn
A.7e60df042a9c0868.FlowToken.TokensDeposited
The main reason for using these two events is just to mock out the indexer's functionality.
For production, we will use the following two events:
flow.evm.BlockExecuted
flow.evm.TransactionExecuted
To see the interaction of the the
server
/indexer
/storage
in action, we can simply submit somecurl
requests:We can see that the
result
foreth_blockNumber
is changing, based on the block height from the events.For contributor use:
master
branchFiles changed
in the Github PR explorer