diff --git a/common/src/main/java/haveno/common/ThreadUtils.java b/common/src/main/java/haveno/common/ThreadUtils.java index 37abe8454a..832ea91ade 100644 --- a/common/src/main/java/haveno/common/ThreadUtils.java +++ b/common/src/main/java/haveno/common/ThreadUtils.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -41,10 +40,10 @@ public class ThreadUtils { * @param command the command to execute * @param threadId the thread id */ - public static void execute(Runnable command, String threadId) { + public static Future execute(Runnable command, String threadId) { synchronized (EXECUTORS) { if (!EXECUTORS.containsKey(threadId)) EXECUTORS.put(threadId, Executors.newFixedThreadPool(1)); - EXECUTORS.get(threadId).execute(() -> { + return EXECUTORS.get(threadId).submit(() -> { synchronized (THREADS) { THREADS.put(threadId, Thread.currentThread()); } @@ -60,24 +59,10 @@ public class ThreadUtils { * @param threadId the thread id */ public static void await(Runnable command, String threadId) { - if (isCurrentThread(Thread.currentThread(), threadId)) { - command.run(); - } else { - CountDownLatch latch = new CountDownLatch(1); - execute(() -> { - try { - command.run(); - } catch (Exception e) { - throw e; - } finally { - latch.countDown(); - } - }, threadId); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + try { + execute(command, threadId).get(); + } catch (Exception e) { + throw new RuntimeException(e); } } diff --git a/core/src/main/java/haveno/core/api/CoreWalletsService.java b/core/src/main/java/haveno/core/api/CoreWalletsService.java index a3713faa72..3b5dcefb7e 100644 --- a/core/src/main/java/haveno/core/api/CoreWalletsService.java +++ b/core/src/main/java/haveno/core/api/CoreWalletsService.java @@ -133,7 +133,6 @@ class CoreWalletsService { verifyWalletCurrencyCodeIsValid(currencyCode); verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); - if (balances.getAvailableBalance().get() == null) throw new IllegalStateException("balance is not yet available"); switch (currencyCode.trim().toUpperCase()) { case "": @@ -418,28 +417,8 @@ class CoreWalletsService { private XmrBalanceInfo getXmrBalances() { verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); - - var availableBalance = balances.getAvailableBalance().get(); - if (availableBalance == null) - throw new IllegalStateException("available balance is not yet available"); - - var pendingBalance = balances.getPendingBalance().get(); - if (pendingBalance == null) - throw new IllegalStateException("locked balance is not yet available"); - - var reservedOfferBalance = balances.getReservedOfferBalance().get(); - if (reservedOfferBalance == null) - throw new IllegalStateException("reserved offer balance is not yet available"); - - var reservedTradeBalance = balances.getReservedTradeBalance().get(); - if (reservedTradeBalance == null) - throw new IllegalStateException("reserved trade balance is not yet available"); - - return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(), - availableBalance.longValue(), - pendingBalance.longValue(), - reservedOfferBalance.longValue(), - reservedTradeBalance.longValue()); + if (balances.getAvailableBalance() == null) throw new IllegalStateException("Balances are not yet available"); + return balances.getBalances(); } // Returns a Coin for the transfer amount string, or a RuntimeException if invalid. diff --git a/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java b/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java index 1483e11235..2a9e23cd42 100644 --- a/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java +++ b/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java @@ -1,10 +1,10 @@ package haveno.core.api.model; +import java.math.BigInteger; + import com.google.common.annotations.VisibleForTesting; import haveno.common.Payload; -import lombok.Getter; -@Getter public class XmrBalanceInfo implements Payload { public static final XmrBalanceInfo EMPTY = new XmrBalanceInfo(-1, @@ -19,17 +19,19 @@ public class XmrBalanceInfo implements Payload { private final long pendingBalance; private final long reservedOfferBalance; private final long reservedTradeBalance; + private final long reservedBalance; public XmrBalanceInfo(long balance, long unlockedBalance, - long lockedBalance, + long pendingBalance, long reservedOfferBalance, long reservedTradeBalance) { this.balance = balance; this.availableBalance = unlockedBalance; - this.pendingBalance = lockedBalance; + this.pendingBalance = pendingBalance; this.reservedOfferBalance = reservedOfferBalance; this.reservedTradeBalance = reservedTradeBalance; + this.reservedBalance = reservedOfferBalance + reservedTradeBalance; } @VisibleForTesting @@ -45,6 +47,30 @@ public class XmrBalanceInfo implements Payload { reservedTradeBalance); } + public BigInteger getBalance() { + return BigInteger.valueOf(balance); + } + + public BigInteger getAvailableBalance() { + return BigInteger.valueOf(availableBalance); + } + + public BigInteger getPendingBalance() { + return BigInteger.valueOf(pendingBalance); + } + + public BigInteger getReservedOfferBalance() { + return BigInteger.valueOf(reservedOfferBalance); + } + + public BigInteger getReservedTradeBalance() { + return BigInteger.valueOf(reservedTradeBalance); + } + + public BigInteger getReservedBalance() { + return BigInteger.valueOf(reservedBalance); + } + /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/haveno/core/presentation/BalancePresentation.java b/core/src/main/java/haveno/core/presentation/BalancePresentation.java index 5d7f7ff228..9838f6159d 100644 --- a/core/src/main/java/haveno/core/presentation/BalancePresentation.java +++ b/core/src/main/java/haveno/core/presentation/BalancePresentation.java @@ -19,6 +19,7 @@ package haveno.core.presentation; import com.google.inject.Inject; import haveno.common.UserThread; +import haveno.core.api.model.XmrBalanceInfo; import haveno.core.trade.HavenoUtils; import haveno.core.xmr.Balances; import javafx.beans.property.SimpleStringProperty; @@ -38,14 +39,13 @@ public class BalancePresentation { @Inject public BalancePresentation(Balances balances) { - balances.getAvailableBalance().addListener((observable, oldValue, newValue) -> { - UserThread.execute(() -> availableBalance.set(HavenoUtils.formatXmr(newValue, true))); - }); - balances.getPendingBalance().addListener((observable, oldValue, newValue) -> { - UserThread.execute(() -> pendingBalance.set(HavenoUtils.formatXmr(newValue, true))); - }); - balances.getReservedBalance().addListener((observable, oldValue, newValue) -> { - UserThread.execute(() -> reservedBalance.set(HavenoUtils.formatXmr(newValue, true))); + balances.getUpdateCounter().addListener((observable, oldValue, newValue) -> { + XmrBalanceInfo info = balances.getBalances(); + UserThread.execute(() -> { + availableBalance.set(HavenoUtils.formatXmr(info.getAvailableBalance(), true)); + pendingBalance.set(HavenoUtils.formatXmr(info.getPendingBalance(), true)); + reservedBalance.set(HavenoUtils.formatXmr(info.getReservedBalance(), true)); + }); }); } } diff --git a/core/src/main/java/haveno/core/xmr/Balances.java b/core/src/main/java/haveno/core/xmr/Balances.java index 287060246b..5b47daa548 100644 --- a/core/src/main/java/haveno/core/xmr/Balances.java +++ b/core/src/main/java/haveno/core/xmr/Balances.java @@ -37,6 +37,7 @@ package haveno.core.xmr; import com.google.inject.Inject; import haveno.common.ThreadUtils; import haveno.common.UserThread; +import haveno.core.api.model.XmrBalanceInfo; import haveno.core.offer.OpenOffer; import haveno.core.offer.OpenOfferManager; import haveno.core.support.dispute.Dispute; @@ -51,8 +52,8 @@ import haveno.core.xmr.wallet.XmrWalletService; import java.math.BigInteger; import java.util.List; import java.util.stream.Collectors; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.ListChangeListener; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -67,15 +68,18 @@ public class Balances { private final RefundManager refundManager; @Getter - private final ObjectProperty availableBalance = new SimpleObjectProperty<>(); + private BigInteger availableBalance; @Getter - private final ObjectProperty pendingBalance = new SimpleObjectProperty<>(); + private BigInteger pendingBalance; @Getter - private final ObjectProperty reservedOfferBalance = new SimpleObjectProperty<>(); + private BigInteger reservedOfferBalance; @Getter - private final ObjectProperty reservedTradeBalance = new SimpleObjectProperty<>(); + private BigInteger reservedTradeBalance; @Getter - private final ObjectProperty reservedBalance = new SimpleObjectProperty<>(); // TODO (woodser): this balance is sum of reserved funds for offers and trade multisigs; remove? + private BigInteger reservedBalance; // TODO (woodser): this balance is sum of reserved funds for offers and trade multisigs; remove? + + @Getter + private final IntegerProperty updateCounter = new SimpleIntegerProperty(0); @Inject public Balances(TradeManager tradeManager, @@ -103,52 +107,57 @@ public class Balances { updateBalances(); } + public XmrBalanceInfo getBalances() { + synchronized (this) { + return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(), + availableBalance.longValue(), + pendingBalance.longValue(), + reservedOfferBalance.longValue(), + reservedTradeBalance.longValue()); + } + } + private void updateBalances() { ThreadUtils.submitToPool(() -> doUpdateBalances()); } private void doUpdateBalances() { + synchronized (this) { + + // get wallet balances + BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance(); + availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance(); - // get wallet balances - BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getWallet().getBalance(0); - BigInteger unlockedBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getWallet().getUnlockedBalance(0); + // calculate pending balance by adding frozen trade balances - reserved amounts + pendingBalance = balance.subtract(availableBalance); + List trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); + for (Trade trade : trades) { + if (trade.getFrozenAmount().equals(new BigInteger("0"))) continue; + BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); + pendingBalance = pendingBalance.add(trade.getFrozenAmount()).subtract(trade.getReservedAmount()).subtract(tradeFee).subtract(trade.getSelf().getDepositTxFee()); + } - // calculate pending balance by adding frozen trade balances - reserved amounts - BigInteger pendingBalance = balance.subtract(unlockedBalance); - List trades = tradeManager.getTradesStreamWithFundsLockedIn().collect(Collectors.toList()); - for (Trade trade : trades) { - if (trade.getFrozenAmount().equals(new BigInteger("0"))) continue; - BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee(); - pendingBalance = pendingBalance.add(trade.getFrozenAmount()).subtract(trade.getReservedAmount()).subtract(tradeFee).subtract(trade.getSelf().getDepositTxFee()); + // calculate reserved offer balance + reservedOfferBalance = BigInteger.ZERO; + if (xmrWalletService.getWallet() != null) { + List frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false)); + for (MoneroOutputWallet frozenOutput : frozenOutputs) reservedOfferBalance = reservedOfferBalance.add(frozenOutput.getAmount()); + } + for (Trade trade : trades) { + reservedOfferBalance = reservedOfferBalance.subtract(trade.getFrozenAmount()); // subtract frozen trade balances + } + + // calculate reserved trade balance + reservedTradeBalance = BigInteger.ZERO; + for (Trade trade : trades) { + reservedTradeBalance = reservedTradeBalance.add(trade.getReservedAmount()); + } + + // calculate reserved balance + reservedBalance = reservedOfferBalance.add(reservedTradeBalance); + + // notify balance update + UserThread.execute(() -> updateCounter.set(updateCounter.get() + 1)); } - - // calculate reserved offer balance - BigInteger reservedOfferBalance = BigInteger.ZERO; - if (xmrWalletService.getWallet() != null) { - List frozenOutputs = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false)); - for (MoneroOutputWallet frozenOutput : frozenOutputs) reservedOfferBalance = reservedOfferBalance.add(frozenOutput.getAmount()); - } - for (Trade trade : trades) { - reservedOfferBalance = reservedOfferBalance.subtract(trade.getFrozenAmount()); // subtract frozen trade balances - } - - // calculate reserved trade balance - BigInteger reservedTradeBalance = BigInteger.ZERO; - for (Trade trade : trades) { - reservedTradeBalance = reservedTradeBalance.add(trade.getReservedAmount()); - } - - // set balances - setBalances(balance, unlockedBalance, pendingBalance, reservedOfferBalance, reservedTradeBalance); - } - - private void setBalances(BigInteger balance, BigInteger unlockedBalance, BigInteger pendingBalance, BigInteger reservedOfferBalance, BigInteger reservedTradeBalance) { - UserThread.execute(() -> { - this.availableBalance.set(unlockedBalance); - this.pendingBalance.set(pendingBalance); - this.reservedOfferBalance.set(reservedOfferBalance); - this.reservedTradeBalance.set(reservedTradeBalance); - this.reservedBalance.set(reservedOfferBalance.add(reservedTradeBalance)); - }); } }