stability fixes on tor

optimize when multisig info imported
fetch updates for tx progress indicators off main thread
add synchronization locks
refactor address entry management
add totalTxFee to process model
prevent same user from taking same offer at same time
set refresh rate to 30s for tor
This commit is contained in:
woodser 2023-04-06 16:51:12 -04:00
parent 36cf91e093
commit 1b753e4f29
33 changed files with 498 additions and 354 deletions

View file

@ -20,6 +20,7 @@ package haveno.desktop.components;
import com.jfoenix.controls.JFXTextField;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import haveno.common.UserThread;
import haveno.common.util.Utilities;
import haveno.core.locale.Res;
import haveno.core.user.BlockChainExplorer;
@ -135,12 +136,14 @@ public class TxIdTextField extends AnchorPane {
};
xmrWalletService.addWalletListener(txUpdater);
updateConfidence(txId, true, null);
textField.setText(txId);
textField.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId));
blockExplorerIcon.setOnMouseClicked(mouseEvent -> openBlockExplorer(txId));
copyIcon.setOnMouseClicked(e -> Utilities.copyToClipboard(txId));
txConfidenceIndicator.setVisible(true);
// update off main thread
new Thread(() -> updateConfidence(txId, true, null)).start();
}
public void cleanup() {
@ -165,7 +168,7 @@ public class TxIdTextField extends AnchorPane {
}
}
private void updateConfidence(String txId, boolean useCache, Long height) {
private synchronized void updateConfidence(String txId, boolean useCache, Long height) {
MoneroTx tx = null;
try {
tx = useCache ? xmrWalletService.getTxWithCache(txId) : xmrWalletService.getTx(txId);
@ -173,14 +176,19 @@ public class TxIdTextField extends AnchorPane {
} catch (Exception e) {
// do nothing
}
GUIUtil.updateConfidence(tx, progressIndicatorTooltip, txConfidenceIndicator);
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;
}
updateConfidence(tx);
}
private void updateConfidence(MoneroTx tx) {
UserThread.execute(() -> {
GUIUtil.updateConfidence(tx, progressIndicatorTooltip, txConfidenceIndicator);
if (txConfidenceIndicator.getProgress() != 0) {
AnchorPane.setRightAnchor(txConfidenceIndicator, 0.0);
}
if (txConfidenceIndicator.getProgress() >= 1.0 && txUpdater != null) {
xmrWalletService.removeWalletListener(txUpdater); // unregister listener
txUpdater = null;
}
});
}
}

View file

