Skip to content
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: add StateSync component #650

Open
wants to merge 25 commits into
base: next
Choose a base branch
from
Open

feat: add StateSync component #650

wants to merge 25 commits into from

Conversation

tomyrd
Copy link
Collaborator

@tomyrd tomyrd commented Jan 2, 2025

This PR is the second iteration of the StateSync component, based on this comment. Although the PR is still in draft, most of the core changes are already made so it can be reviewed, at least partially.

I like this new version a lot more than the previous. It's already working and the rust unit and integration tests are passing.

General changes

  • The rpc_api in the client was changed from Box to Arc so it can be shared with the component. This also means that I had to change the base struct to give it interior mutability.
  • I moved most of the code from crates/rust-client/src/sync/mod.rs to the new crates/rust-client/src/sync/state_sync.rs. The moved code isn't a 1 to 1 replica, since I was moving the code, I went ahead and refactored it a bit to be more clear and added some extra documentation. I belive this new version of the sync process is more clear and easier to read/understand.

TODOs

  • Rebase the branch to bring the changes from the RPC functions (some conflicts will probably emerge but they shouldn't be too hard to solve)
  • Since I'm already refactoring the sync process, I want to move the apply_mmr_changes part inside the component. I feel like it could be better documented and it should fit nicely inside the note_state_sync logic (it's just a different note state change).
  • Remove all the temporary TODOs in the code.
  • Once I'm finished with the changes above, I need to modify the web store to be compatible with the new functionality.

@tomyrd tomyrd force-pushed the tomyrd-sync-component-alt branch from 8731b2b to d54e186 Compare January 2, 2025 23:05
@tomyrd
Copy link
Collaborator Author

tomyrd commented Jan 8, 2025

I need to tidy up the code and update the documentation, but the basic idea with this updated version is that we have callback functions (defined in state_sync.rs) that receive the unprocessed sync response and return the updates needed to be made in the store. These callbacks can be defined as closures that have access to the rpc_api and store and could be redefined by the user to affect how the changes are applied to the store at the end.

These callbacks could also work as a sort of event reaction, as the user could redefine them and they would be called on each sync with all the new and updated node information.

@tomyrd tomyrd force-pushed the tomyrd-sync-component-alt branch from 6baeafa to 50938fb Compare January 9, 2025 02:42
@tomyrd tomyrd force-pushed the tomyrd-sync-component-alt branch from 50938fb to 2f26007 Compare January 9, 2025 14:50
Comment on lines +165 to +173
for (account_id, digest) in account_updates.mismatched_private_accounts() {
// Mismatched digests may be due to stale network data. If the mismatched digest is
// tracked in the db and corresponds to the mismatched account, it means we
// got a past update and shouldn't lock the account.
if let Some(account) = self.get_account_header_by_hash(*digest).await? {
if account.id() == *account_id {
continue;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be done before calling the store? Then it would be deduplicated from the codebase, or am I missing something?

Copy link
Collaborator Author

@tomyrd tomyrd Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be done before calling the store?

Not with the current structure, because the SyncStateUpdate is built inside the StateSync component which doesn't have access to the store. But the code duplication isn't ideal, I'll add it to the follow ups. If we want to access the store inside the process, we should add a callback (maybe we can re-add this one).

.updated_input_notes()
.iter()
.chain(committed_notes.new_input_notes().iter())
.chain(note_updates.new_input_notes().iter())
.filter(|note| note.is_committed())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change needed?

Copy link
Collaborator Author

@tomyrd tomyrd Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, the check_block_relevance only received committed note updates, now it receives every note update, and some of those input note records can't be converted to Note (if the note was consumed externally it doesn't store its metadata).

@tomyrd tomyrd marked this pull request as ready for review January 10, 2025 21:12
@igamigo igamigo linked an issue Jan 10, 2025 that may be closed by this pull request
@igamigo igamigo changed the title feat: add StateSync component (alternative) feat: add StateSync component Jan 13, 2025
@igamigo
Copy link
Collaborator

igamigo commented Jan 14, 2025

I've fixed some of the comments that I had on my pending review and refactored the code a little bit. I've also added a comment on the follow-up issue (#663). One thing to note is that the StateSync component is not currently a part of Client, but rather it a default implemnetation is instantiated whenever we want to do a sync. It can also be used separately of course, but there is no way to provide a custom StateSync to a Client to compose it with the other client features. Not sure if this was the intention of this first iteration or not, but mentioning it just in case. cc @bobbinth

Comment on lines +197 to +205
pub async fn sync_state_step(
&self,
current_block: BlockHeader,
current_block_has_relevant_notes: bool,
current_partial_mmr: PartialMmr,
accounts: Vec<AccountHeader>,
note_tags: Vec<NoteTag>,
unspent_nullifiers: Vec<Nullifier>,
) -> Result<Option<SyncStatus>, ClientError> {
Copy link
Contributor

@bobbinth bobbinth Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: maybe I missed this, but I thought we were going to do a full sync (not just one step) and then return the result of the entire sync from this function. Then, the result would be inserted into the store.

What is the motivation for exposing step-wise syncing?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not entirely sure what the original motivation here was. One thing that comes to mind is that we get the new state from the store each time we do a new step. This involves the block number, account headers and note tags. I think it shouldn't be too difficult to make it so that we can expose the full sync instead though there is probably some logic that we need to add in order to make it keep a consistent state within the component. If everything else looks OK I could make another PR with these changes (unless you prefer to have them in this PR instead)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refactor: separate client in more stand-alone components
3 participants