use of org.bitcoinj.core.Transaction in project bisq-core by bisq-network.
the class TxBroadcaster method broadcastTx.
public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction tx, Callback callback, int delayInSec) {
Timer timeoutTimer;
final String txId = tx.getHashAsString();
if (!broadcastTimerMap.containsKey(txId)) {
timeoutTimer = UserThread.runAfter(() -> {
log.warn("Broadcast of tx {} not completed after {} sec.", txId, delayInSec);
stopAndRemoveTimer(txId);
UserThread.execute(() -> callback.onTimeout(new TxBroadcastTimeoutException(tx, delayInSec, wallet)));
}, delayInSec);
broadcastTimerMap.put(txId, timeoutTimer);
} else {
// Would be due a wrong way how to use the API (calling 2 times a broadcast with same tx).
stopAndRemoveTimer(txId);
UserThread.execute(() -> callback.onFailure(new TxBroadcastException("We got broadcastTx called with a tx " + "which has an open timeoutTimer. txId=" + txId, txId)));
}
Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction result) {
if (result != null) {
if (txId.equals(result.getHashAsString())) {
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
if (broadcastTimerMap.containsKey(txId)) {
wallet.maybeCommitTx(tx);
stopAndRemoveTimer(txId);
// At regtest we get called immediately back but we want to make sure that the handler is not called
// before the caller is finished.
UserThread.execute(() -> callback.onSuccess(tx));
} else {
stopAndRemoveTimer(txId);
log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
}
} else {
stopAndRemoveTimer(txId);
UserThread.execute(() -> callback.onTxMalleability(new TxMalleabilityException(tx, result)));
}
} else {
stopAndRemoveTimer(txId);
UserThread.execute(() -> callback.onFailure(new TxBroadcastException("Transaction returned from the " + "broadcastTransaction call back is null.", txId)));
}
}
@Override
public void onFailure(@NotNull Throwable throwable) {
stopAndRemoveTimer(txId);
UserThread.execute(() -> callback.onFailure(new TxBroadcastException("We got an onFailure from " + "the peerGroup.broadcastTransaction callback.", throwable)));
}
});
}
use of org.bitcoinj.core.Transaction in project bisq-core by bisq-network.
the class DisputeManager method onDisputedPayoutTxMessage.
// Losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer
private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerPublishedDisputePayoutTxMessage) {
final String uid = peerPublishedDisputePayoutTxMessage.getUid();
final String tradeId = peerPublishedDisputePayoutTxMessage.getTradeId();
Optional<Dispute> disputeOptional = findOwnDispute(tradeId);
if (!disputeOptional.isPresent()) {
log.debug("We got a peerPublishedPayoutTxMessage but we don't have a matching dispute. TradeId = " + tradeId);
if (!delayMsgMap.containsKey(uid)) {
// We delay 3 sec. to be sure the close msg gets added first
Timer timer = UserThread.runAfter(() -> onDisputedPayoutTxMessage(peerPublishedDisputePayoutTxMessage), 3);
delayMsgMap.put(uid, timer);
} else {
log.warn("We got a peerPublishedPayoutTxMessage after we already repeated to apply the message after a delay. " + "That should never happen. TradeId = " + tradeId);
}
return;
}
Dispute dispute = disputeOptional.get();
final Contract contract = dispute.getContract();
PubKeyRing ownPubKeyRing = keyRing.getPubKeyRing();
boolean isBuyer = ownPubKeyRing.equals(contract.getBuyerPubKeyRing());
PubKeyRing peersPubKeyRing = isBuyer ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
cleanupRetryMap(uid);
Transaction walletTx = tradeWalletService.addTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction());
dispute.setDisputePayoutTxId(walletTx.getHashAsString());
BtcWalletService.printTx("Disputed payoutTx received from peer", walletTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
}
use of org.bitcoinj.core.Transaction in project bisq-core by bisq-network.
the class DisputeManager method onDisputeResultMessage.
// We get that message at both peers. The dispute object is in context of the trader
private void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) {
String errorMessage = null;
boolean success = false;
PubKeyRing arbitratorsPubKeyRing = null;
DisputeResult disputeResult = disputeResultMessage.getDisputeResult();
if (isArbitrator(disputeResult)) {
log.error("Arbitrator received disputeResultMessage. That must never happen.");
return;
}
final String tradeId = disputeResult.getTradeId();
Optional<Dispute> disputeOptional = findDispute(tradeId, disputeResult.getTraderId());
final String uid = disputeResultMessage.getUid();
if (!disputeOptional.isPresent()) {
log.debug("We got a dispute result msg but we don't have a matching dispute. " + "That might happen when we get the disputeResultMessage before the dispute was created. " + "We try again after 2 sec. to apply the disputeResultMessage. TradeId = " + tradeId);
if (!delayMsgMap.containsKey(uid)) {
// We delay2 sec. to be sure the comm. msg gets added first
Timer timer = UserThread.runAfter(() -> onDisputeResultMessage(disputeResultMessage), 2);
delayMsgMap.put(uid, timer);
} else {
log.warn("We got a dispute result msg after we already repeated to apply the message after a delay. " + "That should never happen. TradeId = " + tradeId);
}
return;
}
try {
cleanupRetryMap(uid);
Dispute dispute = disputeOptional.get();
arbitratorsPubKeyRing = dispute.getArbitratorPubKeyRing();
DisputeCommunicationMessage disputeCommunicationMessage = disputeResult.getDisputeCommunicationMessage();
if (!dispute.getDisputeCommunicationMessages().contains(disputeCommunicationMessage))
dispute.addDisputeCommunicationMessage(disputeCommunicationMessage);
else if (disputeCommunicationMessage != null)
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + disputeCommunicationMessage.getTradeId());
dispute.setIsClosed(true);
if (dispute.disputeResultProperty().get() != null)
log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " + "again because the first close did not succeed. TradeId = " + tradeId);
dispute.setDisputeResult(disputeResult);
// We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals
// There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb)
// The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives
// more BTC as he has deposited
final Contract contract = dispute.getContract();
boolean isBuyer = keyRing.getPubKeyRing().equals(contract.getBuyerPubKeyRing());
DisputeResult.Winner publisher = disputeResult.getWinner();
// Default isLoserPublisher is set to false
if (disputeResult.isLoserPublisher()) {
// we invert the logic
if (publisher == DisputeResult.Winner.BUYER)
publisher = DisputeResult.Winner.SELLER;
else if (publisher == DisputeResult.Winner.SELLER)
publisher = DisputeResult.Winner.BUYER;
}
if ((isBuyer && publisher == DisputeResult.Winner.BUYER) || (!isBuyer && publisher == DisputeResult.Winner.SELLER)) {
final Optional<Trade> tradeOptional = tradeManager.getTradeById(tradeId);
Transaction payoutTx = null;
if (tradeOptional.isPresent()) {
payoutTx = tradeOptional.get().getPayoutTx();
} else {
final Optional<Tradable> tradableOptional = closedTradableManager.getTradableById(tradeId);
if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) {
payoutTx = ((Trade) tradableOptional.get()).getPayoutTx();
}
}
if (payoutTx == null) {
if (dispute.getDepositTxSerialized() != null) {
byte[] multiSigPubKey = isBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(dispute.getTradeId(), multiSigPubKey);
Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx(dispute.getDepositTxSerialized(), disputeResult.getArbitratorSignature(), disputeResult.getBuyerPayoutAmount(), disputeResult.getSellerPayoutAmount(), contract.getBuyerPayoutAddressString(), contract.getSellerPayoutAddressString(), multiSigKeyPair, contract.getBuyerMultiSigPubKey(), contract.getSellerMultiSigPubKey(), disputeResult.getArbitratorPubKey());
Transaction committedDisputedPayoutTx = tradeWalletService.addTxToWallet(signedDisputedPayoutTx);
tradeWalletService.broadcastTx(committedDisputedPayoutTx, new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {
// after successful publish we send peer the tx
dispute.setDisputePayoutTxId(transaction.getHashAsString());
sendPeerPublishedPayoutTxMessage(transaction, dispute, contract);
// set state after payout as we call swapTradeEntryToAvailableEntry
if (tradeManager.getTradeById(dispute.getTradeId()).isPresent())
tradeManager.closeDisputedTrade(dispute.getTradeId());
else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId());
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
}
}
@Override
public void onFailure(TxBroadcastException exception) {
log.error(exception.getMessage());
}
}, 15);
success = true;
} else {
errorMessage = "DepositTx is null. TradeId = " + tradeId;
log.warn(errorMessage);
success = false;
}
} else {
log.warn("We got already a payout tx. That might be the case if the other peer did not get the " + "payout tx and opened a dispute. TradeId = " + tradeId);
dispute.setDisputePayoutTxId(payoutTx.getHashAsString());
sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract);
success = true;
}
} else {
log.trace("We don't publish the tx as we are not the winning party.");
// Clean up tangling trades
if (dispute.disputeResultProperty().get() != null && dispute.isClosed() && tradeManager.getTradeById(dispute.getTradeId()).isPresent()) {
tradeManager.closeDisputedTrade(dispute.getTradeId());
}
success = true;
}
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
e.printStackTrace();
errorMessage = "Error at traderSignAndFinalizeDisputedPayoutTx " + e.toString();
log.error(errorMessage);
success = false;
throw new RuntimeException(errorMessage);
} finally {
if (arbitratorsPubKeyRing != null) {
// We use the disputeCommunicationMessage as we only persist those not the disputeResultMessage.
// If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage.
DisputeCommunicationMessage disputeCommunicationMessage = disputeResultMessage.getDisputeResult().getDisputeCommunicationMessage();
sendAckMessage(disputeCommunicationMessage, arbitratorsPubKeyRing, success, errorMessage);
}
}
}
use of org.bitcoinj.core.Transaction in project bisq-core by bisq-network.
the class BsqWalletService method getPreparedUnlockTx.
// /////////////////////////////////////////////////////////////////////////////////////////
// Unlock bond tx
// /////////////////////////////////////////////////////////////////////////////////////////
public Transaction getPreparedUnlockTx(TxOutput lockedTxOutput) throws AddressFormatException {
Transaction tx = new Transaction(params);
// Unlocking means spending the full value of the locked txOutput to another txOutput with the same value
Coin amountToUnlock = Coin.valueOf(lockedTxOutput.getValue());
checkArgument(Restrictions.isAboveDust(amountToUnlock), "The amount is too low (dust limit).");
Transaction lockupTx = getTransaction(lockedTxOutput.getTxId());
checkNotNull(lockupTx, "lockupTx must not be null");
TransactionOutPoint outPoint = new TransactionOutPoint(params, lockedTxOutput.getIndex(), lockupTx);
// Input is not signed yet so we use new byte[]{}
tx.addInput(new TransactionInput(params, tx, new byte[] {}, outPoint, amountToUnlock));
tx.addOutput(new TransactionOutput(params, tx, amountToUnlock, getUnusedAddress()));
printTx("prepareUnlockTx", tx);
return tx;
}
use of org.bitcoinj.core.Transaction in project bisq-core by bisq-network.
the class BsqWalletService method getPreparedVoteRevealTx.
// /////////////////////////////////////////////////////////////////////////////////////////
// MyVote reveal tx
// /////////////////////////////////////////////////////////////////////////////////////////
public Transaction getPreparedVoteRevealTx(TxOutput stakeTxOutput) {
Transaction tx = new Transaction(params);
final Coin stake = Coin.valueOf(stakeTxOutput.getValue());
Transaction blindVoteTx = getTransaction(stakeTxOutput.getTxId());
checkNotNull(blindVoteTx, "blindVoteTx must not be null");
TransactionOutPoint outPoint = new TransactionOutPoint(params, stakeTxOutput.getIndex(), blindVoteTx);
// Input is not signed yet so we use new byte[]{}
tx.addInput(new TransactionInput(params, tx, new byte[] {}, outPoint, stake));
tx.addOutput(new TransactionOutput(params, tx, stake, getUnusedAddress()));
// printTx("getPreparedVoteRevealTx", tx);
return tx;
}
Aggregations