Skip to content

Commit

Permalink
Merge pull request #2614 from ManfredKarrer/add-missing-check-for-man…
Browse files Browse the repository at this point in the history
…datory-bsq-output

Add missing check for mandatory bsq output
  • Loading branch information
ripcurlx authored Apr 2, 2019
2 parents eaeef7a + 54f1a9f commit 6e2aaec
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 18 deletions.
27 changes: 20 additions & 7 deletions core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -524,19 +524,23 @@ 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);
public Transaction getPreparedProposalTx(Coin fee, boolean requireChangeOutput) throws InsufficientBsqException {
return getPreparedBurnFeeTx(fee, requireChangeOutput);
}

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.
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/java/bisq/core/dao/node/parser/TxOutputParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,25 +194,25 @@ 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
// that tx.
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;
Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/bisq/core/dao/node/parser/TxParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down

0 comments on commit 6e2aaec

Please sign in to comment.