Skip to main content

Relayer recovery

How to diagnose the relayer’s writz-lend state, rebuild it from the chain, and start a new relayer from nothing. Audience: whoever operates a relayer, including a third party running their own.

What the relayer holds

Positions and balances live in writz-lend. Losing the relayer’s data loses no funds, and every position stays readable on chain. The relayer keeps three things of its own: Without a cursor, the repay watcher starts from WRITZ_LEND_DEPLOY_LEDGER, never from the chain tip. A lost database therefore delays a borrower’s release. It does not drop it, as long as that ledger is inside RPC retention.

Diagnose

Everything starts at GET /health:

Recover

Stop the relayer first. The command refuses to run while a live relayer on the same host holds the data directory.
The output is the same in both:
The result is accepted only if all of these hold:
  • Every deposit has a position, and its status matches the replay: repay_full gives Closed, liquidate gives Liquidated, otherwise Active.
  • For each supplier, supply minus withdraw in scaled units equals get_supply_position.
  • The sum over all suppliers equals total_scaled_supply.
Contract views can only be read at the current ledger, so the command reads the events again after the views. If a new event arrived, it checks again, and after five rounds it fails instead of accepting a mixed state. Run it again when the contract is quieter. If a check fails, it prints the first difference, exits non-zero, and changes nothing. Do not work around it: the chain is right and the events are not. If it passes, the archive is swapped in atomically, the watcher’s cursor moves to the replayed position, and every repay_full without a release is queued. Start the relayer: the sweep publishes those releases with no manual step. Existing pending rows are kept.

Start a new relayer

The rebuild needs public RPC access only, with no copy of anyone’s database, and a source of events never has to be trusted, because the chain proves or rejects the result.
Then configure the rest of the relayer as in Deploy targets and start it. Publishing releases needs the relayer account and the protocol signer. Rebuilding does not.

Limits

  • RPC retention. Testnet RPC keeps 120,960 ledgers, 7 days. The rebuild reads from WRITZ_LEND_DEPLOY_LEDGER, so past that window it stops with outside RPC retention.
  • The archive does not replace the rebuild yet. The rebuild reads RPC only. Rebuilding from an archive file is tracked in #266.
  • After each writz-lend redeploy, update WRITZ_LEND_ID and WRITZ_LEND_DEPLOY_LEDGER together. Rows of the old contract stay in the tables, and the sweep ignores them.

Past RPC retention

Until #266 ships, a relayer that lost its data after the window recovers the releases by hand. Positions and balances are still safe on chain.
  1. List the repay_full events from a source with longer history: a second RPC provider, Hubble or Galexie, or an explorer for a small gap. You need each position’s txid in internal byte order, as in topic[1] of the event.
  2. Queue them. handleRepayFull is idempotent, so a txid that already has its release is harmless, and the sweep does the rest.
  1. Start the relayer and confirm get_release_psbt returns a value for each one.
Never write ad-hoc PSBT signing for this. handleRepayFull also checks the position’s script against the protocol key, and refuses to co-sign on a mismatch.

Stuck releases

A stuck row has used its attempts. The usual cause is a deterministic failure, such as the script mismatch above after a protocol key change. Read the error in the pending_releases table:
Fix the cause, then let it retry:
The borrower is not locked out meanwhile: the timelock exit does not depend on the relayer.

Reference