Skip to content

feat: Store Replica Mode#1987

Merged
sergerad merged 56 commits intonextfrom
sergerad-replicas
May 4, 2026
Merged

feat: Store Replica Mode#1987
sergerad merged 56 commits intonextfrom
sergerad-replicas

Conversation

@sergerad
Copy link
Copy Markdown
Collaborator

@sergerad sergerad commented Apr 23, 2026

Store Replica Mode

Adds a replica mode to the store, enabling (replica) store instances to sync blocks and proofs
from an upstream store. This allows the RPC layer to be scaled horizontally by pointing multiple
RPC nodes at multiple replica stores, without adding load to the primary store or block producer.

Architecture

The store now operates in one of two mutually exclusive modes, controlled by the new StoreMode
enum:

  • BlockProducer mode — existing behaviour. Accepts blocks from the block producer via the
    BlockProducer gRPC service, runs the proof scheduler, and exposes the NtxBuilder service.
  • Replica mode — new. Syncs blocks from an upstream store via the new StoreReplica gRPC
    service. Only exposes the Rpc and StoreReplica services — no block producer service, no NTX builder service, and no proof scheduler task.

New: StoreReplica gRPC service

Implemented on the upstream store side. Exposes two streaming RPCs:

  • SubscribeBlocks — streams committed blocks to a subscriber starting from a given block
    number.
  • SubscribeProofs — streams block proofs to a subscriber starting from a given block number.

Operational

  • run-node.sh updated to demonstrate a topology with a primary store
    and two replicas, with RPC nodes pointing at the replicas.
 # Second replica (upstream = first replica)
➜ grpcurl -plaintext -proto  ./rpc.proto -d '{}' "localhost:57293" rpc.Api/Status
{
...
  "store": {
    "version": "0.15.0",
    "status": "connected",
    "chainTip": 13
  },
...
}

# First replica (upstream = block producer store)
➜ grpcurl -plaintext -proto  ./rpc.proto -d '{}' "localhost:57292" rpc.Api/Status
{
...
  "store": {
    "version": "0.15.0",
    "status": "connected",
    "chainTip": 13
  },
...
}

@sergerad sergerad marked this pull request as ready for review April 24, 2026 03:58
Copy link
Copy Markdown
Collaborator

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

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

I'm about halfway through, I think so far my main question/suggestion is moving the proof inputs out of the db.

Comment thread bin/node/Dockerfile Outdated
Comment thread Makefile Outdated
Comment thread docker-compose.yml
Comment thread CHANGELOG.md
Comment thread proto/proto/internal/store.proto Outdated
Comment thread proto/proto/internal/store.proto Outdated
Comment thread proto/proto/internal/store.proto Outdated
Comment thread proto/proto/internal/store.proto Outdated
Comment thread crates/store/src/db/models/queries/block_headers.rs
Comment thread crates/store/src/server/replica.rs Outdated
@sergerad sergerad force-pushed the sergerad-replicas branch from 4907595 to 637af22 Compare April 29, 2026 22:29
Copy link
Copy Markdown
Contributor

@kkovaacs kkovaacs left a 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!

Copy link
Copy Markdown
Collaborator

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

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

I left some minor (potentially followup) issues.

I think we can head towards an embedded store, which will also change this code somewhat, so we can have another clean up pass then perhaps.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I suspect we could get away with a basic VecDeque for our use case since block numbers are ordered and we should only ever be inserting in sequential order?

Need to review usage site still though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Proof cache push can occur out of order

Comment on lines +27 to +34
self.0.send_if_modified(|current| {
if new_tip > *current {
*current = new_tip;
true
} else {
false
}
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm unsure when we would be calling this out-of-order?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It should never be called out of order based on how we use it atm. I would still prefer to keep the type impl robust against out of order calls.

Comment on lines +247 to +250
// Cache the proof bytes for replica subscriptions.
proof_cache.push(block_num, ProofNotification::new(block_num, proof_bytes));

// Advance the proven tip (this also notifies replica watch subscribers).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Won't this be much simpler if we pass the proof back to the scheduler, who can then arrange and insert the blocks in order?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment thread crates/store/src/state/notifications.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: feels a bit weird to call these notifications. They're really just serialized block and block proofs, that we happen to use in the context of a cache/event system.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Renamed to replica.rs

Comment thread crates/store/src/server/replica.rs Outdated
}
next = next.child();
}
// Wait for tip change.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fyi I'm unsure if you made this a do-while loop on purpose, but this changed call would return immediately on the first iteration, so its possible to to have this be the loop condition e.g.

while tip_rx.changed().await.is_ok() {
    ...
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The thing is that we want to read the tip before waiting for tip change

@sergerad sergerad merged commit 961546e into next May 4, 2026
17 checks passed
@sergerad sergerad deleted the sergerad-replicas branch May 4, 2026 19:26
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.

3 participants