Skip to content

server/swap: Meshify swap - #3631

Open
martonp wants to merge 94 commits into
decred:masterfrom
martonp:mesh-swap
Open

martonp wants to merge 94 commits into
decred:masterfrom
martonp:mesh-swap

Conversation

@martonp

@martonp martonp commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

This PR builds on #3630 and integrates server/swap with mesh. It adds 21 commits (+6,932, -1,439).

It adds 2 mesh commands:

  • init: validates a client's swap contract and emits a swap_contract_recorded event.
  • redeem: validates a client's redemption and emits a swap_redemption_recorded event.

It also adds 6 events:

  • match_acks_recorded: records client match acknowledgement signatures and swap addresses.
  • swap_contract_recorded: records a validated swap contract and advances the match's settlement status.
  • audit_ack_recorded: records a client's acknowledgement of the counterparty's swap contract.
  • swap_redemption_recorded: records a validated redemption and applies the resulting match, order, and reputation changes.
  • redemption_ack_recorded: records a client's acknowledgement of the counterparty's redemption.
  • match_failed: ends an unsuccessful match and applies any resulting order revocations and reputation changes.

While the swap protocol and general functionality remain largely the same, the swapper must be able to continue active swaps after another node takes over. To support this, some things had to be redesigned:

  • Active swap restoration is moved out of NewSwapper into a mesh state loader. Every active swap must be restored successfully. Missing required data now fails startup instead of causing matches to be skipped.

  • Fixes gaps in active swap restoration, including UTXO funding coin locks and the secret revealed by the maker's redemption, which were not previously restored.

  • The swapper runs as a mesh master worker before the market workers, so it is ready to track matches when markets start. Inactivity checks are enabled only after all market workers report readiness.

  • To recover from requests lost during failover or connection interruptions, outstanding match, audit, and redemption requests are periodically resent while still needed to settle active matches. They are also resent when the swapper starts or a user connects to the master. Counterparty swap address notifications are also periodically resent while needed.

  • init and redeem requests are now idempotent.

  • Implements TrackMatches and RequestMatchAcks as a replacement for Negotiate. These functions were left as stubs in the market PR. Match persistence already happens in epoch_processed. These functions register the matches in memory and request client acknowledgements.

Create the event log table to store each event's sequence number, kind,
payload, transaction data, and tip hash.

Add `applyEventTx` to run an event's database changes and append its log
entry in one transaction. Both are committed together, and a mismatch
with a supplied sequence number or expected tip hash rolls back the
changes. This helper is for use by event appliers.

Add `EventLogFrontier` and `EventLogEntriesAfter` so the mesh package can
query the event log table.
Define which tables are included in snapshots, which must be empty
before loading a snapshot, and which are preserved as local configuration.
Check at startup that every table in `public` and the stored market schemas
has a classification to avoid future developers adding tables without
considering how snapshotting will be affected.

Add `HasNoEventSourcedState` to check whether all event-sourced data is
absent, as required before loading a snapshot. Add `WipeEventSourcedState`
to clear all event-sourced data before restoring from a peer.
Add `WriteSnapshot`, used by the master to create a snapshot that is
sent to the slave, and also add `LoadSnapshot`, used by the slave to
populate its DB with the master's data.

The snapshot includes:

- The event-log sequence number and tip hash corresponding to the snapshot.
- All accounts, bonds, prepaid bonds, and reputation points.

For configured markets, it also includes:

- Market lifecycle state.
- Active orders, cancels, and matches.
- The past 30 days of archived orders, archived cancels, and matches, plus
  older archived orders referenced by included matches or archived cancels.
- The latest 1,000 candles from each candle table.
- Recent epoch reports needed to rebuild unfinished candles.
Add the v9 upgrade and update table schemas. The upgrade makes the
following changes:

- Create the `event_log` table.
- Create the `market_lifecycle` table to store market state, scheduled
  suspensions and resumptions, epoch progress, and trading parameters.
- Remove the legacy `accounts.fee_asset` column.
- Remove commitment and preimage uniqueness constraints from
  `orders_archived` and `cancels_archived`. Nodes may retain different
  archived histories, so these constraints could make archiving the same
  order succeed on one node and fail on another.
- Add non-unique commitment indexes to `orders_archived` and
  `cancels_archived` for lookups.
- Add indexes on `makerOrder` and `takerOrder` for active matches, to
  support finding an order's active matches during future swap processing.
- Add a genesis event when a database has existing event-sourced data and
  an empty event log. It includes a random nonce, so each legacy database
  initialized this way has a distinct history. Databases with no
  event-sourced data do not receive a genesis event.
Return account lookup errors separately from an unknown account, and update auth
callers to handle them. This prevents database failures from being treated as
missing accounts or empty bond lists when connecting, posting bonds, or
calculating reputation. This is needed for mesh because a failed database lookup
must not be cached as though the account does not exist.

