mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-22 14:42:37 +01:00
Refactor parser classes
- Remove tempTx from TxOutputParser - Renamings - Add isOpReturnOutput method to TempTxOuput
This commit is contained in:
parent
c0d776e536
commit
56b8a3986b
3 changed files with 63 additions and 55 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue