harden proxy auth and event ordering
This commit is contained in:
+128
-38
@@ -361,6 +361,8 @@ pub(crate) struct JobCtx<S> {
|
||||
pub(crate) client: Client,
|
||||
/// Relay token events at schedule time.
|
||||
pub(crate) tokens: HashMap<EventId, TokenEvent>,
|
||||
/// A previous job errored after possibly mutating the store
|
||||
pub(crate) publish_failed: bool,
|
||||
}
|
||||
|
||||
impl<S> JobCtx<S>
|
||||
@@ -426,35 +428,14 @@ where
|
||||
log::warn!("saga recovery failed for {mint_url}: {error:#}");
|
||||
}
|
||||
|
||||
let minted = wallet.mint_unissued_quotes().await?;
|
||||
if minted == Amount::ZERO {
|
||||
continue;
|
||||
}
|
||||
let local_ys = proof_ys(wallet.get_unspent_proofs().await?.iter())?;
|
||||
let states = vec![State::Pending, State::Reserved, State::PendingSpent];
|
||||
|
||||
let update = self
|
||||
.sync_token_events(
|
||||
wallet,
|
||||
nostr_mint(mint_url)?,
|
||||
&previous_ids(&tokens, mint_url),
|
||||
Some(keys::History {
|
||||
direction: TransactionDirection::In,
|
||||
amount: u64::from(minted),
|
||||
redeemed: Vec::new(),
|
||||
sender: None,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
let in_flight = wallet.get_proofs_by_states(states).await?;
|
||||
let in_flight_ys = proof_ys(in_flight.iter())?;
|
||||
|
||||
for id in &update.destroyed {
|
||||
tokens.remove(id);
|
||||
}
|
||||
tokens.insert(update.event_id, update.token.clone());
|
||||
updates.push(update);
|
||||
}
|
||||
|
||||
for (mint_url, wallet) in &self.wallets {
|
||||
let mut relay_ys = HashSet::new();
|
||||
let mut added = Vec::new();
|
||||
let mut unseen = Vec::new();
|
||||
|
||||
for token in tokens
|
||||
.values()
|
||||
@@ -468,23 +449,104 @@ where
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if relay_ys.insert(info.y) {
|
||||
added.push(info);
|
||||
|
||||
if !relay_ys.insert(info.y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if local_ys.contains(&info.y) || in_flight_ys.contains(&info.y) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unseen.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
let unspent = wallet.get_unspent_proofs().await?;
|
||||
let removed = unspent
|
||||
.iter()
|
||||
.filter(|proof| match proof.y() {
|
||||
Ok(y) => !relay_ys.contains(&y),
|
||||
Err(_) => false,
|
||||
})
|
||||
.map(|proof| proof.y())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if unseen.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
wallet.localstore.update_proofs(added, removed).await?;
|
||||
let unseen_proofs: Proofs = unseen.iter().map(|info| info.proof.clone()).collect();
|
||||
|
||||
let spent = match wallet.check_proofs_spent(unseen_proofs).await {
|
||||
Ok(states) => states
|
||||
.into_iter()
|
||||
.filter(|state| state.state == State::Spent)
|
||||
.map(|state| state.y)
|
||||
.collect::<HashSet<_>>(),
|
||||
Err(error) => {
|
||||
log::warn!("mint state check failed, accepting unseen proofs: {error:#}");
|
||||
HashSet::new()
|
||||
}
|
||||
};
|
||||
|
||||
let added: Vec<ProofInfo> = unseen
|
||||
.into_iter()
|
||||
.filter(|info| !spent.contains(&info.y))
|
||||
.collect();
|
||||
|
||||
if !added.is_empty() {
|
||||
wallet.localstore.update_proofs(added, Vec::new()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
for (mint_url, wallet) in &self.wallets {
|
||||
let minted = wallet.mint_unissued_quotes().await?;
|
||||
if minted != Amount::ZERO {
|
||||
let update = self
|
||||
.sync_token_events(
|
||||
wallet,
|
||||
nostr_mint(mint_url)?,
|
||||
&previous_ids(&tokens, mint_url),
|
||||
Some(keys::History {
|
||||
direction: TransactionDirection::In,
|
||||
amount: u64::from(minted),
|
||||
redeemed: Vec::new(),
|
||||
sender: None,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for id in &update.destroyed {
|
||||
tokens.remove(id);
|
||||
}
|
||||
tokens.insert(update.event_id, update.token.clone());
|
||||
updates.push(update);
|
||||
}
|
||||
|
||||
let relay_ys = relay_proof_ys(&tokens, mint_url);
|
||||
let local_ys = proof_ys(wallet.get_unspent_proofs().await?.iter())?;
|
||||
|
||||
if local_ys != relay_ys {
|
||||
if self.publish_failed {
|
||||
let update = self
|
||||
.sync_token_events(
|
||||
wallet,
|
||||
nostr_mint(mint_url)?,
|
||||
&previous_ids(&tokens, mint_url),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
for id in &update.destroyed {
|
||||
tokens.remove(id);
|
||||
}
|
||||
tokens.insert(update.event_id, update.token.clone());
|
||||
updates.push(update);
|
||||
} else {
|
||||
let removed = wallet
|
||||
.get_unspent_proofs()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|proof| match proof.y() {
|
||||
Ok(y) => !relay_ys.contains(&y),
|
||||
Err(_) => false,
|
||||
})
|
||||
.map(|proof| proof.y())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
wallet.localstore.update_proofs(Vec::new(), removed).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Outcome::Reconciled { updates })
|
||||
@@ -499,6 +561,34 @@ fn previous_ids(tokens: &HashMap<EventId, TokenEvent>, mint: &MintUrl) -> Vec<Ev
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn proof_ys<'a>(
|
||||
proofs: impl Iterator<Item = &'a Proof>,
|
||||
) -> Result<HashSet<cdk::nuts::PublicKey>, Error> {
|
||||
proofs.map(|proof| proof.y().map_err(Error::from)).collect()
|
||||
}
|
||||
|
||||
/// Proof ys referenced by the relay token events of one mint
|
||||
fn relay_proof_ys(
|
||||
tokens: &HashMap<EventId, TokenEvent>,
|
||||
mint: &MintUrl,
|
||||
) -> HashSet<cdk::nuts::PublicKey> {
|
||||
let mut ys = HashSet::new();
|
||||
for token in tokens
|
||||
.values()
|
||||
.filter(|token| token.mint.as_str() == mint.to_string())
|
||||
{
|
||||
for proof in &token.proofs {
|
||||
match proof_info(proof, mint) {
|
||||
Ok(info) => {
|
||||
ys.insert(info.y);
|
||||
}
|
||||
Err(error) => log::warn!("skipping proof: {error:#}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
ys
|
||||
}
|
||||
|
||||
/// Resolve a recipient's 10019 wallet info and pick a mint we share with them.
|
||||
async fn recipient_wallet_info<S>(
|
||||
ctx: &JobCtx<S>,
|
||||
|
||||
Reference in New Issue
Block a user