use of bisq.core.dao.state.blockchain.TxInput in project bisq-core by bisq-network.
the class TxParserTest method testGetGenesisTx.
@Test
public void testGetGenesisTx() {
int blockHeight = 200;
String blockHash = "abc123";
Coin genesisTotalSupply = Coin.parseCoin("2.5");
// Thu Jun 20 14:04:25 CEST 2013
long time = 1371729865;
final List<TxInput> inputs = Arrays.asList(new TxInput("tx0", 0, null), new TxInput("tx1", 1, null));
RawTxOutput output = new RawTxOutput(0, genesisTotalSupply.value, null, null, null, null, blockHeight);
RawTx rawTx = new RawTx("tx2", blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
String genesisTxId = "genesisTxId";
int genesisBlockHeight = 150;
// With mismatch in block height and tx id, we should not get genesis tx back.
Optional<TempTx> result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
Optional<TempTx> want = Optional.empty();
Assert.assertEquals(want, result);
// With correct block height but mismatch in tx id, we should still not get genesis tx back.
blockHeight = 150;
rawTx = new RawTx("tx2", blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
want = Optional.empty();
Assert.assertEquals(want, result);
// With correct tx id and block height, we should find our genesis tx with correct tx and output type.
rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
TempTx tempTx = TempTx.fromRawTx(rawTx);
tempTx.setTxType(TxType.GENESIS);
for (int i = 0; i < tempTx.getTempTxOutputs().size(); ++i) {
tempTx.getTempTxOutputs().get(i).setTxOutputType(TxOutputType.GENESIS_OUTPUT);
}
want = Optional.of(tempTx);
Assert.assertEquals(want, result);
// With correct tx id and block height, but too low sum of outputs (lower than genesisTotalSupply), we
// should see an exception raised.
output = new RawTxOutput(0, genesisTotalSupply.value - 1, null, null, null, null, blockHeight);
rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
try {
result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too low");
} catch (InvalidGenesisTxException igtxe) {
String wantMessage = "Genesis tx is invalid; not using all available inputs. Remaining input value is 1 sat";
Assert.assertTrue("Unexpected exception, want message starting with " + "'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
}
// With correct tx id and block height, but too high sum of outputs (higher than from genesisTotalSupply), we
// should see an exception raised.
RawTxOutput output1 = new RawTxOutput(0, genesisTotalSupply.value - 2, null, null, null, null, blockHeight);
RawTxOutput output2 = new RawTxOutput(0, 3, null, null, null, null, blockHeight);
rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output1, output2)));
try {
result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too high");
} catch (InvalidGenesisTxException igtxe) {
String wantMessage = "Genesis tx is invalid; using more than available inputs. Remaining input value is 2 sat";
Assert.assertTrue("Unexpected exception, want message starting with " + "'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
}
}
use of bisq.core.dao.state.blockchain.TxInput in project bisq-core by bisq-network.
the class TxParser method findTx.
// Apply state changes to tx, inputs and outputs
// return true if any input contained BSQ
// Any tx with BSQ input is a BSQ tx (except genesis tx but that is not handled in
// that class).
// 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);
// Let's see if we have a genesis tx
Optional<TempTx> optionalGenesisTx = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
if (optionalGenesisTx.isPresent()) {
TempTx genesisTx = optionalGenesisTx.get();
txOutputParser.processGenesisTxOutput(genesisTx);
return Optional.of(Tx.fromTempTx(genesisTx));
}
// 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
// functions lets use the parsingModel.
TempTx tempTx = TempTx.fromRawTx(rawTx);
for (int inputIndex = 0; inputIndex < tempTx.getTxInputs().size(); inputIndex++) {
TxInput input = tempTx.getTxInputs().get(inputIndex);
TxOutputKey outputKey = input.getConnectedTxOutputKey();
txInputParser.process(outputKey, blockHeight, rawTx.getId(), inputIndex);
}
long accumulatedInputValue = txInputParser.getAccumulatedInputValue();
txOutputParser.setAvailableInputValue(accumulatedInputValue);
txOutputParser.setUnlockBlockHeight(txInputParser.getUnlockBlockHeight());
txOutputParser.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput());
// TODO remove
txOutputParser.setTempTx(tempTx);
boolean hasBsqInputs = accumulatedInputValue > 0;
if (hasBsqInputs) {
final 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.
// Setting the opReturn type here does not mean it will be a valid BSQ tx as the checks are only partial and
// BSQ inputs are not verified yet.
// We keep the temporary opReturn type in the parsingModel object.
checkArgument(!outputs.isEmpty(), "outputs must not be empty");
int lastIndex = outputs.size() - 1;
txOutputParser.processOpReturnCandidate(outputs.get(lastIndex));
// We iterate all outputs including the opReturn to do a full validation including the BSQ fee
for (int index = 0; index < outputs.size(); index++) {
boolean isLastOutput = index == lastIndex;
txOutputParser.processTxOutput(isLastOutput, outputs.get(index), index);
}
remainingInputValue = txOutputParser.getAvailableInputValue();
processOpReturnType(blockHeight, tempTx);
// 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) {
boolean isAnyTxOutputTypeUndefined = tempTx.getTempTxOutputs().stream().anyMatch(txOutput -> TxOutputType.UNDEFINED == txOutput.getTxOutputType());
if (!isAnyTxOutputTypeUndefined) {
// TODO(chirhonul): we don't modify the tempTx within the call below, so maybe we should
// use RawTx?
TxType txType = TxParser.getBisqTxType(tempTx, txOutputParser.getOptionalOpReturnTypeCandidate().isPresent(), remainingInputValue, getOptionalOpReturnType());
tempTx.setTxType(txType);
if (remainingInputValue > 0)
tempTx.setBurntFee(remainingInputValue);
} else {
tempTx.setTxType(TxType.INVALID);
String msg = "We have undefined txOutput types which must not happen. tx=" + tempTx;
DevEnv.logErrorAndThrowIfDevMode(msg);
}
} else {
// 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);
}
}
if (hasBsqInputs || txInputParser.getBurntBondValue() > 0)
return Optional.of(Tx.fromTempTx(tempTx));
else
return Optional.empty();
}
use of bisq.core.dao.state.blockchain.TxInput in project bisq-core by bisq-network.
the class IssuanceService method issueBsq.
public void issueBsq(CompensationProposal compensationProposal, int chainHeight) {
bsqStateService.getIssuanceCandidateTxOutputs().stream().filter(txOutput -> isValid(txOutput, compensationProposal, periodService, chainHeight)).forEach(txOutput -> {
// We don't check atm if the output is unspent. We cannot use the bsqWallet as that would not
// reflect our current block state (could have been spent at later block which is valid and
// bsqWallet would show that spent state). We would need to support a spent status for the outputs
// which are interpreted as BTC (as a not yet accepted comp. request).
Optional<Tx> optionalTx = bsqStateService.getTx(compensationProposal.getTxId());
if (optionalTx.isPresent()) {
long amount = compensationProposal.getRequestedBsq().value;
Tx tx = optionalTx.get();
// We use key from first input
TxInput txInput = tx.getTxInputs().get(0);
String pubKey = txInput.getPubKey();
Issuance issuance = new Issuance(tx.getId(), chainHeight, amount, pubKey);
bsqStateService.addIssuance(issuance);
bsqStateService.addUnspentTxOutput(txOutput);
StringBuilder sb = new StringBuilder();
sb.append("\n################################################################################\n");
sb.append("We issued new BSQ to tx with ID ").append(txOutput.getTxId()).append("\nfor compensationProposal with UID ").append(compensationProposal.getTxId()).append("\n################################################################################\n");
log.info(sb.toString());
} else {
// TODO throw exception
log.error("Tx for compensation request not found. txId={}", compensationProposal.getTxId());
}
});
}
Aggregations