Skip to content

Commit c49b296

Browse files
fix: prevent topup max from creating invalid transactions (#1442)
* fix: log tx data for debugging * fix: properly handle max topup
1 parent 5ae3114 commit c49b296

File tree

3 files changed

+30
-7
lines changed

3 files changed

+30
-7
lines changed

wallet/src/de/schildbach/wallet/payments/SendCoinsTaskRunner.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ class SendCoinsTaskRunner @Inject constructor(
463463
metadataProvider.setTransactionService(sendRequest.tx.txId, serviceName)
464464
}
465465
log.info("send successful, transaction committed: {}", transaction.txId.toString())
466+
log.info(" transaction: {}", transaction.toStringHex())
466467
walletApplication.broadcastTransaction(transaction)
467468
logSendTxEvent(transaction, wallet)
468469
transaction

wallet/src/de/schildbach/wallet/ui/send/BuyCreditsFragment.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ class BuyCreditsFragment : SendCoinsFragment() {
131131
// TODO: there are no events for Topups
132132
// viewModel.logEvent(AnalyticsConstants.Topup.ENTER_AMOUNT_TOPUP)
133133

134-
if (enterAmountFragment?.maxSelected == true) {
134+
val maxSelected = enterAmountFragment?.maxSelected ?: false
135+
if (maxSelected) {
135136
viewModel.logEvent(AnalyticsConstants.SendReceive.ENTER_AMOUNT_MAX)
136137
}
137-
138138
// buy do an asset lock transaction or we do this in the worker?
139-
val topUpKey = viewModel.getNextTopupKey()
140-
val tx = viewModel.signAndSendAssetLock(editedAmount, exchangeRate, checkBalance, topUpKey)
139+
val topUpKey = viewModel.getNextKey()
140+
val tx = viewModel.signAndSendAssetLock(editedAmount, exchangeRate, checkBalance, topUpKey, maxSelected)
141141
buyCreditsViewModel.topUpTransaction = tx
142142

143143
onSignAndSendPaymentSuccess(tx)
@@ -150,6 +150,7 @@ class BuyCreditsFragment : SendCoinsFragment() {
150150
} catch (ex: InsufficientMoneyException) {
151151
showInsufficientMoneyDialog(ex.missing ?: Coin.ZERO)
152152
} catch (ex: KeyCrypterException) {
153+
log.info("send topup failure (encryption)", ex)
153154
showFailureDialog(ex)
154155
} catch (ex: Wallet.CouldNotAdjustDownwards) {
155156
showEmptyWalletFailedDialog()

wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import org.bitcoinj.core.Context
5151
import org.bitcoinj.core.ECKey
5252
import org.bitcoinj.core.InsufficientMoneyException
5353
import org.bitcoinj.core.Transaction
54+
import org.bitcoinj.evolution.AssetLockTransaction
5455
import org.bitcoinj.utils.ExchangeRate
5556
import org.bitcoinj.wallet.AuthenticationKeyChain
5657
import org.bitcoinj.wallet.SendRequest
@@ -261,7 +262,8 @@ class SendCoinsViewModel @Inject constructor(
261262
editedAmount: Coin,
262263
exchangeRate: ExchangeRate?,
263264
checkBalance: Boolean,
264-
key: ECKey
265+
key: ECKey,
266+
emptyWallet: Boolean
265267
): Transaction {
266268
_state.value = State.SENDING
267269
if (!isAssetLock) {
@@ -270,7 +272,7 @@ class SendCoinsViewModel @Inject constructor(
270272
val finalPaymentIntent = basePaymentIntent.mergeWithEditedValues(editedAmount, null)
271273

272274
val transaction = try {
273-
val finalSendRequest = sendCoinsTaskRunner.createAssetLockSendRequest(
275+
var finalSendRequest = sendCoinsTaskRunner.createAssetLockSendRequest(
274276
basePaymentIntent.mayEditAmount(),
275277
finalPaymentIntent,
276278
true,
@@ -280,6 +282,25 @@ class SendCoinsViewModel @Inject constructor(
280282
finalSendRequest.memo = basePaymentIntent.memo
281283
finalSendRequest.exchangeRate = exchangeRate
282284

285+
if (emptyWallet) {
286+
sendCoinsTaskRunner.signSendRequest(finalSendRequest)
287+
wallet.completeTx(finalSendRequest)
288+
289+
// make sure that the asset lock payload matches the OP_RETURN output
290+
val outputValue = finalSendRequest.tx.outputs.first().value
291+
val assetLockedValue = (finalSendRequest.tx as AssetLockTransaction).assetLockPayload.creditOutputs.first().value
292+
if (assetLockedValue != outputValue) {
293+
val newRequest = SendRequest.assetLock(wallet.params, key, outputValue, true)
294+
newRequest.coinSelector = finalSendRequest.coinSelector
295+
newRequest.returnChange = finalSendRequest.returnChange
296+
newRequest.aesKey = finalSendRequest.aesKey
297+
finalSendRequest = newRequest
298+
} else {
299+
// this shouldn't happen
300+
error("The asset lock value is the same as the output though emptying the wallet")
301+
}
302+
}
303+
283304
sendCoinsTaskRunner.sendCoins(finalSendRequest, checkBalanceConditions = checkBalance)
284305
} catch (ex: Exception) {
285306
_state.value = State.FAILED
@@ -534,7 +555,7 @@ class SendCoinsViewModel @Inject constructor(
534555
}
535556
}
536557

537-
fun getNextTopupKey(): ECKey {
558+
fun getNextKey(): ECKey {
538559
val authGroup = wallet.getKeyChainExtension(
539560
AuthenticationGroupExtension.EXTENSION_ID
540561
) as AuthenticationGroupExtension

0 commit comments

Comments
 (0)