Refactor parser classes

- Remove tempTx from TxOutputParser
- Renamings
- Add isOpReturnOutput method to TempTxOuput
This commit is contained in:
Manfred Karrer 2018-10-03 22:28:31 -05:00
parent c0d776e536
commit 56b8a3986b
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
3 changed files with 63 additions and 55 deletions

View file

@ -24,7 +24,6 @@ import bisq.core.dao.state.blockchain.TempTx;
import bisq.core.dao.state.blockchain.TempTxOutput;
import bisq.core.dao.state.blockchain.TxOutput;
import bisq.core.dao.state.blockchain.TxOutputType;
import bisq.core.dao.state.blockchain.TxType;
import com.google.common.annotations.VisibleForTesting;
@ -53,8 +52,6 @@ public class TxOutputParser {
@Setter
private int unlockBlockHeight;
@Setter
private TempTx tempTx; //TODO remove
@Setter
@Getter
private Optional<TxOutput> optionalSpentLockupTxOutput = Optional.empty();
@ -74,7 +71,7 @@ public class TxOutputParser {
// Private
private int lockTime;
private List<TempTxOutput> tempTxOutputs = new ArrayList<>();
private List<TempTxOutput> utxoCandidates = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -93,25 +90,20 @@ public class TxOutputParser {
void processGenesisTxOutput(TempTx genesisTx) {
for (int i = 0; i < genesisTx.getTempTxOutputs().size(); ++i) {
TempTxOutput tempTxOutput = genesisTx.getTempTxOutputs().get(i);
tempTxOutputs.add(tempTxOutput);
utxoCandidates.add(tempTxOutput);
bsqStateService.addUnspentTxOutput(TxOutput.fromTempOutput(tempTxOutput));
}
commitTxOutputsForValidTx();
}
void commitTxOutputsForValidTx() {
tempTxOutputs.forEach(output -> bsqStateService.addUnspentTxOutput(TxOutput.fromTempOutput(output)));
void commitUTXOCandidates() {
utxoCandidates.forEach(output -> bsqStateService.addUnspentTxOutput(TxOutput.fromTempOutput(output)));
}
/**
* This sets all outputs to BTC_OUTPUT and doesn't add any txOutputs to the unspentTxOutput map in bsqStateService
*/
void commitTxOutputsForInvalidTx() {
tempTxOutputs.forEach(output -> output.setTxOutputType(TxOutputType.BTC_OUTPUT));
}
// We do not check for pubKeyScript.scriptType.NULL_DATA because that is only set if dumpBlockchainData is true
boolean isOpReturnOutput(TempTxOutput txOutput) {
return txOutput.getOpReturnData() != null;
void invalidateUTXOCandidates() {
utxoCandidates.forEach(output -> output.setTxOutputType(TxOutputType.BTC_OUTPUT));
}
void processOpReturnOutput(TempTxOutput tempTxOutput) {
@ -136,10 +128,7 @@ public class TxOutputParser {
void processTxOutput(TempTxOutput tempTxOutput) {
long txOutputValue = tempTxOutput.getValue();
int index = tempTxOutput.getIndex();
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)) {
if (isUnlockBondTx(tempTxOutput.getValue(), index)) {
// 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
// output to not be BSQ output at all
@ -177,7 +166,7 @@ public class TxOutputParser {
txOutput.setTxOutputType(TxOutputType.UNLOCK_OUTPUT);
txOutput.setUnlockBlockHeight(unlockBlockHeight);
tempTxOutputs.add(txOutput);
utxoCandidates.add(txOutput);
bsqOutputFound = true;
}
@ -211,7 +200,7 @@ public class TxOutputParser {
bsqOutput = TxOutputType.BSQ_OUTPUT;
}
txOutput.setTxOutputType(bsqOutput);
tempTxOutputs.add(txOutput);
utxoCandidates.add(txOutput);
bsqOutputFound = true;
}

View file

@ -79,11 +79,14 @@ public class TxParser {
// There might be txs without any valid BSQ txOutput but we still keep track of it,
// for instance to calculate the total burned BSQ.
public Optional<Tx> findTx(RawTx rawTx, String genesisTxId, int genesisBlockHeight, Coin genesisTotalSupply) {
txInputParser = new TxInputParser(bsqStateService);
txOutputParser = new TxOutputParser(bsqStateService);
// ****************************************************************************************
// Parse Genesis
// ****************************************************************************************
// Let's see if we have a genesis tx
Optional<TempTx> optionalGenesisTx = TxParser.findGenesisTx(
Optional<TempTx> optionalGenesisTx = findGenesisTx(
genesisTxId,
genesisBlockHeight,
genesisTotalSupply,
@ -94,6 +97,12 @@ public class TxParser {
return Optional.of(Tx.fromTempTx(genesisTx));
}
// ****************************************************************************************
// Parse Inputs
// ****************************************************************************************
txInputParser = new TxInputParser(bsqStateService);
// If it is not a genesis tx we continue to parse to see if it is a valid BSQ tx.
int blockHeight = rawTx.getBlockHeight();
// We could pass tx also to the sub validators but as long we have not refactored the validators to pure
@ -107,22 +116,18 @@ public class TxParser {
}
long accumulatedInputValue = txInputParser.getAccumulatedInputValue();
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.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput());
txOutputParser.setTempTx(tempTx); //TODO remove
boolean hasBsqInputs = accumulatedInputValue > 0;
// ****************************************************************************************
// Parse Outputs
// ****************************************************************************************
if (hasBsqInputs) {
txOutputParser.setAvailableInputValue(accumulatedInputValue);
txOutputParser.setUnlockBlockHeight(txInputParser.getUnlockBlockHeight());
txOutputParser.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput());
List<TempTxOutput> outputs = tempTx.getTempTxOutputs();
// We start with last output as that might be an OP_RETURN output and gives us the specific tx type, so it is
// easier and cleaner at parsing the other outputs to detect which kind of tx we deal with.
@ -132,7 +137,7 @@ public class TxParser {
checkArgument(!outputs.isEmpty(), "outputs cannot be empty");
int lastIndex = outputs.size() - 1;
int lastNonOpReturnIndex = lastIndex;
if (txOutputParser.isOpReturnOutput(outputs.get(lastIndex))) {
if (outputs.get(lastIndex).isOpReturnOutput()) {
txOutputParser.processOpReturnOutput(outputs.get(lastIndex));
lastNonOpReturnIndex -= 1;
}
@ -146,43 +151,43 @@ public class TxParser {
long remainingInputValue = txOutputParser.getAvailableInputValue();
boolean hasBurntBSQ = remainingInputValue > 0;
// Apply txType and optional txOutputTypes based on opReturn types
// We might get a INVALID TxType here
verifyTxAndOutputs(blockHeight, tempTx, remainingInputValue);
//TODO use setBurntFee but check in bsqStateService if txType is INVALID
if (hasBurntBSQ)
tempTx.setBurntFee(remainingInputValue);
if (isTxInvalid(tempTx, txOutputParser.isBsqOutputFound()))
tempTx.setTxType(TxType.INVALID);
// We might get a INVALID TxType here as well
// ****************************************************************************************
// Verify and apply txType and txOutputTypes after we have all outputs parsed
// ****************************************************************************************
applyTxTypeAndTxOutputType(blockHeight, tempTx, remainingInputValue);
TxType txType = evaluateTxType(tempTx, txOutputParser.getOptionalOpReturnType(), hasBurntBSQ,
txInputParser.isUnLockInputValid());
tempTx.setTxType(txType);
if (tempTx.getTxType() != TxType.INVALID) {
txOutputParser.commitTxOutputsForValidTx();
} else {
txOutputParser.commitTxOutputsForInvalidTx();
if (isTxInvalid(tempTx, txOutputParser.isBsqOutputFound())) {
tempTx.setTxType(TxType.INVALID);
txOutputParser.invalidateUTXOCandidates();
if (hasBurntBSQ) {
log.warn("We have destroyed BSQ because of an invalid tx. Burned BSQ={}. tx={}",
remainingInputValue / 100D, tempTx);
}
} else {
txOutputParser.commitUTXOCandidates();
}
//TODO use setBurntFee but check in bsqStateService if txType is INVALID
if (hasBurntBSQ)
tempTx.setBurntFee(remainingInputValue);
}
// TODO || parsingModel.getBurntBondValue() > 0; should not be necessary
// TODO || txInputParser.getBurntBondValue() > 0; should not be necessary
// How should we consider the burnt BSQ from spending a LOCKUP tx with the wrong format.
// Example: LOCKUP txOutput is 1000 satoshi but first txOutput in spending tx is 900
// satoshi, this burns the 1000 satoshi and is currently not considered in the
// bsqInputBalancePositive, hence the need to check for parsingModel.getBurntBondValue
// Perhaps adding boolean parsingModel.isBSQTx and checking for that would be better?
//TODO
if (hasBsqInputs || txInputParser.getBurntBondValue() > 0)
return Optional.of(Tx.fromTempTx(tempTx));
else
@ -202,7 +207,7 @@ public class TxParser {
* It verifies also if the fee is correct (if required) and if the phase is correct (if relevant).
* We set the txType as well as the txOutputType of the relevant outputs.
*/
private void verifyTxAndOutputs(int blockHeight, TempTx tempTx, long bsqFee) {
private void applyTxTypeAndTxOutputType(int blockHeight, TempTx tempTx, long bsqFee) {
OpReturnType opReturnType = null;
Optional<OpReturnType> optionalOpReturnType = txOutputParser.getOptionalOpReturnType();
if (optionalOpReturnType.isPresent()) {
@ -347,6 +352,15 @@ public class TxParser {
return true;
}
// We don't allow multiple opReturn outputs (they are non-standard but to be safe lets check it)
long numOpReturnOutputs = tempTx.getTempTxOutputs().stream()
.filter(TempTxOutput::isOpReturnOutput)
.count();
if (numOpReturnOutputs > 1) {
log.warn("Invalid tx. We have multiple opReturn outputs. tx=" + tempTx);
return true;
}
if (!bsqOutputFound) {
log.warn("Invalid Tx: No BSQ output found. tx=" + tempTx);
return true;

View file

@ -79,4 +79,9 @@ public class TempTxOutput extends BaseTxOutput {
"\n unlockBlockHeight=" + unlockBlockHeight +
"\n} " + super.toString();
}
public boolean isOpReturnOutput() {
// We do not check for pubKeyScript.scriptType.NULL_DATA because that is only set if dumpBlockchainData is true
return getOpReturnData() != null;
}
}