Skip to content

Commit 9505e25

Browse files
committed
fix: guard trezor broadcast failure
1 parent 1ecae3d commit 9505e25

2 files changed

Lines changed: 55 additions & 6 deletions

File tree

app/src/main/java/to/bitkit/ui/screens/trezor/TrezorViewModel.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,10 +381,16 @@ class TrezorViewModel @Inject constructor(
381381
}
382382
ToastEventBus.send(type = Toast.ToastType.SUCCESS, title = "Transaction broadcast")
383383
}
384-
.onFailure {
385-
TrezorDebugLog.log("BROADCAST", "FAILED: ${it.message}")
386-
_uiState.update { it.copy(send = it.send.copy(isBroadcasting = false)) }
387-
ToastEventBus.send(it)
384+
.onFailure { error ->
385+
TrezorDebugLog.log("BROADCAST", "FAILED: ${error.message}")
386+
if (_uiState.value.send.step != signedStep) return@onFailure
387+
388+
_uiState.update {
389+
if (it.send.step != signedStep) return@update it
390+
391+
it.copy(send = it.send.copy(isBroadcasting = false))
392+
}
393+
ToastEventBus.send(error)
388394
}
389395
}
390396
}

app/src/test/java/to/bitkit/ui/screens/trezor/TrezorViewModelTest.kt

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package to.bitkit.ui.screens.trezor
22

3+
import com.synonym.bitkitcore.TrezorSignedTx
34
import kotlinx.coroutines.CompletableDeferred
45
import kotlinx.coroutines.ExperimentalCoroutinesApi
56
import kotlinx.coroutines.flow.MutableStateFlow
@@ -267,6 +268,46 @@ class TrezorViewModelTest : BaseUnitTest() {
267268
assertNull(state.broadcastTxid)
268269
}
269270

271+
@Test
272+
fun `broadcastSignedTx failure should not clear newer broadcast`() = test {
273+
loadSignedTx()
274+
val firstBroadcastResult = CompletableDeferred<Result<String>>()
275+
val secondBroadcastResult = CompletableDeferred<Result<String>>()
276+
val broadcastResults = ArrayDeque(
277+
listOf(firstBroadcastResult, secondBroadcastResult)
278+
)
279+
whenever(trezorRepo.broadcastRawTx(any(), any()))
280+
.doSuspendableAnswer { broadcastResults.removeFirst().await() }
281+
282+
sut.broadcastSignedTx()
283+
assertTrue(sut.uiState.value.isBroadcasting)
284+
285+
val secondSignedTx = TrezorSignedTx(
286+
signatures = listOf("30440220new"),
287+
serializedTx = "0200000001new",
288+
txid = "new-broadcast-txid",
289+
)
290+
sut.resetSendFlow()
291+
loadSignedTx(secondSignedTx)
292+
sut.broadcastSignedTx()
293+
assertTrue(sut.uiState.value.isBroadcasting)
294+
295+
firstBroadcastResult.complete(Result.failure(RuntimeException("first failed")))
296+
advanceUntilIdle()
297+
298+
val staleFailureState = sut.uiState.value
299+
assertEquals(secondSignedTx, staleFailureState.signedTxResult)
300+
assertTrue(staleFailureState.isBroadcasting)
301+
assertNull(staleFailureState.broadcastTxid)
302+
303+
secondBroadcastResult.complete(Result.success("second-broadcast-txid"))
304+
advanceUntilIdle()
305+
306+
val finalState = sut.uiState.value
307+
assertFalse(finalState.isBroadcasting)
308+
assertEquals("second-broadcast-txid", finalState.broadcastTxid)
309+
}
310+
270311
@Test
271312
fun `composeTx should not call repo when destination address is blank`() = test {
272313
loadAccountInfo()
@@ -346,12 +387,14 @@ class TrezorViewModelTest : BaseUnitTest() {
346387
advanceUntilIdle()
347388
}
348389

349-
private suspend fun TestScope.loadSignedTx() {
390+
private suspend fun TestScope.loadSignedTx(
391+
signedTx: TrezorSignedTx = TrezorPreviewData.sampleSignedTx,
392+
) {
350393
loadAccountInfo()
351394
whenever(trezorRepo.composeTransaction(any(), any(), any(), any(), anyOrNull(), any()))
352395
.thenReturn(Result.success(listOf(TrezorPreviewData.sampleComposeResult)))
353396
whenever(trezorRepo.signTxFromPsbt(any(), anyOrNull()))
354-
.thenReturn(Result.success(TrezorPreviewData.sampleSignedTx))
397+
.thenReturn(Result.success(signedTx))
355398

356399
sut.setSendAddress("bc1qtest123")
357400
sut.setSendAmount("1000")

0 commit comments

Comments
 (0)