fix confirmation progress in ui

fix my role label to show during trade initialization
support arbitrator role label
This commit is contained in:
woodser 2022-11-05 13:10:18 -04:00
parent 247087ef46
commit 4fb62d8669
13 changed files with 60 additions and 277 deletions

View file

@ -340,7 +340,6 @@ public class XmrWalletService {
// freeze deposit inputs
for (MoneroOutput input : depositTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
wallet.save();
return depositTx;
}
}
@ -441,10 +440,8 @@ public class XmrWalletService {
}
public MoneroTx getTx(String txHash) {
synchronized (txCache) {
List<MoneroTx> txs = getTxs(Arrays.asList(txHash));
return txs.isEmpty() ? null : txs.get(0);
}
List<MoneroTx> txs = getTxs(Arrays.asList(txHash));
return txs.isEmpty() ? null : txs.get(0);
}
public List<MoneroTx> getTxs(List<String> txHashes) {
@ -461,16 +458,14 @@ public class XmrWalletService {
synchronized (txCache) {
for (MoneroTx tx : txs) txCache.remove(tx.getHash());
}
}, connectionsService.getDefaultRefreshPeriodMs());
}, connectionsService.getDefaultRefreshPeriodMs() / 1000);
return txs;
}
}
public MoneroTx getTxWithCache(String txHash) {
synchronized (txCache) {
List<MoneroTx> cachedTxs = getTxsWithCache(Arrays.asList(txHash));
return cachedTxs.isEmpty() ? null : cachedTxs.get(0);
}
List<MoneroTx> cachedTxs = getTxsWithCache(Arrays.asList(txHash));
return cachedTxs.isEmpty() ? null : cachedTxs.get(0);
}
public List<MoneroTx> getTxsWithCache(List<String> txHashes) {

View file

@ -180,19 +180,14 @@ public class TradeUtil {
* @return String describing a trader's role for a given trade
*/
public String getRole(Trade trade) {
Contract contract = trade.getContract();
if (contract == null)
throw new IllegalStateException(format("could not get role because no contract was found for trade '%s'",
trade.getShortId()));
Offer offer = trade.getOffer();
if (offer == null)
throw new IllegalStateException(format("could not get role because no offer was found for trade '%s'",
trade.getShortId()));
return getRole(contract.isBuyerMakerAndSellerTaker(),
offer.isMyOffer(keyRing),
offer.getCurrencyCode());
return (trade.isArbitrator() ? "Arbitrator for " : "") + // TODO: use Res.get()
getRole(trade.getBuyer() == trade.getMaker(),
trade.isArbitrator() ? true : trade.isMaker(), // arbitrator role in context of maker
offer.getCurrencyCode());
}
/**

View file

@ -103,7 +103,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
// relay txs
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
daemon.submitTxHex(processModel.getMaker().getDepositTxHex()); // TODO (woodser): check that result is good. will need to release funds if one is submitted
daemon.submitTxHex(processModel.getMaker().getDepositTxHex()); // TODO (woodser): check that result is good. will need to release funds if one is submitted
daemon.submitTxHex(processModel.getTaker().getDepositTxHex());
// update trade state

View file

@ -85,7 +85,7 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
null);
// send request to maker
log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing());
log.info("Send {} with offerId {} and uid {} to maker {}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMaker().getNodeAddress());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getMaker().getNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received
trade.getMaker().getPubKeyRing(),

View file

@ -1,179 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.core.btc.listeners.AddressConfidenceListener;
import bisq.core.trade.Trade;
import bisq.common.taskrunner.TaskRunner;
import org.fxmisc.easybind.Subscription;
import lombok.extern.slf4j.Slf4j;
// TODO (woodser): adapt to XMR or remove
@Slf4j
public class BuyerSetupDepositTxListener extends TradeTask {
// Use instance fields to not get eaten up by the GC
private Subscription tradeStateSubscription;
private AddressConfidenceListener confidenceListener;
public BuyerSetupDepositTxListener(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
throw new RuntimeException("BuyerSetupDepositTxListener needs updated for XMR");
// try {
// runInterceptHook();
//
// if (trade.getDepositTx() == null && processModel.getPreparedDepositTx() != null) {
// BtcWalletService walletService = processModel.getBtcWalletService();
// NetworkParameters params = walletService.getParams();
// Transaction preparedDepositTx = new Transaction(params, processModel.getPreparedDepositTx());
// checkArgument(!preparedDepositTx.getOutputs().isEmpty(), "preparedDepositTx.getOutputs() must not be empty");
// Address depositTxAddress = preparedDepositTx.getOutput(0).getScriptPubKey().getToAddress(params);
//
// // For buyer as maker takerFeeTxId is null
// @Nullable String takerFeeTxId = trade.getTakerFeeTxId();
// String makerFeeTxId = trade.getOffer().getOfferFeePaymentTxId();
// TransactionConfidence confidence = walletService.getConfidenceForAddress(depositTxAddress);
// if (isConfTxDepositTx(confidence, params, depositTxAddress, takerFeeTxId, makerFeeTxId) &&
// isVisibleInNetwork(confidence)) {
// applyConfidence(confidence);
// } else {
// confidenceListener = new AddressConfidenceListener(depositTxAddress) {
// @Override
// public void onTransactionConfidenceChanged(TransactionConfidence confidence) {
// if (isConfTxDepositTx(confidence, params, depositTxAddress,
// takerFeeTxId, makerFeeTxId) && isVisibleInNetwork(confidence)) {
// applyConfidence(confidence);
// }
// }
// };
// walletService.addAddressConfidenceListener(confidenceListener);
//
// tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
// if (trade.isDepositPublished()) {
// swapReservedForTradeEntry();
//
// // hack to remove tradeStateSubscription at callback
// UserThread.execute(this::unSubscribeAndRemoveListener);
// }
// });
// }
// }
//
// // we complete immediately, our object stays alive because the balanceListener is stored in the WalletService
// complete();
// } catch (Throwable t) {
// failed(t);
// }
}
// // We check if the txIds of the inputs matches our maker fee tx and taker fee tx and if the depositTxAddress we
// // use for the confidence lookup is use as an output address.
// // This prevents that past txs which have the our depositTxAddress as input or output (deposit or payout txs) could
// // be interpreted as our deposit tx. This happened because if a bug which caused re-use of the Multisig address
// // entries and if both traders use the same key for multiple trades the depositTxAddress would be the same.
// // We fix that bug as well but we also need to avoid that past already used addresses might be taken again
// // (the Multisig flag got reverted to available in the address entry).
// private boolean isConfTxDepositTx(@Nullable TransactionConfidence confidence,
// NetworkParameters params,
// Address depositTxAddress,
// @Nullable String takerFeeTxId,
// String makerFeeTxId) {
// if (confidence == null) {
// return false;
// }
//
// Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
// long numInputMatches = walletTx.getInputs().stream()
// .map(TransactionInput::getOutpoint)
// .filter(Objects::nonNull)
// .map(TransactionOutPoint::getHash)
// .map(Sha256Hash::toString)
// .filter(txId -> txId.equals(takerFeeTxId) || txId.equals(makerFeeTxId))
// .count();
// if (takerFeeTxId == null && numInputMatches != 1) {
// log.warn("We got a transactionConfidenceTx which does not match our inputs. " +
// "takerFeeTxId is null (valid if role is buyer as maker) and numInputMatches " +
// "is not 1 as expected (for makerFeeTxId). " +
// "numInputMatches={}, transactionConfidenceTx={}",
// numInputMatches, walletTx);
// return false;
// } else if (takerFeeTxId != null && numInputMatches != 2) {
// log.warn("We got a transactionConfidenceTx which does not match our inputs. " +
// "numInputMatches is not 2 as expected (for makerFeeTxId and takerFeeTxId). " +
// "numInputMatches={}, transactionConfidenceTx={}",
// numInputMatches, walletTx);
// return false;
// }
//
// boolean isOutputMatching = walletTx.getOutputs().stream()
// .map(transactionOutput -> transactionOutput.getScriptPubKey().getToAddress(params))
// .anyMatch(address -> address.equals(depositTxAddress));
// if (!isOutputMatching) {
// log.warn("We got a transactionConfidenceTx which does not has the depositTxAddress " +
// "as output (but as input). depositTxAddress={}, transactionConfidenceTx={}",
// depositTxAddress, walletTx);
// }
// return isOutputMatching;
// }
//
// private void applyConfidence(TransactionConfidence confidence) {
// if (trade.getDepositTx() == null) {
// Transaction walletTx = processModel.getTradeWalletService().getWalletTx(confidence.getTransactionHash());
// trade.applyDepositTx(walletTx);
// BtcWalletService.printTx("depositTx received from network", walletTx);
//
// // We don't want to trigger the tradeStateSubscription when setting the state, so we unsubscribe before
// unSubscribeAndRemoveListener();
// trade.setState(Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK);
//
// processModel.getTradeManager().requestPersistence();
// } else {
// unSubscribeAndRemoveListener();
// }
//
// swapReservedForTradeEntry();
// }
//
// private boolean isVisibleInNetwork(TransactionConfidence confidence) {
// return confidence != null &&
// (confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) ||
// confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING));
// }
//
// private void swapReservedForTradeEntry() {
// processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(trade.getId(),
// AddressEntry.Context.RESERVED_FOR_TRADE);
// }
//
// private void unSubscribeAndRemoveListener() {
// if (tradeStateSubscription != null) {
// tradeStateSubscription.unsubscribe();
// tradeStateSubscription = null;
// }
//
// if (confidenceListener != null) {
// processModel.getBtcWalletService().removeAddressConfidenceListener(confidenceListener);
// confidenceListener = null;
// }
// }
}

View file

@ -82,7 +82,7 @@ public class MakerSendInitTradeRequest extends TradeTask {
null);
// send request to arbitrator
log.info("Sending {} with offerId {} and uid {} to arbitrator {} with pub key ring {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing());
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
processModel.getP2PService().sendEncryptedDirectMessage(
trade.getArbitrator().getNodeAddress(),
trade.getArbitrator().getPubKeyRing(),

View file

@ -18,7 +18,6 @@
package bisq.core.trade.protocol.tasks;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.BuyerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositsConfirmedMessage;
import bisq.core.trade.messages.TradeMailboxMessage;

View file

@ -119,7 +119,7 @@ public class TakerSendInitTradeRequestToArbitrator extends TradeTask {
processModel.getMakerSignature());
// send request to arbitrator
log.info("Sending {} with offerId {} and uid {} to arbitrator {} with pub key ring {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing());
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
processModel.getP2PService().sendEncryptedDirectMessage(
arbitratorNodeAddress,
arbitrator.getPubKeyRing(),

View file

@ -30,7 +30,6 @@ import bisq.common.util.Utilities;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import java.math.BigInteger;
import com.jfoenix.controls.JFXTextField;
import javafx.scene.control.Label;
@ -135,17 +134,13 @@ public class TxIdTextField extends AnchorPane {
// TODO: this only listens for new blocks, listen for double spend
txUpdater = new MoneroWalletListener() {
@Override
public void onNewBlock(long height) {
updateConfidence(txId);
}
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
updateConfidence(txId);
public void onNewBlock(long lastBlockHeight) {
updateConfidence(txId, false, lastBlockHeight + 1);
}
};
xmrWalletService.addWalletListener(txUpdater);
updateConfidence(txId);
updateConfidence(txId, true, null);
textField.setText(txId);
textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId));
@ -175,31 +170,22 @@ public class TxIdTextField extends AnchorPane {
}
}
private void updateConfidence(String txId) {
private void updateConfidence(String txId, boolean useCache, Long height) {
MoneroTx tx = null;
try {
tx = xmrWalletService.getTxWithCache(txId);
tx.setNumConfirmations(tx.isConfirmed() ? xmrWalletService.getConnectionsService().getLastInfo().getHeight() - tx.getHeight() : 0l); // TODO: use tx.getNumConfirmations() when MoneroDaemonRpc supports it
tx = useCache ? xmrWalletService.getTxWithCache(txId) : xmrWalletService.getTx(txId);
tx.setNumConfirmations(tx.isConfirmed() ? (height == null ? xmrWalletService.getConnectionsService().getLastInfo().getHeight() : height) - tx.getHeight(): 0l); // TODO: don't set if tx.getNumConfirmations() works reliably on non-local testnet
} catch (Exception e) {
// do nothing
}
GUIUtil.updateConfidence(tx, progressIndicatorTooltip, txConfidenceIndicator);
if (tx != null) {
if (txConfidenceIndicator.getProgress() != 0) {
txConfidenceIndicator.setVisible(true);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
}
if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater); // unregister listener
txUpdater = null;
}
} else {
//TODO we should show some placeholder in case of a tx which we are not aware of but which can be
// confirmed already. This is for instance the case of the other peers trade fee tx, as it is not related
// to our wallet we don't have a confidence object but we should show that it is in an unknown state instead
// of not showing anything which causes confusion that the tx was not broadcasted. Best would be to request
// it from a block explorer service but that is a bit too heavy for that use case...
// Maybe a question mark with a tooltip explaining why we don't know about the confidence might be ok...
if (txConfidenceIndicator.getProgress() != 0) {
txConfidenceIndicator.setVisible(true);
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
}
if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater); // unregister listener
txUpdater = null;
}
}
}

View file

@ -376,18 +376,18 @@ public class PendingTradesDataModel extends ActivatableDataModel {
return;
}
MoneroTx makerDepositTx = selectedTrade.getMakerDepositTx();
MoneroTx takerDepositTx = selectedTrade.getTakerDepositTx();
String tradeId = selectedTrade.getId();
tradeStateChangeListener = (observable, oldValue, newValue) -> {
if (makerDepositTx != null && takerDepositTx != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
String makerDepositTxHash = selectedTrade.getMaker().getDepositTxHash();
String takerDepositTxHash = selectedTrade.getTaker().getDepositTxHash();
if (makerDepositTxHash != null && takerDepositTxHash != null) { // TODO (woodser): this treats separate deposit ids as one unit, being both available or unavailable
makerTxId.set(makerDepositTxHash);
takerTxId.set(takerDepositTxHash);
notificationCenter.setSelectedTradeId(tradeId);
selectedTrade.stateProperty().removeListener(tradeStateChangeListener);
} else {
makerTxId.set("");
takerTxId.set("");
makerTxId.set("");
takerTxId.set("");
}
};
selectedTrade.stateProperty().addListener(tradeStateChangeListener);
@ -399,9 +399,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
}
isMaker = tradeManager.isMyOffer(offer);
if (makerDepositTx != null && takerDepositTx != null) {
makerTxId.set(makerDepositTx.getHash());
takerTxId.set(takerDepositTx.getHash());
String makerDepositTxHash = selectedTrade.getMaker().getDepositTxHash();
String takerDepositTxHash = selectedTrade.getTaker().getDepositTxHash();
if (makerDepositTxHash != null && takerDepositTxHash != null) {
makerTxId.set(makerDepositTxHash);
takerTxId.set(takerDepositTxHash);
} else {
makerTxId.set("");
takerTxId.set("");

View file

@ -299,18 +299,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
//
String getMyRole(PendingTradesListItem item) {
Trade trade = item.getTrade();
Contract contract = trade.getContract();
if (contract != null) {
Offer offer = trade.getOffer();
checkNotNull(offer);
checkNotNull(offer.getCurrencyCode());
return tradeUtil.getRole(contract.isBuyerMakerAndSellerTaker(),
dataModel.isMaker(offer),
offer.getCurrencyCode());
} else {
return "";
}
return tradeUtil.getRole(item.getTrade());
}
String getPaymentMethod(PendingTradesListItem item) {
@ -425,7 +414,8 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
}
switch (tradeState) {
// preparation
// initialization
case PREPARATION:
case MULTISIG_PREPARED:
case MULTISIG_MADE:
@ -433,16 +423,13 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
case MULTISIG_COMPLETED:
case CONTRACT_SIGNATURE_REQUESTED:
case CONTRACT_SIGNED:
sellerState.set(UNDEFINED);
buyerState.set(BuyerState.UNDEFINED);
break;
// deposit requested
case SENT_PUBLISH_DEPOSIT_TX_REQUEST:
case SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST:
case SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST:
sellerState.set(UNDEFINED); // TODO: show view while trade initializes?
buyerState.set(BuyerState.UNDEFINED);
break;
// deposit published
case ARBITRATOR_PUBLISHED_DEPOSIT_TXS:
case DEPOSIT_TXS_SEEN_IN_NETWORK:
case DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN: // TODO: separate step to wait for first confirmation
@ -451,7 +438,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
break;
// buyer and seller step 2
// deposit unlocked
// deposits unlocked
case DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN:
sellerState.set(SellerState.STEP2);
buyerState.set(BuyerState.STEP2);

View file

@ -191,7 +191,7 @@ public abstract class TradeStepView extends AnchorPane {
if (peerTxIdSubscription != null)
peerTxIdSubscription.unsubscribe();
selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> {
peerTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> {
if (!id.isEmpty())
peerTxIdTextField.setup(id);
else

View file

@ -569,23 +569,21 @@ public class GUIUtil {
public static void updateConfidence(MoneroTx tx,
Tooltip tooltip,
TxConfidenceIndicator txConfidenceIndicator) {
if (tx != null) {
if (!tx.isRelayed()) {
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
} else if (tx.isFailed()) {
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
} else if (tx.isConfirmed()) {
tooltip.setText(Res.get("confidence.confirmed", tx.getNumConfirmations()));
txConfidenceIndicator.setProgress(Math.min(1, tx.getNumConfirmations() / (double) XmrWalletService.NUM_BLOCKS_UNLOCK));
} else {
tooltip.setText(Res.get("confidence.seen", 0)); // TODO: replace with numBroadcastPeers
txConfidenceIndicator.setProgress(-1.0);
}
txConfidenceIndicator.setPrefSize(24, 24);
if (tx != null && !tx.isRelayed()) {
tooltip.setText(Res.get("confidence.unknown"));
txConfidenceIndicator.setProgress(0);
} else if (tx != null && tx.isFailed()) {
tooltip.setText(Res.get("confidence.invalid"));
txConfidenceIndicator.setProgress(0);
} else if (tx != null && tx.isConfirmed()) {
tooltip.setText(Res.get("confidence.confirmed", tx.getNumConfirmations()));
txConfidenceIndicator.setProgress((double) tx.getNumConfirmations() / (double) XmrWalletService.NUM_BLOCKS_UNLOCK);
} else {
tooltip.setText(Res.get("confidence.seen", 0)); // TODO: replace with numBroadcastPeers
txConfidenceIndicator.setProgress(-1.0);
}
txConfidenceIndicator.setPrefSize(24, 24);
}
public static void openWebPage(String target) {