Skip to main content

worgl_client/
purse.rs

1//! A holder that talks to its mints and saves itself.
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::{Mutex, MutexGuard};
6use std::time::Duration;
7
8use tokio::time::{Instant, sleep, sleep_until};
9use worgl::{KeySet, Note, PublicKey, TransferRequest};
10use worgl_sdk::{Balance, Holder, Payment, PaymentRequest, ScheduledTransfer, Spacing};
11
12use crate::client::Client;
13use crate::error::Error;
14use crate::store::{FileStore, Locked, Store};
15
16/// Longest wait honored before submitting a transfer, in seconds: a year.
17/// Later times are submitted at this delay.
18pub const MAX_DELAY_SECS: u64 = 365 * 24 * 60 * 60;
19
20/// Longest a mint's `Retry-After` makes a purse wait before a retry.
21const MAX_RETRY_AFTER: Duration = Duration::from_secs(30);
22
23/// A [`Holder`] connected to a server and a [`Store`]: it reads mint info
24/// itself, submits transfers at their scheduled times, retries what failed
25/// on the way, and saves after every change.
26///
27/// Nothing that depends on a change leaves the purse before the change is
28/// saved: a payment request's keys are saved before the request is
29/// returned, and the spent notes and change keys of a payment before the
30/// payment is. If a save fails, the change is undone and the call fails.
31///
32/// Methods take `&self`, so one purse can be shared between tasks; a
33/// refresh waiting for its schedule does not block payments. The holder is
34/// locked only while it changes, never across a request.
35///
36/// Several processes can share one store too. Before each change the purse
37/// takes the store's [lock](Store::lock) and reads the holder saved there,
38/// makes the change to that, and saves before letting go, so a note
39/// another process took in meanwhile is kept, not saved over. Reads such as
40/// [`holder`](Self::holder) show the holder as this purse last read or
41/// saved it.
42#[derive(Debug)]
43pub struct Purse<S: Store = FileStore> {
44    client: Client,
45    holder: Mutex<Holder>,
46    store: S,
47    retry: Retry,
48}
49
50/// How a purse resends a transfer that failed on the way: a lost
51/// connection, a timeout, or a mint that was paused, busy or could not
52/// store it. Resending is safe, since the mint answers an identical
53/// transfer with the identical notes, before or after a rotation, for as
54/// long as it remembers the transfer (see [`Purse::settle`]).
55///
56/// When the mint answers `busy`, the next wait is as long as its
57/// `Retry-After` asks, up to 30 seconds, if that is longer; the waits after
58/// it double from where they were. A `Retry-After` on any other answer,
59/// such as a proxy's, changes no wait. The mint answers `busy` only to a
60/// request it did not apply, so with the default and [`Client::new`]'s
61/// 30-second timeout the last try still ends within 127 seconds of any the
62/// mint may have applied, inside the 128 seconds a mint keeps each answer
63/// by default, and is answered as before.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Retry {
66    /// Tries in all, the first included.
67    pub attempts: u32,
68    /// Wait before the second try; it doubles before each further one.
69    pub first_delay: Duration,
70}
71
72impl Default for Retry {
73    /// Four tries, one, two and four seconds apart.
74    fn default() -> Self {
75        Self {
76            attempts: 4,
77            first_delay: Duration::from_secs(1),
78        }
79    }
80}
81
82/// What submitting a set of transfers came to.
83#[derive(Debug, Default)]
84pub struct Submitted {
85    /// Value the purse took in: payments to it and its own change.
86    pub value: u128,
87    /// Transfers the mint answered.
88    pub answered: usize,
89    /// Every note in the answers, this purse's own included.
90    pub notes: Vec<Note>,
91    /// The notes in the answers for neither a note this purse holds nor a
92    /// key it expects: the counterparty's (see [`Holder::theirs`]). Hand
93    /// them over for its [`Purse::accept`]: a payee hands the payer its
94    /// change, and a payer that submitted a payment hands the payee its
95    /// notes. The payer's purse keeps those for the payee too, until
96    /// [`forget_owed`](Purse::forget_owed) says they are handed over.
97    pub theirs: Vec<Note>,
98    /// Transfers the mint refused, or that kept failing on the way.
99    pub refused: Vec<Refused>,
100}
101
102/// A transfer that did not go through, and why.
103#[derive(Debug)]
104pub struct Refused {
105    /// The transfer.
106    pub request: TransferRequest,
107    /// The last error. [`Error::is_transient`] tells whether submitting it
108    /// again later may work; a refusal such as `spent`, `expired` or
109    /// `layer_full` will not.
110    pub error: Error,
111    /// For a transfer refused as `spent` or `expired` that this purse made
112    /// or is paid by, what it may lose ([`Holder::at_stake`]), as it stood
113    /// before the transfer was dropped: the transfer may have been applied,
114    /// with an answer the mint no longer keeps. Only a counterparty that
115    /// submitted it can then hand the notes over, for
116    /// [`accept`](Purse::accept). `None` otherwise.
117    pub at_stake: Option<u128>,
118    /// What the purse did with the transfer.
119    pub outcome: Outcome,
120}
121
122/// What a purse did with a transfer the mint refused.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum Outcome {
125    /// Nothing: a transfer this purse made stays outgoing, for the next
126    /// [`settle`](Purse::settle). So it is after a refusal that may pass
127    /// later, one [`Error::is_transient`] accepts or `frozen`, and after one
128    /// that shows nothing about the transfer: `unknown_mint` or `unissued`
129    /// from a server that is not the mint's or lost its state, `spent` or
130    /// `expired` with another status than the mint's 409, `layer_full` with
131    /// another than its 503, or `unauthorized` or `duplicate_input`, which
132    /// the mint never answers to a transfer a holder signed.
133    Kept,
134    /// Refused as `spent` or `expired`, with the mint's status 409: the note
135    /// it spends is gone, so the purse [forgot](Holder::forget) the
136    /// transfer. Change the counterparty hands over later is still taken in
137    /// by [`accept`](Purse::accept).
138    Dropped,
139    /// Refused as `layer_full`, with the mint's status 503, or as `policy`,
140    /// when the policy the mint publishes now refuses it too: the purse
141    /// [cancelled](Holder::cancel) the transfer and took its note back. The
142    /// mint checks the note before the layer the new notes would land in, so
143    /// after `layer_full` the note is live and the transfer was never
144    /// applied, and it never will be: that layer gets no bits back before
145    /// the note's own is dropped.
146    Cancelled,
147}
148
149impl Purse<FileStore> {
150    /// A purse saved in the file at `path`, created on the first save if it
151    /// does not exist yet.
152    pub fn open(client: Client, path: impl Into<PathBuf>) -> Result<Self, Error> {
153        let store = FileStore::new(path);
154        let holder = store.load()?;
155        Ok(Self::new(client, holder, store))
156    }
157}
158
159impl<S: Store> Purse<S> {
160    /// A purse holding `holder`, talking to `client` and saving to `store`.
161    pub fn new(client: Client, holder: Holder, store: S) -> Self {
162        Self {
163            client,
164            holder: Mutex::new(holder),
165            store,
166            retry: Retry::default(),
167        }
168    }
169
170    /// Sets how failed transfers are retried.
171    pub fn with_retry(mut self, retry: Retry) -> Self {
172        self.retry = retry;
173        self
174    }
175
176    /// The client requests go through.
177    pub fn client(&self) -> &Client {
178        &self.client
179    }
180
181    /// The store saves go to.
182    pub fn store(&self) -> &S {
183        &self.store
184    }
185
186    /// A copy of the holder as it is now.
187    pub fn holder(&self) -> Holder {
188        self.lock().clone()
189    }
190
191    /// Value held at `mint`, by what it can do now, from the mint's info.
192    pub async fn balance(&self, mint: &PublicKey) -> Result<Balance, Error> {
193        let view = self.client.view(mint).await?;
194        Ok(self.lock().balance(&view))
195    }
196
197    /// Asks for `amount` from `mint`: a payment request with a fresh key
198    /// per note, saved before it is returned. Send it to the payer.
199    pub async fn request(&self, mint: &PublicKey, amount: u64) -> Result<PaymentRequest, Error> {
200        let view = self.client.view(mint).await?;
201        self.change(
202            |holder| Ok(holder.request(&view, amount)?),
203            |holder, request| holder.withdraw(request),
204        )
205    }
206
207    /// Like [`request`](Self::request), in notes of at most `largest`, for a
208    /// payer whose notes are small.
209    pub async fn request_at_most(
210        &self,
211        mint: &PublicKey,
212        amount: u64,
213        largest: u64,
214    ) -> Result<PaymentRequest, Error> {
215        let view = self.client.view(mint).await?;
216        self.change(
217            |holder| Ok(holder.request_at_most(&view, amount, largest)?),
218            |holder, request| holder.withdraw(request),
219        )
220    }
221
222    /// Asks a playground faucet for a note and keeps it. The faucet's mint
223    /// must be on this purse's server; the faucet may share its URL. The
224    /// note goes to a fresh key, saved before the faucet is asked, as for
225    /// any payment request. Returns the faucet's mint and the value kept.
226    pub async fn take_from_faucet(&self, faucet: &Client) -> Result<(PublicKey, u128), Error> {
227        let about = faucet.faucet().await?;
228        let request = self
229            .request_at_most(&about.mint, about.amount, about.amount)
230            .await?;
231        let [output] = request.outputs.as_slice() else {
232            self.withdraw(&request)?;
233            return Err(Error::FaucetAmount(about.amount));
234        };
235        let taken = match faucet.ask_faucet(&output.holder).await {
236            Ok(note) => self.accept(&[note]).await,
237            Err(error) => Err(error),
238        };
239        if taken.is_err() {
240            self.withdraw(&request)?;
241        }
242        Ok((about.mint, taken?))
243    }
244
245    /// Stops expecting the notes of a request that will not be paid.
246    pub fn withdraw(&self, request: &PaymentRequest) -> Result<(), Error> {
247        self.change(
248            |holder| {
249                holder.withdraw(request);
250                Ok(())
251            },
252            |_, ()| {},
253        )
254    }
255
256    /// Answers a payment request, after reading the mint's info. The notes
257    /// spent, the keys for the change and the payment itself (see
258    /// [`Holder::payments`]) are saved before the payment is returned. Send
259    /// it to the payee, who submits it and can hand back the change for
260    /// [`accept`](Self::accept); or [`settle`](Self::settle) later to take it
261    /// in. A request for a note the mint would not make, or to a key this
262    /// purse has paid already, is refused before anything is signed (see
263    /// [`Holder::pay`]).
264    pub async fn pay(&self, request: &PaymentRequest, spacing: Spacing) -> Result<Payment, Error> {
265        let view = self.client.view(&request.mint).await?;
266        self.change_or_restore(|holder| Ok(holder.pay(&view, request, spacing)?))
267    }
268
269    /// Takes back the note of an outgoing transfer the mint never applied,
270    /// keeping the keys of its change in case it did, and saves (see
271    /// [`Holder::cancel`]). Returns whether the transfer was outgoing.
272    ///
273    /// A payee that holds the transfer can still submit it: if the mint
274    /// applies it, or already had, the payee's notes and the note taken back
275    /// claim the same value, and the mint refuses whichever is spent second.
276    pub fn cancel(&self, request: &TransferRequest) -> Result<bool, Error> {
277        self.change_or_restore(|holder| Ok(holder.cancel(request)))
278    }
279
280    /// Drops an outgoing transfer whose note is gone, keeping the keys of its
281    /// change so that change handed over later is still taken in, and saves
282    /// (see [`Holder::forget`]). Returns whether the transfer was outgoing.
283    pub fn forget(&self, request: &TransferRequest) -> Result<bool, Error> {
284        self.change_or_restore(|holder| Ok(holder.forget(request)))
285    }
286
287    /// Stops keeping notes owed to payees, once they are handed over, and
288    /// saves (see [`Holder::forget_owed`]). The notes of a payment are in
289    /// [`Holder::payments`]: write them where the payee gets them, then
290    /// forget them. Returns the value it stopped keeping.
291    pub fn forget_owed(&self, notes: &[Note]) -> Result<u128, Error> {
292        self.change_or_restore(|holder| Ok(holder.forget_owed(notes)))
293    }
294
295    /// Takes a payment for `request`: checks that it pays what was asked,
296    /// then submits each transfer at its time and takes in the notes.
297    /// Returns once the last transfer is answered or refused. The payer
298    /// chose the times; wrap the call in a timeout to bound the wait. Hand
299    /// [`Submitted::theirs`], the payer's change, back to the payer.
300    pub async fn receive(
301        &self,
302        request: &PaymentRequest,
303        payment: &Payment,
304    ) -> Result<Submitted, Error> {
305        request.check(payment)?;
306        self.submit(&payment.transfers).await
307    }
308
309    /// Takes in notes the counterparty handed over from the mint's answers,
310    /// after checking each one's signature against its mint's keys, and
311    /// settles the outgoing transfers they answer (see
312    /// [`Holder::take_answers`]). Notes for keys this purse never handed out
313    /// are ignored. A note whose layer the mint has dropped no longer
314    /// verifies and fails the call. Returns the value taken in.
315    ///
316    /// The signatures show that the mint issued the notes, not that it
317    /// applied the transfers they settle: a counterparty could pay the same
318    /// keys from notes of its own (see [`Holder::take_answers`]).
319    pub async fn accept(&self, notes: &[Note]) -> Result<u128, Error> {
320        let mut keys: HashMap<PublicKey, KeySet> = HashMap::new();
321        for note in notes {
322            if !keys.contains_key(note.issuer()) {
323                let set = self.client.keys(note.issuer()).await?;
324                keys.insert(*note.issuer(), set);
325            }
326            note.verify(&keys[note.issuer()])?;
327        }
328        self.change(|holder| Ok(holder.take_answers(notes)), |_, _| {})
329    }
330
331    /// Resends the transfers this purse made and has not seen answered, at
332    /// random gaps of `spacing`, and takes in their change. Call it once a
333    /// payee has had time to submit a payment, or after a restart. Planned
334    /// refreshes still waiting for their time are resent too. The answer to
335    /// a payment's transfer keeps the payee's notes for it, in case the payee
336    /// has not submitted the payment and the resend carried it out (see
337    /// [`MadePayment::owed`](worgl_sdk::MadePayment::owed)), and a transfer
338    /// refused for good is dropped or cancelled (see [`Refused::outcome`]).
339    ///
340    /// The mint answers a resent transfer with the same notes only while it
341    /// keeps its answer, and at the latest until the notes it issued expire
342    /// (see [Error codes]). After that, a transfer already applied is
343    /// refused as `spent` or `expired`, with [`Refused::at_stake`] set, and
344    /// its change can only come from the counterparty, for
345    /// [`accept`](Self::accept). [`refresh`](Self::refresh) resends the
346    /// transfers whose chance the next rotation ends.
347    ///
348    /// [Error codes]: https://github.com/zhangheqi/worgl/blob/main/site/src/content/docs/docs/error-codes.md#sending-a-request-again
349    pub async fn settle(&self, spacing: Spacing) -> Result<Submitted, Error> {
350        let transfers = self.lock().resend(spacing);
351        self.submit(&transfers).await
352    }
353
354    /// Plans the refresh of `mint`'s notes that die at the next rotation,
355    /// after reading its info, and saves the plan. The outgoing transfers
356    /// to resend before that rotation join it (see
357    /// [`Holder::due_resends`]); if the save fails, only the refreshes are
358    /// undone. Submit the transfers at their times with
359    /// [`submit`](Self::submit).
360    ///
361    /// The times are planned by the server's clock, from the `Date` of its
362    /// info answer ([`Client::view_and_time`]), so this machine's clock,
363    /// slow or fast, moves none of them past the rotation. When the answer
364    /// has no `Date`, they are planned by this machine's clock instead. Once
365    /// the rotation's time has come and the mint has not made it yet, they
366    /// are all due at once.
367    pub async fn plan_refresh(&self, mint: &PublicKey) -> Result<Vec<ScheduledTransfer>, Error> {
368        let (view, now) = self.client.view_and_time(mint).await?;
369        let (mut transfers, resends) = self.change_or_restore(|holder| {
370            // Before the refreshes join the outgoing transfers.
371            let resends = holder.due_resends(&view, now);
372            Ok((holder.refresh(&view, now), resends))
373        })?;
374        transfers.extend(resends);
375        transfers.sort_by_key(|t| t.after_secs);
376        Ok(transfers)
377    }
378
379    /// Refreshes `mint`'s notes that die at the next rotation and resends
380    /// the outgoing transfers that must land before it: plans the transfers
381    /// with [`plan_refresh`](Self::plan_refresh) and submits each at its
382    /// random time before the rotation. Returns once the last one is done,
383    /// which may be most of a rotation period away; run it in a task of its
384    /// own.
385    pub async fn refresh(&self, mint: &PublicKey) -> Result<Submitted, Error> {
386        let transfers = self.plan_refresh(mint).await?;
387        self.submit(&transfers).await
388    }
389
390    /// Drops `mint`'s notes whose layer was dropped, after reading its info,
391    /// and returns the value lost. It also stops waiting for unconfirmed
392    /// change that would sit in a dropped layer, and stops keeping notes
393    /// owed to payees once their layer is dropped (see [`Holder::prune`]).
394    /// The info alone decides, not this machine's clock: a note goes, with
395    /// its key, only once the mint has dropped its layer.
396    pub async fn prune(&self, mint: &PublicKey) -> Result<u128, Error> {
397        let view = self.client.view(mint).await?;
398        self.change(|holder| Ok(holder.prune(&view)), |_, _| {})
399    }
400
401    /// Submits each transfer at its time, counted from now, retrying
402    /// failures on the way, and records each answer: the transfer stops
403    /// being outgoing and the notes for this purse are taken in, and the
404    /// payee's notes are kept for it when the transfer is part of a payment
405    /// this purse made (see [`Holder::settle`]). A payer that submits its own
406    /// payment hands them to the payee from [`Submitted::theirs`], then
407    /// calls [`forget_owed`](Self::forget_owed) with them, as `worgl submit`
408    /// does.
409    ///
410    /// A refused transfer does not stop the others; it is listed in
411    /// [`Submitted::refused`]. One this purse made that the mint refused for
412    /// good is dropped or cancelled ([`Refused::outcome`]); the rest stay
413    /// outgoing. To confirm a `policy` refusal, the purse reads the mint's
414    /// info again. The call fails only if a save does.
415    pub async fn submit(&self, transfers: &[ScheduledTransfer]) -> Result<Submitted, Error> {
416        let mut order: Vec<&ScheduledTransfer> = transfers.iter().collect();
417        order.sort_by_key(|transfer| transfer.after_secs);
418        let start = Instant::now();
419        let mut submitted = Submitted::default();
420        for transfer in order {
421            let delay = Duration::from_secs(transfer.after_secs.min(MAX_DELAY_SECS));
422            sleep_until(start + delay).await;
423            match self.send(&transfer.request).await {
424                Ok(notes) => {
425                    let (value, theirs) = self.change(
426                        |holder| {
427                            let value = holder.settle(&transfer.request, &notes);
428                            Ok((value, holder.theirs(&notes)))
429                        },
430                        |_, _| {},
431                    )?;
432                    submitted.value += value;
433                    submitted.answered += 1;
434                    submitted.notes.extend(notes);
435                    submitted.theirs.extend(theirs);
436                }
437                Err(error) => {
438                    let (at_stake, outcome) = self.refused(&transfer.request, &error).await?;
439                    submitted.refused.push(Refused {
440                        request: transfer.request.clone(),
441                        error,
442                        at_stake,
443                        outcome,
444                    });
445                }
446            }
447        }
448        Ok(submitted)
449    }
450
451    /// Drops or cancels a transfer the mint refused, when the refusal shows
452    /// it can never be applied, and returns what is at stake on it and what
453    /// was done. A transfer that is not outgoing is left as it is.
454    async fn refused(
455        &self,
456        request: &TransferRequest,
457        error: &Error,
458    ) -> Result<(Option<u128>, Outcome), Error> {
459        let cancel = match error.code() {
460            Some("spent" | "expired") => {
461                // The mint answers both with 409 Conflict. Any other answer,
462                // such as a gateway's, shows nothing about the note.
463                if !matches!(error, Error::Refused(refusal) if refusal.status == 409) {
464                    return Ok((self.lock().at_stake(request), Outcome::Kept));
465                }
466                return self.change_or_restore(|holder| {
467                    // Before the transfer is dropped.
468                    let at_stake = holder.at_stake(request);
469                    let outcome = if holder.forget(request) {
470                        Outcome::Dropped
471                    } else {
472                        Outcome::Kept
473                    };
474                    Ok((at_stake, outcome))
475                });
476            }
477            // The mint answers so with 503, after finding every note spent
478            // live. Any other answer shows nothing about the notes.
479            Some("layer_full") => {
480                matches!(error, Error::Refused(refusal) if refusal.status == 503)
481            }
482            // The mint checks its policy before its receipts, so a transfer
483            // applied under a looser policy is refused too: its change keys
484            // stay. Cancel only if the policy published now agrees.
485            Some("policy") => match request.inputs.first() {
486                Some(spend) => match self.client.view(spend.note.issuer()).await {
487                    Ok(view) => {
488                        let inputs: Vec<u64> =
489                            request.inputs.iter().map(|s| s.note.amount()).collect();
490                        let outputs: Vec<u64> = request.outputs.iter().map(|o| o.amount).collect();
491                        view.policy.check_transfer(&inputs, &outputs).is_err()
492                    }
493                    Err(_) => false,
494                },
495                None => false,
496            },
497            // Anything else may pass later or shows nothing about the
498            // transfer: `unknown_mint` or `unissued` from a server that is
499            // not the mint's or lost its state, or `unauthorized` or
500            // `duplicate_input`, which the mint never answers to a transfer
501            // a holder signed, so they come from another server or version.
502            _ => false,
503        };
504        if !cancel {
505            return Ok((None, Outcome::Kept));
506        }
507        let cancelled = self.change_or_restore(|holder| Ok(holder.cancel(request)))?;
508        let outcome = if cancelled {
509            Outcome::Cancelled
510        } else {
511            Outcome::Kept
512        };
513        Ok((None, outcome))
514    }
515
516    /// Sends one transfer, retrying while the failure is transient.
517    async fn send(&self, request: &TransferRequest) -> Result<Vec<Note>, Error> {
518        let mut delay = self.retry.first_delay;
519        let mut attempt = 1;
520        loop {
521            match self.client.transfer(request).await {
522                Err(error) if error.is_transient() && attempt < self.retry.attempts => {
523                    sleep(next_wait(delay, &error)).await;
524                    delay = delay.saturating_mul(2);
525                    attempt += 1;
526                }
527                result => return result,
528            }
529        }
530    }
531
532    /// Like [`change`](Self::change), but a failed save puts the holder back
533    /// as it was before `apply`.
534    fn change_or_restore<T>(
535        &self,
536        apply: impl FnOnce(&mut Holder) -> Result<T, Error>,
537    ) -> Result<T, Error> {
538        let (value, _) = self.change(
539            |holder| {
540                let before = holder.clone();
541                Ok((apply(holder)?, before))
542            },
543            |holder, (_, before)| *holder = before.clone(),
544        )?;
545        Ok(value)
546    }
547
548    /// Changes the holder as saved now and saves it, holding the store's
549    /// lock throughout. If the save fails, `undo` takes the change back and
550    /// the save's error is returned.
551    fn change<T>(
552        &self,
553        apply: impl FnOnce(&mut Holder) -> Result<T, Error>,
554        undo: impl FnOnce(&mut Holder, &T),
555    ) -> Result<T, Error> {
556        let mut holder = self.lock();
557        // Held until the save is done.
558        let locked = self.store.lock()?;
559        if let Some(saved) = locked.as_ref().and_then(Locked::saved) {
560            *holder = Holder::from_json(saved)?;
561        }
562        let value = apply(&mut holder)?;
563        if let Err(error) = self.store.save(&holder.to_json()) {
564            undo(&mut holder, &value);
565            return Err(error.into());
566        }
567        Ok(value)
568    }
569
570    fn lock(&self) -> MutexGuard<'_, Holder> {
571        // A panic in another task holding the lock should not take the
572        // purse down with it.
573        self.holder.lock().unwrap_or_else(|e| e.into_inner())
574    }
575}
576
577/// The wait before the next try after `error`: `delay`, or after `busy`
578/// what the mint's `Retry-After` asks if that is longer, up to
579/// [`MAX_RETRY_AFTER`]. It stretches this wait only, never the ones after.
580///
581/// Only `busy` stretches it: the mint answers so only to a request it did
582/// not apply. Another answer, such as a proxy's, may come after the mint
583/// applied the try, and longer waits could take the last try past the
584/// time the mint keeps the receipt for.
585fn next_wait(delay: Duration, error: &Error) -> Duration {
586    match error.retry_after() {
587        Some(asked) if error.code() == Some("busy") => delay.max(asked.min(MAX_RETRY_AFTER)),
588        _ => delay,
589    }
590}