From 6387cfa5bf27bd9830bd30596884adc9f81731e6 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 1 Apr 2019 21:23:00 -0500 Subject: [PATCH 1/3] Refactoring: rename local variable --- .../bisq/core/dao/node/parser/TxOutputParser.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java index 1838d5c1d6a..e520e191015 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java @@ -194,15 +194,15 @@ private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValu if (optionalOpReturnType.isPresent()) opReturnTypeCandidate = optionalOpReturnType.get(); - TxOutputType bsqOutput; + TxOutputType txOutputType; if (isFirstOutput && opReturnTypeCandidate == OpReturnType.BLIND_VOTE) { - bsqOutput = TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT; + txOutputType = TxOutputType.BLIND_VOTE_LOCK_STAKE_OUTPUT; optionalBlindVoteLockStakeOutput = Optional.of(txOutput); } else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.VOTE_REVEAL) { - bsqOutput = TxOutputType.VOTE_REVEAL_UNLOCK_STAKE_OUTPUT; + txOutputType = TxOutputType.VOTE_REVEAL_UNLOCK_STAKE_OUTPUT; optionalVoteRevealUnlockStakeOutput = Optional.of(txOutput); } else if (isFirstOutput && opReturnTypeCandidate == OpReturnType.LOCKUP) { - bsqOutput = TxOutputType.LOCKUP_OUTPUT; + txOutputType = TxOutputType.LOCKUP_OUTPUT; // We store the lockTime in the output which will be used as input for a unlock tx. // That makes parsing of that data easier as if we would need to access it from the opReturn output of @@ -210,9 +210,9 @@ private void handleBsqOutput(TempTxOutput txOutput, int index, long txOutputValu txOutput.setLockTime(lockTime); optionalLockupOutput = Optional.of(txOutput); } else { - bsqOutput = TxOutputType.BSQ_OUTPUT; + txOutputType = TxOutputType.BSQ_OUTPUT; } - txOutput.setTxOutputType(bsqOutput); + txOutput.setTxOutputType(txOutputType); utxoCandidates.add(txOutput); bsqOutputFound = true; From 69a2a04546eaad746c2ddf9fa8bf6b65164e31f8 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 1 Apr 2019 21:33:11 -0500 Subject: [PATCH 2/3] Add check for mandatory BSQ output for compensation tx Fixes https://github.com/bisq-network/bisq/issues/2613 If the user has the exact fee in his wallet no BSQ change output would be created but that violates our requirement for compensation or reimbursement txs. Beside that the error message for dust outputs was not clear. --- .../core/btc/wallet/BsqWalletService.java | 25 ++++++++++++++----- .../bisq/core/dao/node/parser/TxParser.java | 6 +++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index c3645e0e8fe..a025824bef4 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -525,18 +525,22 @@ private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmoun // We create a tx with Bsq inputs for the fee and optional BSQ change output. // As the fee amount will be missing in the output those BSQ fees are burned. public Transaction getPreparedProposalTx(Coin fee) throws InsufficientBsqException { - return getPreparedBurnFeeTx(fee); + return getPreparedBurnFeeTx(fee, true); } public Transaction getPreparedBurnFeeTx(Coin fee) throws InsufficientBsqException { + return getPreparedBurnFeeTx(fee, false); + } + + private Transaction getPreparedBurnFeeTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException { DaoKillSwitch.assertDaoIsNotDisabled(); final Transaction tx = new Transaction(params); - addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector); + addInputsAndChangeOutputForTx(tx, fee, bsqCoinSelector, requireChangeOutput); // printTx("getPreparedFeeTx", tx); return tx; } - private void addInputsAndChangeOutputForTx(Transaction tx, Coin fee, BsqCoinSelector bsqCoinSelector) + private void addInputsAndChangeOutputForTx(Transaction tx, Coin fee, BsqCoinSelector bsqCoinSelector, boolean requireChangeOutput) throws InsufficientBsqException { Coin requiredInput; // If our fee is less then dust limit we increase it so we are sure to not get any dust output. @@ -550,10 +554,19 @@ private void addInputsAndChangeOutputForTx(Transaction tx, Coin fee, BsqCoinSele try { // TODO why is fee passed to getChange ??? Coin change = bsqCoinSelector.getChange(fee, coinSelection); + if (requireChangeOutput) { + checkArgument(change.isPositive(), + "This transaction requires a mandatory BSQ change output. " + + "You are missing " + Restrictions.getMinNonDustOutput().value / 100d + + " BSQ for a non dust change output."); + } + if (change.isPositive()) { checkArgument(Restrictions.isAboveDust(change), "The change output of " + change.value / 100d + " BSQ is below the min. dust value of " - + Restrictions.getMinNonDustOutput().value / 100d + " BSQ."); + + Restrictions.getMinNonDustOutput().value / 100d + + " BSQ. You are missing " + (Restrictions.getMinNonDustOutput().value - change.value) / 100d + + " BSQ for a non dust change output."); tx.addOutput(change, getChangeAddress()); } } catch (InsufficientMoneyException e) { @@ -572,7 +585,7 @@ public Transaction getPreparedBlindVoteTx(Coin fee, Coin stake) throws Insuffici DaoKillSwitch.assertDaoIsNotDisabled(); Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress())); - addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector); + addInputsAndChangeOutputForTx(tx, fee.add(stake), bsqCoinSelector, false); //printTx("getPreparedBlindVoteTx", tx); return tx; } @@ -606,7 +619,7 @@ public Transaction getPreparedLockupTx(Coin lockupAmount) throws AddressFormatEx Transaction tx = new Transaction(params); checkArgument(Restrictions.isAboveDust(lockupAmount), "The amount is too low (dust limit)."); tx.addOutput(new TransactionOutput(params, tx, lockupAmount, getUnusedAddress())); - addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector); + addInputsAndChangeOutputForTx(tx, lockupAmount, bsqCoinSelector, false); printTx("prepareLockupTx", tx); return tx; } diff --git a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java index b19fadfc781..57c0cf11816 100644 --- a/core/src/main/java/bisq/core/dao/node/parser/TxParser.java +++ b/core/src/main/java/bisq/core/dao/node/parser/TxParser.java @@ -330,8 +330,10 @@ static boolean isTxInvalid(TempTx tempTx, boolean bsqOutputFound, boolean burntB return true; } - if (!bsqOutputFound) { - log.warn("Invalid Tx: No BSQ output found. tx=" + tempTx); + if ((tempTx.getTxType() == TxType.COMPENSATION_REQUEST || + tempTx.getTxType() == TxType.REIMBURSEMENT_REQUEST) + && !bsqOutputFound) { + log.warn("Invalid Tx: A compensation or reimbursement tx requires 1 BSQ output. Tx=" + tempTx); return true; } From 54f1a9ff5f07a3965fd6faa29ba60bb646736a0a Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 1 Apr 2019 21:39:37 -0500 Subject: [PATCH 3/3] Only require change output for issuance proposals --- .../main/java/bisq/core/btc/wallet/BsqWalletService.java | 4 ++-- .../core/dao/governance/proposal/BaseProposalFactory.java | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index a025824bef4..de75aa3d705 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -524,8 +524,8 @@ private Transaction getPreparedSendTx(String receiverAddress, Coin receiverAmoun // We create a tx with Bsq inputs for the fee and optional BSQ change output. // As the fee amount will be missing in the output those BSQ fees are burned. - public Transaction getPreparedProposalTx(Coin fee) throws InsufficientBsqException { - return getPreparedBurnFeeTx(fee, true); + public Transaction getPreparedProposalTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException { + return getPreparedBurnFeeTx(fee, requireChangeOutput); } public Transaction getPreparedBurnFeeTx(Coin fee) throws InsufficientBsqException { diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java index 87528074448..4c3cb5f78f7 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/BaseProposalFactory.java @@ -85,16 +85,17 @@ protected ProposalWithTransaction createProposalWithTransaction(String name, // The hashOfPayload used in the opReturnData is created with the txId set to null. private Transaction createTransaction(R proposal) throws InsufficientMoneyException, TxException { try { - final Coin fee = ProposalConsensus.getFee(daoStateService, daoStateService.getChainHeight()); + Coin fee = ProposalConsensus.getFee(daoStateService, daoStateService.getChainHeight()); // We create a prepared Bsq Tx for the proposal fee. - final Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProposalTx(fee); + boolean requireChangeOutput = proposal instanceof IssuanceProposal; + Transaction preparedBurnFeeTx = bsqWalletService.getPreparedProposalTx(fee, requireChangeOutput); // payload does not have txId at that moment byte[] hashOfPayload = ProposalConsensus.getHashOfPayload(proposal); byte[] opReturnData = getOpReturnData(hashOfPayload); // We add the BTC inputs for the miner fee. - final Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal); + Transaction txWithBtcFee = completeTx(preparedBurnFeeTx, opReturnData, proposal); // We sign the BSQ inputs of the final tx. Transaction transaction = bsqWalletService.signTx(txWithBtcFee);