-
Notifications
You must be signed in to change notification settings - Fork 48
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
feat: stream SyncState response #685
base: next
Are you sure you want to change the base?
Conversation
crates/store/src/server/api.rs
Outdated
notes, | ||
nullifiers, | ||
})) | ||
let (tx, rx) = mpsc::channel(128); // TODO: check bound of the channel |
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.
This is still TBD, any ideas on what should be the channel size for the stream?
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 question. Each element is essentially at most one block's worth of delta's iiuc.
I would make it much smaller - like 5? @bobbinth
This also interacts with the timeout on the sender from my other comment.
346c5f3
to
14a8f1a
Compare
I have a question regarding a small edge case that I had to deal with in the sync state component refactor in the client. How do we deal with nullifiers for notes that we don't know that they exist that got created and nullified between the request block num and the chain tip? The client can't add the nullifier to the request because it doesn't know of the note's existance so will it miss the nullifier update. Moreover, now that the client is synced to a block after the nullifier, it won't get the update in future requests (which would be the requests containing the new nullifier). Here's a small diagram of the case: Before, with separate requests, the client could update subsquent requests with the new information it got from the node. |
To me this sounds like a case for more separate queries. As in, maybe |
I think this is probably the way to go, use For public notes the node could also track notes and add nullifiers, but for private notes it would require a separate request anyway. |
@igamigo @TomasArrachea can I review this PR as is? Or will there be changes towards separating nullifiers etc out? The latter probably needs at least some broader discussion, e.g. maybe we should have more focussed streams? |
For private notes, state sync will not give us any new info about nullifiers. That is, the only info we get from state sync about private notes is whether a note appeared on chain or not. So, if we didn't know about the nullifier of a private note before we started the state sync process, we won't learn about it during the sync. For public notes, the situation is different as we may get a public note we are interested in during the sync, and so would like to figure out if has already been consumed or not. So, the process could be:
An alternative, as suggested above, could be to remove nullifiers from the state sync requests entirely and just use |
One thing we can do to reduce the amount of data returned from the endpoint is to include |
So, I'd probably break this down as follows:
|
* feat: add block_num parameter to check_nullifiers_by_prefix * chore: update CHANGELOG * review: add `block_num` parameter as required * review: update CHANGELOG * review: update doc comment * chore: update rpc README
crates/rpc/README.md
Outdated
@@ -37,7 +37,7 @@ Returns a nullifier proof for each of the requested nullifiers. | |||
|
|||
### CheckNullifiersByPrefix | |||
|
|||
Returns a list of nullifiers that match the specified prefixes and are recorded in the node. | |||
Returns a list of nullifiers recorded in the node that match the specified prefixes and block creation 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.
It doesn't need to match block creation height, right? If so we should change this to reflect the response will contain nullifiers created at any block larger or equal than the passed one.
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, agreed - let's make it more precise - i.e., the response will contain nullifiers created in or after the specified block.
Actually #707 was merged into this branch, not into |
* feat: add block_num parameter to check_nullifiers_by_prefix * chore: update CHANGELOG * review: add `block_num` parameter as required * review: update CHANGELOG * review: update doc comment * feat: remove nullifiers from `SyncState` endpoint * chore: update doc comments * chore: update CHANGELOG * chore: update rpc README
crates/store/src/server/api.rs
Outdated
transactions, | ||
notes, | ||
})) | ||
let (tx, rx) = mpsc::channel(128); // TODO: check bound of the channel |
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 know these are the common names for channel halves, but consider different naming because we have many transactions within miden.
Maybe sender, receiver
or tx_stream
.
crates/store/src/server/api.rs
Outdated
} | ||
|
||
let result = async { | ||
let account_ids: Vec<AccountId> = read_account_ids(&request.account_ids)?; |
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.
This should move out of the loop and spawn I think - this only has to happen once correct? And if it errors then its a bad sync request and we should reject it immedietely.
crates/store/src/server/api.rs
Outdated
let chain_tip = state.latest_block_num().await.as_u32(); | ||
tokio::spawn(async move { | ||
loop { | ||
if last_block_num == chain_tip { | ||
// The state is up to date, no need to sync | ||
break; | ||
} |
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'm wondering what we want this logic to look like. Right now this locks the chain_tip
it place at the start of the stream.
But of course the chain will progress while we are syncing, and ideally we would sync until we hit the actual tip of the chain.
A more robust way would be to query until the store returns an empty result.
crates/store/src/server/api.rs
Outdated
let (state, delta) = state | ||
.sync_state(last_block_num.into(), account_ids, request.note_tags.clone()) | ||
.await | ||
.map_err(internal_error)?; |
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 would do this at the send location - that way the reader knows all errors are internal errors at the send location.
crates/store/src/server/api.rs
Outdated
} | ||
let is_error = result.is_err(); | ||
|
||
tx.send(result).await.expect("error sending sync state response to the client"); |
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.
This can happen if the client closes its connection. In other words, this can be caused by an external force - and shouldn't cause a panic for us. Rather if let Err(err)
, log the closure (probably at info
) and break the loop.
aka this isn't actually an error, but rather just indicates the connection was lost which is entirely valid. I think.
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.
This also block's correct? We should wrap it in a timeout to prevent a client from slow-rolling us.
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 only blocks when there is no more space on the channel, so it's related to the bound size discussion. Anyway I added a 10 secs timeout in c3b242b, along with some error handling
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.
Its also a DoS vector because without a timeout we just infinitely block. So a bad client could just open a million of these and do nothing, blocking us.
crates/store/src/server/api.rs
Outdated
notes, | ||
nullifiers, | ||
})) | ||
let (tx, rx) = mpsc::channel(128); // TODO: check bound of the channel |
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 question. Each element is essentially at most one block's worth of delta's iiuc.
I would make it much smaller - like 5? @bobbinth
This also interacts with the timeout on the sender from my other comment.
closes #174.
This PR changes the
SyncState
endpoint to return a Stream of responses, instead of returning a single sync update. The node will push all the update responses into the stream. Once the last update in sent, the stream is closed.The same
SyncStateResponse
is used, except thechain_tip
field was removed since the client is no longer needed to check for it.On miden-client #734 the client is updated to consume the stream and test the new behaviour.