From 98a4fb507353e2c592dda024a922983fbf8ffa65 Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Thu, 26 Mar 2026 16:16:35 +0100 Subject: [PATCH] Fix race condition in revert transaction, causing aceess to dead mobx proof models. Patch hotUpdater --- patches/@hot-updater+bare+0.20.11.patch | 13 ++++++++++ src/screens/TranDetailScreen.tsx | 2 -- src/services/walletService.ts | 34 ++++++++++++++----------- 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 patches/@hot-updater+bare+0.20.11.patch diff --git a/patches/@hot-updater+bare+0.20.11.patch b/patches/@hot-updater+bare+0.20.11.patch new file mode 100644 index 0000000..507ee87 --- /dev/null +++ b/patches/@hot-updater+bare+0.20.11.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@hot-updater/bare/dist/index.cjs b/node_modules/@hot-updater/bare/dist/index.cjs +index 2476539..bc47c39 100644 +--- a/node_modules/@hot-updater/bare/dist/index.cjs ++++ b/node_modules/@hot-updater/bare/dist/index.cjs +@@ -6528,6 +6528,8 @@ async function getHermesCommand(cwd) { + if (fileExists(bundledHermesEngine)) return bundledHermesEngine; + const hermesEngine = path.default.join("node_modules", "hermes-engine", getHermesOSBin(), getHermesOSExe()); + if (fileExists(hermesEngine)) return hermesEngine; ++ const hermesCompiler = path.default.join("node_modules", "hermes-compiler", "hermesc", getHermesOSBin(), getHermesOSExe()); ++ if (fileExists(hermesCompiler)) return hermesCompiler; + return path.default.join("node_modules", "hermesvm", getHermesOSBin(), "hermes"); + } + /** diff --git a/src/screens/TranDetailScreen.tsx b/src/screens/TranDetailScreen.tsx index c056cd4..643bf05 100644 --- a/src/screens/TranDetailScreen.tsx +++ b/src/screens/TranDetailScreen.tsx @@ -56,8 +56,6 @@ import { CommonActions, StaticScreenProps, useFocusEffect, useNavigation } from import { QRCodeBlock } from './Wallet/QRCode' import { MintListItem } from './Mints/MintListItem' import { Token, getDecodedToken } from '@cashu/cashu-ts' -import { RECEIVE_OFFLINE_COMPLETE_TASK, RECEIVE_TASK } from '../services/wallet/receiveTask' -import { REVERT_TASK } from '../services/wallet/revertTask' import FastImage from 'react-native-fast-image' import { MintHeader } from './Mints/MintHeader' import { TransferOption } from './TransferScreen' diff --git a/src/services/walletService.ts b/src/services/walletService.ts index 1d08f76..6708794 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -1307,25 +1307,29 @@ const syncStateWithMintTask = async function ( const revertedTxIds: number[] = [] try { - if (proofsToSync.length === 0) { + // Filter out proof nodes that were removed from the MST between the time + // the task was queued and when it actually runs (e.g. revertTask ran first). + const aliveProofs = proofsToSync.filter(p => isAlive(p)) + + if (aliveProofs.length === 0) { const message = `No ${isPending ? 'pending ' : ''}proofs to sync with mint` log.trace('[syncStateWithMintTask]', message) - return { - taskFunction: SYNC_STATE_WITH_MINT_TASK, - mintUrl, - message, - transactionStateUpdates, - completedTransactionIds: [], - errorTransactionIds: [], - revertedTransactionIds: [] + return { + taskFunction: SYNC_STATE_WITH_MINT_TASK, + mintUrl, + message, + transactionStateUpdates, + completedTransactionIds: [], + errorTransactionIds: [], + revertedTransactionIds: [] } } - + // 1. Ask mint what it thinks about these proofs const statesFromMint = await walletStore.getProofsStatesFromMint( mintUrl, mint?.units?.[0] ?? 'sat', - proofsToSync + aliveProofs ) if (mint) mint.setStatus(MintStatus.ONLINE) @@ -1366,7 +1370,7 @@ const syncStateWithMintTask = async function ( // 1. Proofs now SPENT at mint → transaction succeeded // ───────────────────────────────────────────────────────────── if (secrets.spent.size > 0) { - const spentProofs = proofsToSync.filter(p => secrets.spent.has(p.secret)) + const spentProofs = aliveProofs.filter(p => secrets.spent.has(p.secret)) const spentByTx = groupByTId(spentProofs) proofsStore.moveToSpent(spentProofs) // sets isSpent = true, isPending = false + clean if they were in pendingByMintSecrets @@ -1395,7 +1399,7 @@ const syncStateWithMintTask = async function ( // If we were checking pending proofs, move unspent ones back if (isPending) { - const stillUnspent = proofsToSync.filter(p => secrets.unspent.has(p.secret)) + const stillUnspent = aliveProofs.filter(p => secrets.unspent.has(p.secret)) proofsStore.revertToSpendable(stillUnspent) } } else if (tx.status !== TransactionStatus.REVERTED) { @@ -1454,7 +1458,7 @@ const syncStateWithMintTask = async function ( // 2. Proofs still PENDING at mint → keep pending in wallet // ───────────────────────────────────────────────────────────── if (secrets.pending.size > 0) { - const newPendingProofs = proofsToSync.filter(p => secrets.pending.has(p.secret) && !proofsStore.pendingByMintSecrets.includes(p.secret)) + const newPendingProofs = aliveProofs.filter(p => secrets.pending.has(p.secret) && !proofsStore.pendingByMintSecrets.includes(p.secret)) if (newPendingProofs.length > 0) { proofsStore.registerAsPendingAtMint(newPendingProofs) @@ -1540,7 +1544,7 @@ const syncStateWithMintTask = async function ( return { taskFunction: SYNC_STATE_WITH_MINT_TASK, mintUrl, - message: `Sync completed for ${proofsToSync.length} ${isPending ? 'pending ' : ''}proofs`, + message: `Sync completed for ${aliveProofs.length} ${isPending ? 'pending ' : ''}proofs`, transactionStateUpdates, completedTransactionIds: completedTxIds, errorTransactionIds: errorTxIds,