Repository navigation
ic402 v2.17.6
Patch — three job-registry fixes: no job is paid twice or stranded by a race or a failed
payment. Found by the adversarial reviews of 2.17.5 and 2.17.6. No API, type, PaymentResult or
example.did change; resolveDispute accepts one more status.
Fixed
- A resolved job could come back to life.
resolveDispute(refunding the buyer) and
settleJob(paying the operator) put the job in#Settling, await the ledger, and on failure
revert it for retry.resolveJobaccepts any#Settlingjob, so a controller could make it
terminal during that await; the failure then restored the job from a copy taken before the
await, back to its pre-await status (#Submittedor#Disputed) or to#Verified. A
#Submittedor#Disputedjob could then be refunded a second time (by the expiry sweep or
anotherresolveDispute), and a#Submittedone could be confirmed by the buyer and settled to
the operator, after the controller had already handled the funds out-of-band. A#Verifiedone
was stuck (see below), and with this release'sresolveDisputechange could have been settled or
refunded a second time. Those two failure arms now revert only a job that is still#Settling. - The expiry sweep could refund a job whose operator had just been paid.
expireJobsawaits
each refund while walking the job map, and the map's iterator hands back the jobs of a bucket as
they were when it entered that bucket. A job a buyer confirmed (or a controller resolved) during
an earlier job's refund still looked#Submittedto the sweep, so it was marked#Expiredand
its buyer refunded on top of the operator payment. A buyer with two timed-out jobs in the same
job-map bucket could set this up (ids are sequential and the hash is public, so such a pair turns
up within a few dozen jobs). The sweep now re-reads each job's live record before deciding. - A failed operator payment stranded the job in
#Verified.settleJobrolls the job back to
#Verifiedfor retry when paying the operator fails (an ICRC-1 error, or an EVM operator with no
payout address), but nothing could retry it: every public transition rejected#Verified, and
the record was never garbage-collected (and since 2.17.5 held the job sweep at its 60 s cadence).
resolveDisputenow also accepts a#Verifiedjob, so a controller can retry the settle
(refundBuyer = false) once the cause is fixed, or refund the buyer. No funds have moved for a
#Verifiedjob, so either is safe; see the recovery runbook's §6.
Not changed
- The success and parked (
#pending) arms, andsettleJob's Upto-remainder arms once the
operator is paid (which mark#Settledeven when that refund fails), still write their outcome
over aresolveJobthat landed during the await: they record a transfer that did, or may have,
happened, whichreconcileJobrelies on.