Skip to content

Server and Crank

The bridge server is the operational loop that keeps authority acknowledgements moving. It is not an optional dashboard backend — it is core bridge infrastructure. If the server is down, no authority votes are submitted and all transfers stall after the deposit step.

What the server does

The server runs an HTTP service and an in-process crank loop within a single binary.

At runtime it is responsible for:

  • Polling the source chain's DepositTracker for new deposits
  • Polling the destination chain's WithdrawTracker for finalized results
  • Submitting ack_deposit on the destination chain when unacknowledged deposits are found
  • Submitting ack_withdraw on the source chain when unacknowledged results are found
  • Discovering and submitting cleanup_deposit for finalized entries that have not yet been cleaned up
  • Retrying failed actions with bounded backoff
  • Exposing health, metrics, and admin endpoints for operators

Environment variables

HTTP configuration

VariableRequiredDefaultPurpose
BRIDGE_BIND_ADDRESSNo0.0.0.0:$PORT or 0.0.0.0:8080Explicit bind address and port
PORTNo8080Port fallback (Cloud Run sets this automatically)
BRIDGE_AUTH_USERNAMENoBasic auth username for admin endpoints
BRIDGE_AUTH_PASSWORDNoBasic auth password for admin endpoints

BRIDGE_AUTH_USERNAME and BRIDGE_AUTH_PASSWORD must either both be set or both be unset. If set, they protect the admin endpoints (/kill, /crank/once).

Runtime configuration

The crank loop activates when any of these variables is present. Once one is set, the full set is required — a partial configuration is a startup error.

VariableTypePurpose
BRIDGE_SOURCE_RPC_URLURLRPC endpoint for the source chain
BRIDGE_TARGET_RPC_URLURLRPC endpoint for the destination chain
BRIDGE_SOURCE_CHAIN_IDu16Numeric chain ID of the source chain
BRIDGE_TARGET_CHAIN_IDu16Numeric chain ID of the destination chain
BRIDGE_AUTHORITY_KEYPAIRPathFilesystem path to the authority signer keypair JSON

Crank tuning

VariableTypeDefaultConstraintsPurpose
BRIDGE_POLL_INTERVAL_SECSu6415Seconds between crank poll cycles
BRIDGE_CRANK_MAX_ATTEMPTSu323Minimum 1Maximum retry attempts per failed action
BRIDGE_CRANK_RETRY_BACKOFF_MSu641000Base backoff in milliseconds between retries

Bridge-specific

The crank loop holds a run lock (Mutex) that prevents concurrent executions. Even if the poll interval is short, a long-running cycle will not overlap with the next. This is deliberate — concurrent authority submissions for the same entries would waste transactions and complicate error handling.

HTTP endpoints

Public endpoints

GET /health

Returns a JSON BridgeHealthSnapshot:

json
{
  "status": "ok",
  "service": "bridge-server",
  "crank": {
    "enabled": true,
    "last_run_at": 1714000000,
    "last_success_at": 1714000000,
    "last_error": null,
    "last_action": "ack_deposit",
    "total_actions_submitted": 42
  }
}
FieldTypeMeaning
enabledboolWhether runtime config is present and the crank is active
last_run_atu64 or nullUnix timestamp of the most recent crank cycle
last_success_atu64 or nullUnix timestamp of the most recent successful action
last_errorstring or nullError message from the most recent failure
last_actionstring or nullDescription of the most recent action taken
total_actions_submittedu64Cumulative count of all actions submitted since startup

This is the first endpoint to check when diagnosing bridge issues. A stale last_run_at means the crank loop has stopped. A persistent last_error means the loop is running but failing.

GET /metrics

Prometheus-format metrics including counters and histograms for crank runs, retries, per-action submissions, and durations. Suitable for scraping into Prometheus/Grafana.

Admin endpoints

These require basic auth when BRIDGE_AUTH_USERNAME and BRIDGE_AUTH_PASSWORD are configured.

POST /kill

Terminates the server process. Use for emergency shutdown or controlled rotation.

POST /crank/once

Triggers a single crank cycle immediately, bypassing the poll interval. Returns a CrankReportResponse:

json
{
  "ack_deposit_attempted": true,
  "ack_withdraw_attempted": false,
  "cleanup_attempted": 3,
  "cleanup_completed": 2
}

This is the primary manual intervention tool. If the automated loop is stuck or you need to force a specific cycle, hit this endpoint rather than restarting the service.

Crank cycle details

Each poll cycle executes the following steps in order:

  1. Check for pending ack_deposit — compare source DepositTracker.entry_count against destination WithdrawTracker.entry_count.
  2. Submit ack_deposit if unacknowledged entries exist.
  3. Check for pending ack_withdraw — compare destination finalized entries against source DepositTracker state.
  4. Submit ack_withdraw if unacknowledged results exist.
  5. Discover cleanup candidates — scan for deposit entries that are Finalized but have cleanup_completed == false.
  6. Submit cleanup_deposit for each candidate.

Each action that fails is retried up to BRIDGE_CRANK_MAX_ATTEMPTS times with BRIDGE_CRANK_RETRY_BACKOFF_MS backoff between attempts. If all attempts fail, the error is recorded in the health snapshot and the crank moves on to the next action in the cycle.

SettingValueRationale
min-instances1Always-on; cold starts would stall bridge operations
max-instances1Prevents duplicate authority submissions from concurrent instances
concurrency1One request at a time; prevents admin endpoint races
cpu1Sufficient for RPC polling and transaction submission
memory1GiHeadroom for tracker account deserialization
Execution environmentGen2Required for always-on CPU allocation outside of request handling
IngressIAM-protectedAdmin endpoints should not be publicly accessible

Zink recommendation

Do not run multiple instances for the same authority signer. Duplicate submissions waste compute and fees, and concurrent authority votes on the same entries create confusing on-chain state. The single-instance, single-concurrency model is a deliberate safety constraint, not a scaling limitation.

Keypair mounting

Mount the authority keypair from Secret Manager as a file. The conventional path is:

/secrets/authority.json

Set the environment variable to match:

BRIDGE_AUTHORITY_KEYPAIR=/secrets/authority.json

Do not bake the keypair into the container image. Do not pass it as a plaintext environment variable. Secret Manager mounting ensures the key is available at runtime without persisting it in image layers or environment metadata.

Zink is a general-purpose SVM network for programs, operators, and bridge integrations.