@ -42,6 +42,7 @@
package haveno.desktop.components.indicator;
import haveno.common.UserThread;
import haveno.desktop.components.indicator.skin.StaticProgressIndicatorSkin;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
@ -220,7 +221,7 @@ public class TxConfidenceIndicator extends Control {
*/
public final void setProgress(double value) {
progressProperty().set(value);
UserThread.execute(() -> progressProperty().set(value));
}
public final DoubleProperty progressProperty() {

View file

@ -373,44 +373,52 @@ public class OfferBookChartView extends ActivatableViewAndModel<VBox, OfferBookC
}
private List<Double> minMaxFilterLeft(List<XYChart.Data<Number, Number>> data) {
double maxValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.max()
.orElse(Double.MIN_VALUE);
// Hide offers less than a div-factor of dataLimitFactor lower than the highest offer.
double minValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.filter(o -> o > maxValue / dataLimitFactor)
.min()
.orElse(Double.MAX_VALUE);
return List.of(minValue, maxValue);
synchronized (data) {
double maxValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.max()
.orElse(Double.MIN_VALUE);
// Hide offers less than a div-factor of dataLimitFactor lower than the highest offer.
double minValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.filter(o -> o > maxValue / dataLimitFactor)
.min()
.orElse(Double.MAX_VALUE);
return List.of(minValue, maxValue);
}
}
private List<Double> minMaxFilterRight(List<XYChart.Data<Number, Number>> data) {
double minValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.min()
.orElse(Double.MAX_VALUE);
synchronized (data) {
double minValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.min()
.orElse(Double.MAX_VALUE);
// Hide offers a dataLimitFactor factor higher than the lowest offer
double maxValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.filter(o -> o < minValue * dataLimitFactor)
.max()
.orElse(Double.MIN_VALUE);
return List.of(minValue, maxValue);
// Hide offers a dataLimitFactor factor higher than the lowest offer
double maxValue = data.stream()
.mapToDouble(o -> o.getXValue().doubleValue())
.filter(o -> o < minValue * dataLimitFactor)
.max()
.orElse(Double.MIN_VALUE);
return List.of(minValue, maxValue);
}
}
private List<XYChart.Data<Number, Number>> filterLeft(List<XYChart.Data<Number, Number>> data, double maxValue) {
return data.stream()
.filter(o -> o.getXValue().doubleValue() > maxValue / dataLimitFactor)
.collect(Collectors.toList());
synchronized (data) {
return data.stream()
.filter(o -> o.getXValue().doubleValue() > maxValue / dataLimitFactor)
.collect(Collectors.toList());
}
}
private List<XYChart.Data<Number, Number>> filterRight(List<XYChart.Data<Number, Number>> data, double minValue) {
return data.stream()
.filter(o -> o.getXValue().doubleValue() < minValue * dataLimitFactor)
.collect(Collectors.toList());
synchronized (data) {
return data.stream()
.filter(o -> o.getXValue().doubleValue() < minValue * dataLimitFactor)
.collect(Collectors.toList());
}
}
private Tuple4<TableView<OfferListItem>, VBox, Button, Label> getOfferTable(OfferDirection direction) {

View file

@ -389,24 +389,26 @@ class OfferBookChartViewModel extends ActivatableViewModel {
OfferDirection direction,
List<XYChart.Data<Number, Number>> data,
ObservableList<OfferListItem> offerTableList) {
data.clear();
double accumulatedAmount = 0;
List<OfferListItem> offerTableListTemp = new ArrayList<>();
for (Offer offer : sortedList) {
Price price = offer.getPrice();
if (price != null) {
double amount = (double) offer.getAmount().longValueExact() / LongMath.pow(10, HavenoUtils.XMR_SMALLEST_UNIT_EXPONENT);
accumulatedAmount += amount;
offerTableListTemp.add(new OfferListItem(offer, accumulatedAmount));
double priceAsDouble = (double) price.getValue() / LongMath.pow(10, price.smallestUnitExponent());
if (direction.equals(OfferDirection.BUY))
data.add(0, new XYChart.Data<>(priceAsDouble, accumulatedAmount));
else
data.add(new XYChart.Data<>(priceAsDouble, accumulatedAmount));
synchronized (data) {
data.clear();
double accumulatedAmount = 0;
List<OfferListItem> offerTableListTemp = new ArrayList<>();
for (Offer offer : sortedList) {
Price price = offer.getPrice();
if (price != null) {
double amount = (double) offer.getAmount().longValueExact() / LongMath.pow(10, HavenoUtils.XMR_SMALLEST_UNIT_EXPONENT);
accumulatedAmount += amount;
offerTableListTemp.add(new OfferListItem(offer, accumulatedAmount));
double priceAsDouble = (double) price.getValue() / LongMath.pow(10, price.smallestUnitExponent());
if (direction.equals(OfferDirection.BUY))
data.add(0, new XYChart.Data<>(priceAsDouble, accumulatedAmount));
else
data.add(new XYChart.Data<>(priceAsDouble, accumulatedAmount));
}
}
offerTableList.setAll(offerTableListTemp);
}
offerTableList.setAll(offerTableListTemp);
}
private boolean isEditEntry(String id) {

View file

@ -66,6 +66,8 @@ import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkNotNull;
@ -111,6 +113,8 @@ public abstract class TradeStepView extends AnchorPane {
trade = model.dataModel.getTrade();
checkNotNull(trade, "Trade must not be null at TradeStepView");
startCachingTxs();
ScrollPane scrollPane = new ScrollPane();
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
@ -166,16 +170,25 @@ public abstract class TradeStepView extends AnchorPane {
// };
}
private void startCachingTxs() {
List<String> txIds = new ArrayList<String>();
if (!model.dataModel.makerTxId.isEmpty().get()) txIds.add(model.dataModel.makerTxId.get());
if (!model.dataModel.takerTxId.isEmpty().get()) txIds.add(model.dataModel.takerTxId.get());
new Thread(() -> trade.getXmrWalletService().getTxsWithCache(txIds)).start();
}
public void activate() {
if (selfTxIdTextField != null) {
if (selfTxIdSubscription != null)
selfTxIdSubscription.unsubscribe();
selfTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.makerTxId : model.dataModel.takerTxId, id -> {
if (!id.isEmpty())
if (!id.isEmpty()) {
startCachingTxs();
selfTxIdTextField.setup(id);
else
} else {
selfTxIdTextField.cleanup();
}
});
}
if (peerTxIdTextField != null) {
@ -183,10 +196,12 @@ public abstract class TradeStepView extends AnchorPane {
peerTxIdSubscription.unsubscribe();
peerTxIdSubscription = EasyBind.subscribe(model.dataModel.isMaker() ? model.dataModel.takerTxId : model.dataModel.makerTxId, id -> {
if (!id.isEmpty())
if (!id.isEmpty()) {
startCachingTxs();
peerTxIdTextField.setup(id);
else
} else {
peerTxIdTextField.cleanup();
}
});
}
trade.errorMessageProperty().addListener(errorMessageListener);

View file

@ -223,7 +223,7 @@ public class BuyerStep2View extends TradeStepView {
addTradeInfoBlock();
PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
String paymentMethodId = paymentAccountPayload != null ? paymentAccountPayload.getPaymentMethodId() : "";
String paymentMethodId = paymentAccountPayload != null ? paymentAccountPayload.getPaymentMethodId() : "<missing payment account payload>";
TitledGroupBg accountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 4,
Res.get("portfolio.pending.step2_buyer.startPaymentUsing", Res.get(paymentMethodId)),
Layout.COMPACT_GROUP_DISTANCE);