Check for multiple opReturns before processing txOutputs

This commit is contained in:
sqrrm 2018-09-05 16:51:12 +02:00
parent f31bf0d5cf
commit 5dffe7159e
No known key found for this signature in database
GPG key ID: 45235F9EF87089EC
2 changed files with 26 additions and 19 deletions

View file

@ -24,6 +24,7 @@ import bisq.core.dao.state.blockchain.TempTx;
import bisq.core.dao.state.blockchain.TempTxOutput; import bisq.core.dao.state.blockchain.TempTxOutput;
import bisq.core.dao.state.blockchain.TxOutput; import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.blockchain.TxOutputType; import bisq.core.dao.state.blockchain.TxOutputType;
import bisq.core.dao.state.blockchain.TxType;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -105,7 +106,10 @@ public class TxOutputParser {
byte[] opReturnData = tempTxOutput.getOpReturnData(); byte[] opReturnData = tempTxOutput.getOpReturnData();
if (opReturnData == null) { if (opReturnData == null) {
long txOutputValue = tempTxOutput.getValue(); long txOutputValue = tempTxOutput.getValue();
if (isUnlockBondTx(tempTxOutput.getValue(), index)) { if (tempTx.getTxType() == TxType.INVALID) {
// Set all non opReturn outputs to BTC_OUTPUT if the tx is invalid
tempTxOutput.setTxOutputType(TxOutputType.BTC_OUTPUT);
} else if (isUnlockBondTx(tempTxOutput.getValue(), index)) {
// We need to handle UNLOCK transactions separately as they don't follow the pattern on spending BSQ // We need to handle UNLOCK transactions separately as they don't follow the pattern on spending BSQ
// The LOCKUP BSQ is burnt unless the output exactly matches the input, that would cause the // The LOCKUP BSQ is burnt unless the output exactly matches the input, that would cause the
// output to not be BSQ output at all // output to not be BSQ output at all

View file

@ -101,6 +101,15 @@ public class TxParser {
long accumulatedInputValue = txInputParser.getAccumulatedInputValue(); long accumulatedInputValue = txInputParser.getAccumulatedInputValue();
txOutputParser.setAvailableInputValue(accumulatedInputValue); txOutputParser.setAvailableInputValue(accumulatedInputValue);
// We don't allow multiple opReturn outputs (they are non-standard but to be safe lets check it)
long numOpReturnOutputs = tempTx.getTempTxOutputs().stream().filter(txOutputParser::isOpReturnOutput).count();
if (numOpReturnOutputs > 1) {
tempTx.setTxType(TxType.INVALID);
String msg = "Invalid tx. We have multiple opReturn outputs. tx=" + tempTx;
log.warn(msg);
}
txOutputParser.setUnlockBlockHeight(txInputParser.getUnlockBlockHeight()); txOutputParser.setUnlockBlockHeight(txInputParser.getUnlockBlockHeight());
txOutputParser.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput()); txOutputParser.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput());
txOutputParser.setTempTx(tempTx); //TODO remove txOutputParser.setTempTx(tempTx); //TODO remove
@ -116,7 +125,7 @@ public class TxParser {
checkArgument(!outputs.isEmpty(), "outputs must not be empty"); checkArgument(!outputs.isEmpty(), "outputs must not be empty");
int lastIndex = outputs.size() - 1; int lastIndex = outputs.size() - 1;
int lastNonOpReturnIndex = lastIndex; int lastNonOpReturnIndex = lastIndex;
if (txOutputParser.isOpReturnOutput(outputs.get(lastIndex))){ if (txOutputParser.isOpReturnOutput(outputs.get(lastIndex))) {
// TODO(SQ): perhaps the check for isLastOutput could be skipped // TODO(SQ): perhaps the check for isLastOutput could be skipped
txOutputParser.processOpReturnOutput(true, outputs.get(lastIndex)); txOutputParser.processOpReturnOutput(true, outputs.get(lastIndex));
lastNonOpReturnIndex -= 1; lastNonOpReturnIndex -= 1;
@ -134,11 +143,15 @@ public class TxParser {
remainingInputValue = txOutputParser.getAvailableInputValue(); remainingInputValue = txOutputParser.getAvailableInputValue();
// TODO(SQ): If the tx is set to INVALID in this check the txOutputs stay valid
processOpReturnType(blockHeight, tempTx); processOpReturnType(blockHeight, tempTx);
// We don't allow multiple opReturn outputs (they are non-standard but to be safe lets check it) // TODO(SQ): Should the destroyed BSQ from an INVALID tx be considered as burnt fee?
long numOpReturnOutputs = tempTx.getTempTxOutputs().stream().filter(txOutputParser::isOpReturnOutput).count(); if (remainingInputValue > 0)
if (numOpReturnOutputs <= 1) { tempTx.setBurntFee(remainingInputValue);
// Process the type of transaction if not already determined to be INVALID
if (tempTx.getTxType() != TxType.INVALID) {
boolean isAnyTxOutputTypeUndefined = tempTx.getTempTxOutputs().stream() boolean isAnyTxOutputTypeUndefined = tempTx.getTempTxOutputs().stream()
.anyMatch(txOutput -> TxOutputType.UNDEFINED == txOutput.getTxOutputType()); .anyMatch(txOutput -> TxOutputType.UNDEFINED == txOutput.getTxOutputType());
if (!isAnyTxOutputTypeUndefined) { if (!isAnyTxOutputTypeUndefined) {
@ -151,21 +164,11 @@ public class TxParser {
getOptionalOpReturnType() getOptionalOpReturnType()
); );
tempTx.setTxType(txType); tempTx.setTxType(txType);
if (remainingInputValue > 0)
tempTx.setBurntFee(remainingInputValue);
} else { } else {
tempTx.setTxType(TxType.INVALID); tempTx.setTxType(TxType.INVALID);
String msg = "We have undefined txOutput types which must not happen. tx=" + tempTx; String msg = "We have undefined txOutput types which must not happen. tx=" + tempTx;
DevEnv.logErrorAndThrowIfDevMode(msg); DevEnv.logErrorAndThrowIfDevMode(msg);
} }
} else {
// TODO(SQ): The transaction has already been parsed here and the individual txouputs are considered
// spendable or otherwise correct. Perhaps this check should be done earlier.
// We don't consider a tx with multiple OpReturn outputs valid.
tempTx.setTxType(TxType.INVALID);
String msg = "Invalid tx. We have multiple opReturn outputs. tx=" + tempTx;
log.warn(msg);
} }
} }
@ -260,8 +263,8 @@ public class TxParser {
} }
private void processCompensationRequest(int blockHeight, TempTx tempTx, long bsqFee) { private void processCompensationRequest(int blockHeight, TempTx tempTx, long bsqFee) {
boolean isFeeAndPhaseValid; boolean isFeeAndPhaseValid =
isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE);
Optional<TempTxOutput> optionalIssuanceCandidate = txOutputParser.getOptionalIssuanceCandidate(); Optional<TempTxOutput> optionalIssuanceCandidate = txOutputParser.getOptionalIssuanceCandidate();
if (isFeeAndPhaseValid) { if (isFeeAndPhaseValid) {
if (optionalIssuanceCandidate.isPresent()) { if (optionalIssuanceCandidate.isPresent()) {
@ -281,8 +284,8 @@ public class TxParser {
} }
private void processProposal(int blockHeight, TempTx tempTx, long bsqFee) { private void processProposal(int blockHeight, TempTx tempTx, long bsqFee) {
boolean isFeeAndPhaseValid; boolean isFeeAndPhaseValid =
isFeeAndPhaseValid = isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE); isFeeAndPhaseValid(blockHeight, bsqFee, DaoPhase.Phase.PROPOSAL, Param.PROPOSAL_FEE);
if (!isFeeAndPhaseValid) { if (!isFeeAndPhaseValid) {
tempTx.setTxType(TxType.INVALID); tempTx.setTxType(TxType.INVALID);
} }