Pass the caller's context through account and bond queries so cancellation and
deadlines cover the entire lookup.
Introduce the `server/meshevents` package as the catalog of mesh events shared
by the database and application packages. Define events for bond posting,
prepaid bond creation, and reputation forgiveness. Add JSON decoding for order
and match IDs to complement their existing JSON encoding.
Adds `repCache`, an LRU cache for account scores and bonds, to reduce repeated
database reads.

Previously, reputation calculations used active bonds and recent match,
preimage, and order outcomes held in memory for connected users. With mesh, both
nodes need to calculate reputation regardless of which node a user is connected
to.

The cache accepts a caller-supplied function that loads calculated scores and
bond data. Integration with database reads and reputation lookups is added in
subsequent commits.
Previously, legacy reputation was converted to `points` when an account's
reputation was first loaded. With mesh, snapshots can omit older order and match
history needed for that conversion, so converting lazily could produce different
scores on different nodes.

Converts all remaining version-0 accounts during the v9 database upgrade
instead, before mesh starts. This completes the conversion once, so reputation
lookups can read directly from `points` and snapshots include the converted
data.
Previously, reputation lookups used bonds and recent outcomes held in memory for
locally connected users. Updates these lookups to calculate reputation from
stored outcomes and bonds, using `repCache` to cache calculated scores and bond
data. This allows reputation to be calculated independently of which mesh node
the user connects to, without active users repeatedly querying the database.

Adds `UserReputationAt` to calculate reputation with bond expiry evaluated at a
supplied time. Stops periodic bond-expiry checks and retains expired bonds so
their contribution can still be calculated when replaying earlier events.

Adds `BondExpiryThreshold` to `account.Reputation` so clients can determine
which bonds were included in the reported tier. With periodic bond-expiry
notifications disabled, clients will need to track expiry themselves.

Adds `AcctRepStatus` with an error return so callers can distinguish a failed
reputation lookup from an unknown account or a zero tier. This will be required
by the unbooking logic to avoid removing orders when reputation cannot be
retrieved.
Adds `SetReputationInputsListener` and registers an auth callback that
invalidates affected cache entries when notified. The callback refreshes
reputation and sends score-change notifications to locally connected user.

The event appliers added in subsequent commits will invoke the listener after
updating reputation inputs.
Replaces the functions called by the market and swap packages to record
reputation outcomes with stubs. These stubs will be removed when those packages
are updated to record outcomes through mesh events.
Adds support for delivering requests and notifications to clients connected
through a mesh peer, routing responses back to their handlers, and expiring
unanswered requests. Keeps local-only delivery available for node-local
notifications.
Adds `ApplyBondPostedEvent` to store posted bonds, create accounts when needed,
and consume prepaid bond tokens. Returns the account's bonds and latest
reputation outcomes from the same transaction so auth can calculate the
reputation returned with the bond response.
Adds the `postbond` command, which validates on-chain bonds and prepaid-token
redemptions, waits for any missing confirmations, and emits a `bond_posted`
event for newly accepted bonds. Adds the `bond_posted` event handler, which
stores the account and bond and consumes the prepaid token when redeeming one.

Adds the initial mesh setup in the `dex` package. Since dcrdex configuration
options for mesh have not been added yet, the service runs in single-server
mode.
Adds `ApplyPrepaidBondsCreatedEvent` to store newly created prepaid bond tokens.
Adds the `create_prepaid_bonds` command, which generates prepaid bond tokens
with the requested strength and lifetime and emits a `prepaid_bonds_created`
event. Adds the event handler, which stores the tokens in the db.
Adds `ApplyReputationForgivenEvent` to forgive all penalties on an account or
the penalties associated with a single match.
martonp added 28 commits October 1, 2026 00:41
Updates `AcceptOrderCommand` to emit a `suspended_cancel` event when canceling a
booked order on a suspended market.

Applying the event persists the cancellation, removes the order from the book,
releases its funding locks, and notifies clients.
Adds `marketEpochDriver` to run the market using the previously introduced
events. It performs startup cleanup, advances epochs at their scheduled
boundaries, collects preimages, and publishes `epoch_processed` events once
collection finishes. It also completes suspensions after the final epoch is
processed and resumes markets when their scheduled time arrives.

The driver waits for asset backends to synchronize before starting or resuming
trading. The driver is connected to `Market.Run` and registered as a mesh master
worker in a later commit.
Previously, `AddMarketSource` loaded candle history as each market was
registered. It now only registers the market, and `LoadCaches` loads the history
separately. This allows candle caches to be initialized after market state is
restored, using the stored epoch duration.
A `market_started` event can change the epoch duration after candle caches have
been loaded. Updates `ReportEpoch` to adjust the epoch candle cache when this
happens, preserving the standard candle caches and their history.

Ensures standard candles are persisted even when their interval matches the
epoch duration. Also fixes the history cutoff in `LoadEpochStats` so candle
restoration includes the epoch ending exactly at that cutoff.
Connects the market package to the mesh service by registering its order and
lifecycle commands, event appliers, state loaders, and lifecycle callback.
Replaces the legacy `Market.Run` loop with `marketEpochDriver` and registers
markets as mesh master workers.
Splits `Negotiate` into `TrackMatches`, which locks funding coins and registers
already-persisted swaps in memory, and `RequestMatchAcks`, which sends match
requests to clients and handles their acknowledgements. This lets every node
track swaps when applying an `epoch_processed` event, while only the master
requests acknowledgements.

