use of org.bitcoinj.core.TransactionInput in project catena-java by alinush.
the class TxUtils method findDoubleSpendsAgainst.
/**
* Checks if any of the transactions in txs spend the same outputs as the transactions in candidates.
*
* The ordering of the transactions in the collection associated with the output is the following.
* (1) first, the tx from 'txs'
* (2) second, any double spends from 'candidates'
*
* @param txs
* @param candidates
* @return
*/
public static Map<TransactionOutPoint, List<Transaction>> findDoubleSpendsAgainst(Iterator<Transaction> txsIt, Map<Sha256Hash, Transaction> candidates) {
// Maps an outpoint to a list of transactions that spend it (ideally, that list should be of size one)
ArrayListMultimap<TransactionOutPoint, Transaction> outpoints = ArrayListMultimap.create();
while (txsIt.hasNext()) {
Transaction tx = txsIt.next();
// Coinbase TXs cannot double spend
if (tx.isCoinBase())
continue;
// Compile a set of outpoints that are spent by tx.
for (TransactionInput input : tx.getInputs()) {
outpoints.put(input.getOutpoint(), tx);
}
}
// Now for each candidate transaction, see if it spends any outpoints as this tx.
for (Transaction tx : candidates.values()) {
for (TransactionInput input : tx.getInputs()) {
TransactionOutPoint outpoint = input.getOutpoint();
// double spends amongst txs in candidates, which might not be desired by callers.
if (outpoints.containsKey(outpoint)) {
// It does, it's a double spend against the candidates, which makes it relevant.
outpoints.put(outpoint, tx);
}
}
}
// Now clear <outp, list(tx)> pairs where the sizeof(list) == 1 (i.e., no double spend)
Map<TransactionOutPoint, List<Transaction>> doubleSpends = Multimaps.asMap(outpoints);
Iterator<List<Transaction>> it = doubleSpends.values().iterator();
while (it.hasNext()) {
int numSpends = it.next().size();
if (numSpends < 2) {
it.remove();
}
}
return doubleSpends;
}
use of org.bitcoinj.core.TransactionInput in project catena-java by alinush.
the class TxUtils method findDoubleSpendsAmongst.
public static Map<TransactionOutPoint, List<Transaction>> findDoubleSpendsAmongst(Collection<Transaction> candidates) {
ArrayListMultimap<TransactionOutPoint, Transaction> map = ArrayListMultimap.create();
// "spends" <o, tx> pair to the map.
for (Transaction tx : candidates) {
for (TransactionInput input : tx.getInputs()) {
TransactionOutPoint outpoint = input.getOutpoint();
map.put(outpoint, tx);
}
}
// Now clear <o, list(tx)> pairs where the sizeof(list) == 1 (i.e., no double spend)
Map<TransactionOutPoint, List<Transaction>> doubleSpends = Multimaps.asMap(map);
Iterator<List<Transaction>> it = doubleSpends.values().iterator();
while (it.hasNext()) {
int numSpends = it.next().size();
if (numSpends < 2) {
it.remove();
}
}
return doubleSpends;
}
use of org.bitcoinj.core.TransactionInput in project catena-java by alinush.
the class CatenaUtils method isCatenaTxHelper.
/**
* Returns true if the specified tx is a valid Catena TX created from the
* UTXO in prevLink and signed by chainAddr.
* If prevLink is null, then the previous UTXO is not checked.
* If chainAddr is null, then the chain's address is not checked.
*
* @param tx
* @param chainAddr
* @param prevLink
* @return
*/
private static boolean isCatenaTxHelper(Transaction tx, boolean verifySig, Address chainAddr, TransactionOutput prevLink, boolean checkPrevLinkIndex) {
checkNotNull(tx);
log.trace("Inspecting TX " + tx);
String txid = tx.getHashAsString();
NetworkParameters params = tx.getParams();
// Check if this TX is properly connected to the previous TX's (unique) UTXO
if (prevLink != null) {
// Check the prev TX's output is #0
if (checkPrevLinkIndex && prevLink.getIndex() != 0) {
log.warn("Index of UTXO '" + prevLink.getOutPointFor() + "' was '" + prevLink.getIndex() + "' but expected index 0 (w.r.t. to txid=" + txid + ")");
return false;
}
if (checkConnectedTo(tx, prevLink) == false)
return false;
// Check that the address in the previous output matches the one provided to this call
Address prevLinkAddr = prevLink.getAddressFromP2PKHScript(tx.getParams());
if (chainAddr != null && prevLinkAddr.equals(chainAddr) == false) {
log.warn("Address in UTXO '" + prevLink.getOutPointFor() + "' was '" + prevLinkAddr + "' but expected chain address '" + chainAddr + "' (w.r.t. to txid=" + txid + ")");
return false;
}
// Verify the signature on the first input
if (verifySig) {
TransactionInput firstInput = tx.getInput(0);
try {
firstInput.verify();
} catch (ScriptException e) {
log.warn("TX '" + txid + "' has invalid signature: " + Throwables.getStackTraceAsString(e));
return false;
} catch (VerificationException e) {
log.warn("TX '" + txid + "' has invalid format: " + Throwables.getStackTraceAsString(e));
return false;
} catch (Throwable e) {
log.warn("TX '" + txid + "' unknown signature verification error: " + Throwables.getStackTraceAsString(e));
return false;
}
}
}
// Make sure we have only one input
if (tx.getInputs().size() != 1) {
log.warn("expected only one input in tx '" + txid + "', got " + tx.getInputs().size());
return false;
}
// Make sure we have only two outputs (data + next)
if (tx.getOutputs().size() != 2) {
log.warn("expected two outputs in tx '" + txid + "' (continuation and OP_RETURN), got " + tx.getOutputs().size());
return false;
}
// Make sure chain's address is correct in first output
Address firstOutputAddr = tx.getOutput(0).getAddressFromP2PKHScript(params);
if (chainAddr != null && !firstOutputAddr.equals(chainAddr)) {
log.warn("first output address of '" + txid + "' was '" + firstOutputAddr + "'; expected chain address '" + chainAddr + "'");
return false;
}
// Make sure 2nd output is an OP_RETURN
Script secondOutput = tx.getOutput(1).getScriptPubKey();
if (!secondOutput.isOpReturn()) {
log.warn("second output of '" + txid + "' was supposed to be an OP_RETURN, got '" + secondOutput + "'");
return false;
}
// All is well.
return true;
}
use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.
the class BtcWalletService method completePreparedCompensationRequestTx.
// /////////////////////////////////////////////////////////////////////////////////////////
// Public Methods
// /////////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////////////////////
// CompensationRequest tx
// /////////////////////////////////////////////////////////////////////////////////////////
public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException {
// (BsqFee)tx has following structure:
// inputs [1-n] BSQ inputs (fee)
// outputs [0-1] BSQ request fee change output (>= 2730 Satoshi)
// preparedCompensationRequestTx has following structure:
// inputs [1-n] BSQ inputs for request fee
// inputs [1-n] BTC inputs for BSQ issuance and miner fee
// outputs [0-1] BSQ request fee change output (>= 2730 Satoshi)
// outputs [1] Potentially BSQ issuance output (>= 2730 Satoshi)
// outputs [0-1] BTC change output from issuance and miner fee inputs (>= 2730 Satoshi)
// outputs [0-1] OP_RETURN with opReturnData and amount 0
// mining fee: BTC mining fee + burned BSQ fee
Transaction preparedTx = new Transaction(params);
// Copy inputs from BSQ fee tx
feeTx.getInputs().forEach(preparedTx::addInput);
int indexOfBtcFirstInput = feeTx.getInputs().size();
// Need to be first because issuance is not guaranteed to be valid and would otherwise burn change output!
// BSQ change outputs from BSQ fee inputs.
feeTx.getOutputs().forEach(preparedTx::addOutput);
// BSQ issuance output
preparedTx.addOutput(issuanceAmount, issuanceAddress);
// safety check counter to avoid endless loops
int counter = 0;
// estimated size of input sig
final int sigSizePerInput = 106;
// typical size for a tx with 3 inputs
int txSizeWithUnsignedInputs = 300;
final Coin txFeePerByte = feeService.getTxFeePerByte();
Address changeAddress = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
checkNotNull(changeAddress, "changeAddress must not be null");
final BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
final List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
final List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
int numInputs = preparedBsqTxInputs.size();
Transaction resultTx = null;
boolean isFeeOutsideTolerance;
do {
counter++;
if (counter >= 10) {
checkNotNull(resultTx, "resultTx must not be null");
log.error("Could not calculate the fee. Tx=" + resultTx);
break;
}
Transaction tx = new Transaction(params);
preparedBsqTxInputs.stream().forEach(tx::addInput);
preparedBsqTxOutputs.stream().forEach(tx::addOutput);
SendRequest sendRequest = SendRequest.forTx(tx);
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
sendRequest.coinSelector = coinSelector;
sendRequest.changeAddress = changeAddress;
wallet.completeTx(sendRequest);
resultTx = sendRequest.tx;
// add OP_RETURN output
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
} while (isFeeOutsideTolerance);
// Sign all BTC inputs
for (int i = indexOfBtcFirstInput; i < resultTx.getInputs().size(); i++) {
TransactionInput txIn = resultTx.getInputs().get(i);
checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet), "txIn.getConnectedOutput() is not in our wallet. That must not happen.");
signTransactionInput(wallet, aesKey, resultTx, txIn, i);
checkScriptSig(resultTx, txIn, i);
}
checkWalletConsistency(wallet);
verifyTransaction(resultTx);
printTx("BTC wallet: Signed tx", resultTx);
return resultTx;
}
use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.
the class BtcWalletService method completePreparedVoteRevealTx.
// /////////////////////////////////////////////////////////////////////////////////////////
// MyVote reveal tx
// /////////////////////////////////////////////////////////////////////////////////////////
// TODO is same as blind vote tx
public Transaction completePreparedVoteRevealTx(Transaction feeTx, byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException {
// (BsqFee)tx has following structure:
// inputs [1] BSQ inputs (stake)
// inputs [1-n] BSQ inputs (fee)
// outputs [1] BSQ unlocked stake
// outputs [0-1] BSQ change output (>= 2730 Satoshi)
// preparedVoteTx has following structure:
// inputs [1] BSQ inputs (stake)
// inputs [1-n] BSQ inputs (fee)
// inputs [1-n] BTC inputs for miner fee
// outputs [1] BSQ unlocked stake
// outputs [0-1] BSQ change output (>= 2730 Satoshi)
// outputs [0-1] BTC change output from miner fee inputs (>= 2730 Satoshi)
// outputs [0-1] OP_RETURN with opReturnData and amount 0
// mining fee: BTC mining fee + burned BSQ fee
Transaction preparedTx = new Transaction(params);
// Copy inputs from BSQ fee tx
feeTx.getInputs().forEach(preparedTx::addInput);
int indexOfBtcFirstInput = feeTx.getInputs().size();
// BSQ change outputs from BSQ fee inputs.
feeTx.getOutputs().forEach(preparedTx::addOutput);
// safety check counter to avoid endless loops
int counter = 0;
// estimated size of input sig
final int sigSizePerInput = 106;
// typical size for a tx with 3 inputs
int txSizeWithUnsignedInputs = 300;
final Coin txFeePerByte = feeService.getTxFeePerByte();
Address changeAddress = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
checkNotNull(changeAddress, "changeAddress must not be null");
final BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
final List<TransactionInput> preparedBsqTxInputs = preparedTx.getInputs();
final List<TransactionOutput> preparedBsqTxOutputs = preparedTx.getOutputs();
int numInputs = preparedBsqTxInputs.size();
Transaction resultTx = null;
boolean isFeeOutsideTolerance;
do {
counter++;
if (counter >= 10) {
checkNotNull(resultTx, "resultTx must not be null");
log.error("Could not calculate the fee. Tx=" + resultTx);
break;
}
Transaction tx = new Transaction(params);
preparedBsqTxInputs.forEach(tx::addInput);
preparedBsqTxOutputs.forEach(tx::addOutput);
SendRequest sendRequest = SendRequest.forTx(tx);
sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey;
// signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
sendRequest.signInputs = false;
sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
sendRequest.feePerKb = Coin.ZERO;
sendRequest.ensureMinRequiredFee = false;
sendRequest.coinSelector = coinSelector;
sendRequest.changeAddress = changeAddress;
wallet.completeTx(sendRequest);
resultTx = sendRequest.tx;
// add OP_RETURN output
resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
numInputs = resultTx.getInputs().size();
txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
// calculated fee must be inside of a tolerance range with tx fee
isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
} while (isFeeOutsideTolerance);
// Sign all BTC inputs
for (int i = indexOfBtcFirstInput; i < resultTx.getInputs().size(); i++) {
TransactionInput txIn = resultTx.getInputs().get(i);
checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet), "txIn.getConnectedOutput() is not in our wallet. That must not happen.");
signTransactionInput(wallet, aesKey, resultTx, txIn, i);
checkScriptSig(resultTx, txIn, i);
}
checkWalletConsistency(wallet);
verifyTransaction(resultTx);
printTx("BTC wallet: Signed tx", resultTx);
return resultTx;
}
Aggregations