Repository navigation
Conversation
martonp
force-pushed
the
mesh-package
branch
from
September 22, 2026 06:05
8e864a5 to
5d5a3ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR introduces
server/mesh, which provides the infrastructure for running DCRDEX as a two-node master/slave mesh.This package lays the foundation for converting the server into a two-node replicated state machine. It handles all the communication between the two nodes and exposes an API for the rest of the server to integrate with. The application and database changes will follow separately. Those changes mainly involve refactoring the existing logic into command handlers, event appliers, master workers, and state loaders.
I've already completed the meshification of the rest of the server. Those changes still need some polish before review, but they can be viewed here:
https://github.com/martonp/dcrdex/tree/mesh
I also have a mainnet mesh running on these two nodes:
Let me know if you want to test it out, I can send prepaid bonds.
The goal was to preserve the existing client/server protocol. The only changes to the client are that the client can learn the second server's address and reconnect there if its current server becomes unavailable. Legacy clients can still connect to one server using the existing API.
Application API
Serviceis the entry point to the mesh package. The application registers four things:EventApplierfor each event kind. It must commit the database changes in one transaction, then update the in-memory state after that transaction succeeds.The full conversion currently has 10 command types and 16 event types. For example, the
limitcommand validates an order and can produce anorder_acceptedevent. A market worker produces events such asadvance_epochorepoch_processed. The application registers these types and implements their behavior. The mesh package is not aware of what they do.Event application and replication
The master produces events containing the inputs needed for both nodes to apply the same state transition. Event application can only include deterministic computation based on the event itself and the previous state.
Each event’s database transaction records an event-log entry containing its sequence number and tip hash. Mesh applies events one at a time and streams committed entries from the master to the slave.
After successfully connecting, the slave subscribes to the master's event stream using its last committed event log position. The master reads later entries from its database and sends them in batches. The slave applies the events in order and checks their expected positions and hashes. The master waits for each batch to be acknowledged before sending the next.
Replication is asynchronous. The master can commit new events without waiting for the slave. Waiting for acknowledgement limits the event stream, not local event application.
The tip hash identifies event history by chaining each event to the previous tip. Starting from the same state, both nodes must produce the same state changes when applying the same events. A tip-hash mismatch while applying a replicated event halts the slave.
Commands
The mesh service executes commands on the master and delivers responses through the node that received the request. A command can produce an event or complete without changing shared state.
Response timing depends on where the request arrives:
Commands that fail or complete without an event send their replies separately from the event stream.
A command may commit on the master even if the slave never receives its result. If the slave cannot determine the outcome, it returns
ResultUnavailableErrorto the client. The application must make command handlers idempotent so the client can retry the same request without having a second event be emitted.Startup, handshake, and readiness
A node configured with a peer must complete a handshake before serving clients. On each connection, the nodes perform a handshake and compare configurations, roles, and event-log positions to determine compatibility and select the master. Both nodes must share the same signing key and compatible market and asset configurations.
After the handshake is complete, the state loaders must run before event application / master workers can begin.
Then, before clients can connect:
Role selection and divergent histories
The handshake compares the nodes’ event histories:
Histories are compatible if they are equal or one extends the other.
For compatible histories, an existing master keeps its role if its log is at least as long as the peer’s. If neither node is master, the longer log wins, with the lower node ID breaking a tie. The other node becomes slave and catches up if needed.
With divergent histories, an existing master rejects the joining peer and keeps serving. The joining peer halts. If neither or both nodes are master, both halt.
Two masters with equal histories also halt.
The exact role-selection rules are defined in
reducer.go.A node that has not established a role halts if the peer’s configuration is incompatible. An established master rejects the incompatible peer and keeps serving, so a misconfigured joining node cannot take it offline.
Snapshots
A fresh node can load a snapshot from a master instead of replaying the entire history. Replaying the full history is not possible if the node was created pre-mesh.
A snapshot includes all account data, active trade/swap data, and historical trade/swap data for the past 30 days. This logic is not part of this PR. The mesh package just handles the transfer.
A snapshot includes the event-log position corresponding to its database state. After loading it, the slave subscribes to events after that position.
Failover and shutdown
An established slave that loses its master waits 30 seconds before promoting itself, giving the master time to reconnect. A slave still performing its initial sync returns to pending if it loses the connection and does not promote from that state.
During graceful shutdown, the master stops its workers and closes event publication and waits for the slave to catch up. If the slave catches up, the master requests handoff. On receiving the request, the slave promotes immediately, avoiding the usual promotion delay.
Two failure scenarios are important:
Mesh does not reconcile divergent histories or automatically discard committed events. The operator has to decide which history to keep. In the first failure scenario, which can happen quite easily if the master crashes, this is very simple. This is why we do not halt the serving master. In the split-brain scenario, which is rare, this would be a larger challenge.
Client message relaying / presence
The mesh package also provides the functionality to relay client messages between nodes and query which users are connected to the peer.
Messages can target an individual user or be broadcast to the peer's clients. This lets the master send swap requests and notifications to users connected to the slave.
The application combines the peer's response to a presence query with its local connection state to determine whether a user is still connected to the mesh. The master uses this when deciding whether to revoke booked orders after a prolonged disconnection.