Removes match insertion and order cancellation from the swapper, since these
database updates are now handled by the `epoch_processed` event applier.
Adds the `match_acks_recorded` event and `ApplyMatchAcksRecordedEvent` to
store a batch of client match acknowledgement signatures and swap addresses.
Adds the `match_acks_recorded` event applier to store match acknowledgement
signatures and swap addresses before updating tracked matches and notifying
counterparties. Updates `processMatchAcks` to validate the entire response and
emit one event containing the acknowledgements.
Adds the `swap_contract_recorded` event and `ApplySwapContractRecordedEvent`
to store a maker's or taker's swap contract, coin ID, and acceptance time,
and advance the match status to `MakerSwapCast` or `TakerSwapCast`.
Adds the `init` command, which validates the submitted swap contract and emits a
`swap_contract_recorded` event. The event applier records the contract and
updates the tracked match’s swap status. After successful application, the
command acknowledges the request and asks the counterparty to audit the
contract.

Contract validation queries the blockchain before emitting the event. The event
includes the contract details needed to reconstruct swap state without querying
the blockchain during application.
Adds the `audit_ack_recorded` event and `ApplyAuditAckRecordedEvent` to store
a client's acknowledgement signature for its counterparty's swap contract.
Updates `processAck` to emit the event instead of updating the database and
in-memory match itself.
Adds the `swap_redemption_recorded` event and
`ApplySwapRedemptionRecordedEvent` to store a maker's or taker's redemption,
advance the match status, and record the redeeming user's successful match
outcome, excluding self-trades. It also records the order's completion time
and reputation outcome when the order is executed and has no unsettled
matches remaining.
Adds the `redeem` command to validate the client's secret and redemption
transaction and emit a `swap_redemption_recorded` event. Adds the event
applier to persist the redemption and reputation outcomes, update match and
order state, and stop tracking the match once the taker has redeemed.
Adds the `redemption_ack_recorded` event and
`ApplyRedemptionAckRecordedEvent` to record a client's acknowledgement of
its counterparty's redemption. Updates `processAck` to emit the event
instead of updating the database and in-memory match itself.
- Fixes `processBlock` so encountering a match for another asset does not stop
  the remaining confirmation checks.
- Releases each match’s lock after its confirmation check instead of holding all
  locks until the entire scan finishes.
- Removes funding-coin unlocking from block confirmation checks because it is
  handled by event appliers instead.
Adds `applyMatchFailedEvent` to record match failures and their reputation
effects, remove failed matches from memory, update affected orders through
`swapDone`, and notify clients. Rejects failure decisions invalidated by changes
to the match status or newly received swap addresses.
Updates timeout and expired-contract handling to submit `match_failed` events
instead of directly updating storage, reputation, and market state. Failure
detection no longer removes matches before the event is applied. Each event
preserves the state used to detect the failure, allowing the existing applier
to reject it if subsequent swap progress has made it invalid.
Updates `init` and `redeem` commands to return a successful acknowledgement
when the request matches settlement data already recorded in the database,
including after the match has left memory. This allows clients to retry
after a lost response.
Simplifies active swap restoration by reusing `registerSwapContractDedup`
to restore contract and secret-hash tracking, and removing redundant checks
for unavailable assets.
- Exposes `RestoreActiveSwaps` so it can be used as a mesh state loader.
- Restores funding-coin locks for orders involved in active matches.
- Restores the maker’s redemption secret.
- Corrects the maker/taker audit-signature mapping and uses the
  counterparty’s contract data when loading redemptions.
- Fails restoration instead of skipping matches whose orders or settlement
  data cannot be loaded, preventing startup with incomplete swap state.
Resends unanswered `match`, `audit`, and `redemption` requests periodically so
settlement can continue after requests or responses are lost, including during
failover. Requests are rebuilt from the recorded match state and sent by the
master to users connected to either node. Adds periodic resending of
counterparty address notifications until the recipient’s swap contract is
recorded; previously, these notifications were sent when both addresses became
available and when a user reconnected.

Updates `UserConnected` to resend the user’s pending requests immediately when
they connect to the master, avoiding the wait for a periodic retry.
Fixes swap coin locker assignment so UTXO funding coins remain locked during
settlement.
…se windows

Adds `resumePendingRequests` to resend unacknowledged match, audit, and
redemption requests, and `extendInactionDeadlines` to give clients a fresh
response window.
- Runs the swapper as a mesh master worker before the market workers so it
  can handle matches as markets start.
- Adds a final master worker that enables inactivity checks after all market
  workers report readiness, giving clients a fresh response window so startup
  delays do not cause inactivity penalties.
- Moves active swap restoration from `NewSwapper` to a mesh state loader.
- Resends pending requests when the swapper starts.
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.

1 participant