Class: Solrengine::Sdp::ProvisionWalletJob

Inherits:
ActiveJob::Base
  • Object
show all
Defined in:
app/jobs/solrengine/sdp/provision_wallet_job.rb

Overview

Provisions an SDP custody wallet for a WalletOwner row, driving the four-state machine: pending → provisioning → ready | failed.

Idempotent three ways:

1. The row is already ready → return immediately.
2. Optimistic claim: only the job that flips the row pending/failed →
   provisioning proceeds; a duplicate concurrent job updates 0 rows
   and returns without touching the network. A provisioning row whose
   lease has lapsed (worker died between claim and settle) may be
   taken over — see #claim.
3. Label adoption: when SDP already has a wallet labeled
   "#{namespace}-user-#{id}" (a previous create succeeded but the
   response was lost, e.g. a read timeout), adopt it instead of
   creating a duplicate.

Failure posture:

- Transport errors (Unavailable/Timeout) retry with backoff; the claim
  stays held across retries (see #claim). Exhaustion lands the row in
  failed with the transport reason — never a silent dead-letter.
- ProviderCapabilityError (the FL-10 gate: local custody cannot create
  per-user wallets) is terminal: failed immediately, with the
  actionable "use a managed provider" message renderable to the app.
- Any other Sdp::Error is equally terminal — retrying auth/validation
  bugs changes nothing; the reason is stored, not buried in logs.

Inherits ActiveJob::Base directly so the engine never depends on the host app’s ApplicationJob being defined or compatible.

Instance Method Summary collapse

Instance Method Details

#mark_provisioning_failed(user, reason) ⇒ Object

Settles the row in failed — but only when this job holds the claim (row is in provisioning), so a stale job can never clobber a row another job has since taken to ready. Public because the retry_on exhaustion block runs outside the instance’s private context.



71
72
73
74
75
# File 'app/jobs/solrengine/sdp/provision_wallet_job.rb', line 71

def mark_provisioning_failed(user, reason)
  user.class
      .where(id: user.id, sdp_provisioning_state: "provisioning")
      .update_all(sdp_provisioning_state: "failed", sdp_provisioning_error: reason)
end

#perform(user) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/jobs/solrengine/sdp/provision_wallet_job.rb', line 48

def perform(user)
  return if user.wallet_ready?
  return unless claim(user)

  wallet = existing_wallet_for(user) || create_wallet_for(user)
  user.update!(
    sdp_wallet_id: wallet.id,
    wallet_address: wallet.public_key,
    sdp_provisioning_state: "ready",
    sdp_provisioning_error: nil
  )
rescue ::Sdp::Unavailable, ::Sdp::Timeout
  raise # retry_on handles backoff; the claim stays held for the retry
rescue ::Sdp::Error => e
  # Terminal: capability gates (AE1) and auth/validation errors fail the
  # same way no matter how often they are retried. Reason is renderable.
  mark_provisioning_failed(user, e.message)
end