mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
refactor base wallet so trades can sync with progress timeout, etc
This commit is contained in:
parent
1b5c03bce8
commit
ca7d596175
23 changed files with 247 additions and 226 deletions
|
@ -118,7 +118,7 @@ public class CoreDisputesService {
|
|||
}
|
||||
|
||||
public Dispute createDisputeForTrade(Trade trade, Offer offer, PubKeyRing pubKey, boolean isMaker, boolean isSupportTicket) {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
byte[] payoutTxSerialized = null;
|
||||
String payoutTxHashAsString = null;
|
||||
|
||||
|
@ -163,7 +163,7 @@ public class CoreDisputesService {
|
|||
if (winningDisputeOptional.isPresent()) winningDispute = winningDisputeOptional.get();
|
||||
else throw new IllegalStateException(format("dispute for tradeId '%s' not found", tradeId));
|
||||
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
try {
|
||||
|
||||
// create dispute result
|
||||
|
|
|
@ -1085,7 +1085,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
BigInteger reserveAmount = openOffer.getOffer().getAmountNeeded();
|
||||
xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s)
|
||||
MoneroTxWallet splitOutputTx = null;
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||
XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
|
|
@ -30,7 +30,6 @@ import haveno.core.offer.placeoffer.PlaceOfferModel;
|
|||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.protocol.TradeProtocol;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
|
@ -60,11 +59,11 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||
}
|
||||
|
||||
// verify monero connection
|
||||
model.getXmrWalletService().getConnectionService().verifyConnection();
|
||||
model.getXmrWalletService().getXmrConnectionService().verifyConnection();
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = null;
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||
|
||||
// reset protocol timeout
|
||||
verifyPending();
|
||||
|
@ -83,7 +82,7 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||
try {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
MoneroRpcConnection sourceConnection = model.getXmrWalletService().getConnectionService().getConnection();
|
||||
MoneroRpcConnection sourceConnection = model.getXmrWalletService().getXmrConnectionService().getConnection();
|
||||
try {
|
||||
//if (true) throw new RuntimeException("Pretend error");
|
||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||
|
|
|
@ -499,7 +499,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
|
||||
// process on trade thread
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
String errorMessage = null;
|
||||
PubKeyRing senderPubKeyRing = null;
|
||||
try {
|
||||
|
|
|
@ -240,7 +240,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
ThreadUtils.execute(() -> {
|
||||
ChatMessage chatMessage = null;
|
||||
Dispute dispute = null;
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
try {
|
||||
DisputeResult disputeResult = disputeClosedMessage.getDisputeResult();
|
||||
chatMessage = disputeResult.getChatMessage();
|
||||
|
@ -384,7 +384,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
public void maybeReprocessDisputeClosedMessage(Trade trade, boolean reprocessOnError) {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isArbitrator() || trade.getArbitrator().getDisputeClosedMessage() == null || trade.getArbitrator().getDisputeClosedMessage().getUnsignedPayoutTxHex() == null || trade.getDisputeState().ordinal() >= Trade.DisputeState.DISPUTE_CLOSED.ordinal()) {
|
||||
|
@ -478,7 +478,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
trade.setPayoutTxHex(signedMultisigTxHex);
|
||||
requestPersistence(trade);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
throw new IllegalStateException(e.getMessage());
|
||||
}
|
||||
|
||||
// verify mining fee is within tolerance by recreating payout tx
|
||||
|
|
|
@ -28,6 +28,7 @@ import haveno.common.crypto.KeyRing;
|
|||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.util.Utilities;
|
||||
import haveno.core.api.XmrConnectionService;
|
||||
import haveno.core.app.HavenoSetup;
|
||||
import haveno.core.offer.OfferPayload;
|
||||
import haveno.core.offer.OpenOfferManager;
|
||||
|
@ -106,6 +107,7 @@ public class HavenoUtils {
|
|||
public static HavenoSetup havenoSetup;
|
||||
public static ArbitrationManager arbitrationManager;
|
||||
public static XmrWalletService xmrWalletService;
|
||||
public static XmrConnectionService xmrConnectionService;
|
||||
public static OpenOfferManager openOfferManager;
|
||||
|
||||
public static boolean isSeedNode() {
|
||||
|
|
|
@ -44,7 +44,6 @@ import haveno.common.crypto.PubKeyRing;
|
|||
import haveno.common.proto.ProtoUtil;
|
||||
import haveno.common.taskrunner.Model;
|
||||
import haveno.common.util.Utilities;
|
||||
import haveno.core.api.XmrConnectionService;
|
||||
import haveno.core.monetary.Price;
|
||||
import haveno.core.monetary.Volume;
|
||||
import haveno.core.network.MessageState;
|
||||
|
@ -69,6 +68,7 @@ import haveno.core.trade.protocol.TradeProtocol;
|
|||
import haveno.core.trade.statistics.TradeStatistics3;
|
||||
import haveno.core.util.VolumeUtil;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.wallet.XmrWalletBase;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import haveno.network.p2p.AckMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
|
@ -76,14 +76,12 @@ import haveno.network.p2p.P2PService;
|
|||
import haveno.network.p2p.network.TorNetworkNode;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
@ -135,19 +133,17 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
* stored in the task model.
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class Trade implements Tradable, Model {
|
||||
public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
@Getter
|
||||
public final Object lock = new Object();
|
||||
private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_";
|
||||
private static final long SHUTDOWN_TIMEOUT_MS = 60000;
|
||||
private static final long SYNC_EVERY_NUM_BLOCKS = 360; // ~1/2 day
|
||||
private static final long DELETE_AFTER_NUM_BLOCKS = 2; // if deposit requested but not published
|
||||
private static final long EXTENDED_RPC_TIMEOUT = 600000; // 10 minutes
|
||||
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
|
||||
private final Object walletLock = new Object();
|
||||
private final Object pollLock = new Object();
|
||||
private final LongProperty walletHeight = new SimpleLongProperty(0);
|
||||
private MoneroWallet wallet;
|
||||
private boolean wasWalletSynced;
|
||||
private boolean pollInProgress;
|
||||
private boolean restartInProgress;
|
||||
private Subscription protocolErrorStateSubscription;
|
||||
|
@ -413,9 +409,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
// Immutable
|
||||
@Getter
|
||||
transient final private XmrWalletService xmrWalletService;
|
||||
@Getter
|
||||
transient final private XmrConnectionService xmrConnectionService;
|
||||
|
||||
transient final private DoubleProperty initProgressProperty = new SimpleDoubleProperty(0.0);
|
||||
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
|
||||
transient final private ObjectProperty<Phase> phaseProperty = new SimpleObjectProperty<>(state.phase);
|
||||
|
@ -441,10 +434,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
@Getter
|
||||
transient private boolean isInitialized;
|
||||
transient private boolean isFullyInitialized;
|
||||
@Getter
|
||||
transient private boolean isShutDownStarted;
|
||||
@Getter
|
||||
transient private boolean isShutDown;
|
||||
|
||||
// Added in v1.2.0
|
||||
transient private ObjectProperty<BigInteger> tradeAmountProperty;
|
||||
|
@ -511,11 +500,12 @@ public abstract class Trade implements Tradable, Model {
|
|||
@Nullable NodeAddress makerNodeAddress,
|
||||
@Nullable NodeAddress takerNodeAddress,
|
||||
@Nullable NodeAddress arbitratorNodeAddress) {
|
||||
super();
|
||||
this.offer = offer;
|
||||
this.amount = tradeAmount.longValueExact();
|
||||
this.price = tradePrice;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.xmrConnectionService = xmrWalletService.getConnectionService();
|
||||
this.xmrConnectionService = xmrWalletService.getXmrConnectionService();
|
||||
this.processModel = processModel;
|
||||
this.uid = uid;
|
||||
this.takeOfferDate = new Date().getTime();
|
||||
|
@ -1512,13 +1502,13 @@ public abstract class Trade implements Tradable, Model {
|
|||
|
||||
// repeatedly acquire lock to clear tasks
|
||||
for (int i = 0; i < 20; i++) {
|
||||
synchronized (this) {
|
||||
synchronized (getLock()) {
|
||||
HavenoUtils.waitFor(10);
|
||||
}
|
||||
}
|
||||
|
||||
// shut down trade threads
|
||||
synchronized (this) {
|
||||
synchronized (getLock()) {
|
||||
isInitialized = false;
|
||||
isShutDown = true;
|
||||
List<Runnable> shutDownThreads = new ArrayList<>();
|
||||
|
@ -2636,8 +2626,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
private void syncWalletIfBehind() {
|
||||
if (isWalletBehind()) {
|
||||
synchronized (walletLock) {
|
||||
xmrWalletService.syncWallet(wallet);
|
||||
walletHeight.set(wallet.getHeight());
|
||||
syncWithProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2812,7 +2801,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
if (!isInitialized || isShutDownStarted) return;
|
||||
if (isWalletConnectedToDaemon()) {
|
||||
e.printStackTrace();
|
||||
log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
||||
log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getXmrConnectionService().getConnection());
|
||||
};
|
||||
}
|
||||
}, getId());
|
||||
|
|
|
@ -45,7 +45,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("ArbitratorProtocol.handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set
|
||||
|
@ -80,7 +80,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||
public void handleDepositRequest(DepositRequest request, NodeAddress sender) {
|
||||
System.out.println("ArbitratorProtocol.handleDepositRequest() " + trade.getId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
processModel.setTradeMessage(request);
|
||||
|
|
|
@ -62,7 +62,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
|
|
|
@ -70,7 +70,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.tradeResultHandler = tradeResultHandler;
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
|
@ -101,7 +101,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||
NodeAddress peer) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
|
|
|
@ -75,7 +75,7 @@ public class BuyerProtocol extends DisputeProtocol {
|
|||
// re-send payment sent message if not acked
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal() && trade.getState().ordinal() < Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal()) {
|
||||
latchTrade();
|
||||
|
@ -121,7 +121,7 @@ public class BuyerProtocol extends DisputeProtocol {
|
|||
public void onPaymentSent(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println("BuyerProtocol.onPaymentSent()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
BuyerEvent event = BuyerEvent.PAYMENT_SENT;
|
||||
|
|
|
@ -67,7 +67,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
|
|
|
@ -70,7 +70,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||
ErrorMessageHandler errorMessageHandler) {
|
||||
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.tradeResultHandler = tradeResultHandler;
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
|
@ -101,7 +101,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||
NodeAddress peer) {
|
||||
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
expect(phase(Trade.Phase.INIT)
|
||||
.with(message)
|
||||
|
|
|
@ -70,7 +70,7 @@ public class SellerProtocol extends DisputeProtocol {
|
|||
// re-send payment received message if payout not published
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.isPayoutPublished()) {
|
||||
latchTrade();
|
||||
|
@ -117,7 +117,7 @@ public class SellerProtocol extends DisputeProtocol {
|
|||
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
log.info("SellerProtocol.onPaymentReceived()");
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
latchTrade();
|
||||
this.errorMessageHandler = errorMessageHandler;
|
||||
SellerEvent event = SellerEvent.PAYMENT_RECEIVED;
|
||||
|
|
|
@ -243,7 +243,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
if (!trade.isCompleted()) processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
|
||||
// initialize trade
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
trade.initialize(processModel.getProvider());
|
||||
|
||||
// process mailbox messages
|
||||
|
@ -261,7 +261,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
ThreadUtils.execute(() -> {
|
||||
if (!trade.isDepositsConfirmed() || trade.isDepositsConfirmedAcked() || trade.isPayoutPublished() || depositsConfirmedTasksCalled) return;
|
||||
depositsConfirmedTasksCalled = true;
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return; // skip if shutting down
|
||||
latchTrade();
|
||||
expect(new Condition(trade))
|
||||
|
@ -282,7 +282,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isSeller() || trade.getSeller().getPaymentReceivedMessage() == null || (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.isPayoutPublished())) {
|
||||
|
@ -299,7 +299,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
System.out.println(getClass().getSimpleName() + ".handleInitMultisigRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// check trade
|
||||
if (trade.hasFailed()) {
|
||||
|
@ -335,7 +335,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
|
||||
System.out.println(getClass().getSimpleName() + ".handleSignContractRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// check trade
|
||||
if (trade.hasFailed()) {
|
||||
|
@ -379,7 +379,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
System.out.println(getClass().getSimpleName() + ".handleSignContractResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// check trade
|
||||
if (trade.hasFailed()) {
|
||||
|
@ -425,7 +425,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
System.out.println(getClass().getSimpleName() + ".handleDepositResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
trade.addInitProgressStep();
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
Validator.checkTradeId(processModel.getOfferId(), response);
|
||||
latchTrade();
|
||||
processModel.setTradeMessage(response);
|
||||
|
@ -455,7 +455,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
System.out.println(getClass().getSimpleName() + ".handle(DepositsConfirmedMessage) from " + sender + " for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
latchTrade();
|
||||
this.errorMessageHandler = null;
|
||||
|
@ -493,7 +493,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
// a mailbox message with PaymentSentMessage.
|
||||
// TODO A better fix would be to add a listener for the wallet sync state and process
|
||||
// the mailbox msg once wallet is ready and trade state set.
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
if (trade.getPhase().ordinal() >= Trade.Phase.PAYMENT_SENT.ordinal()) {
|
||||
log.warn("Received another PaymentSentMessage which was already processed for {} {}, ACKing", trade.getClass().getSimpleName(), trade.getId());
|
||||
|
@ -542,7 +542,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
log.warn("Ignoring PaymentReceivedMessage since not buyer or arbitrator");
|
||||
return;
|
||||
}
|
||||
synchronized (trade) {
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
latchTrade();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
|
@ -817,7 +817,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||
}
|
||||
|
||||
void handleTaskRunnerFault(NodeAddress ackReceiver, @Nullable TradeMessage message, String source, String errorMessage) {
|
||||
log.error("Task runner failed with error {}. Triggered from {}. Monerod={}" , errorMessage, source, trade.getXmrWalletService().getConnectionService().getConnection());
|
||||
log.error("Task runner failed with error {}. Triggered from {}. Monerod={}" , errorMessage, source, trade.getXmrWalletService().getXmrConnectionService().getConnection());
|
||||
|
||||
if (message != null) {
|
||||
sendAckMessage(ackReceiver, message, false, errorMessage);
|
||||
|
|
|
@ -28,7 +28,6 @@ import haveno.core.trade.Trade.State;
|
|||
import haveno.core.trade.messages.SignContractRequest;
|
||||
import haveno.core.trade.protocol.TradeProtocol;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
|
@ -78,7 +77,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||
|
||||
// create deposit tx and freeze inputs
|
||||
MoneroTxWallet depositTx = null;
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||
|
||||
// check for timeout
|
||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while getting lock to create deposit tx, tradeId=" + trade.getShortId());
|
||||
|
|
|
@ -24,7 +24,6 @@ import haveno.core.trade.TakerTrade;
|
|||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.TradeProtocol;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.wallet.XmrWalletService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
@ -50,7 +49,7 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = null;
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||
|
||||
// check for timeout
|
||||
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while getting lock to create reserve tx, tradeId=" + trade.getShortId());
|
||||
|
|
|
@ -44,6 +44,7 @@ import haveno.core.offer.OpenOfferManager;
|
|||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.support.dispute.refund.RefundManager;
|
||||
import haveno.core.trade.ClosedTradableManager;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.TradeManager;
|
||||
|
@ -124,7 +125,7 @@ public class Balances {
|
|||
|
||||
private void doUpdateBalances() {
|
||||
synchronized (this) {
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
|
||||
|
||||
// get wallet balances
|
||||
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance();
|
||||
|
|
165
core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java
Normal file
165
core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java
Normal file
|
@ -0,0 +1,165 @@
|
|||
package haveno.core.xmr.wallet;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.core.api.XmrConnectionService;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.xmr.setup.DownloadListener;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.TaskLooper;
|
||||
import monero.daemon.model.MoneroTx;
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.MoneroWalletFull;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
|
||||
@Slf4j
|
||||
public class XmrWalletBase {
|
||||
|
||||
// constants
|
||||
public static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60;
|
||||
|
||||
// inherited
|
||||
protected MoneroWallet wallet;
|
||||
@Getter
|
||||
protected final Object walletLock = new Object();
|
||||
@Getter
|
||||
protected XmrConnectionService xmrConnectionService;
|
||||
protected boolean wasWalletSynced;
|
||||
protected final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
||||
protected boolean isClosingWallet;
|
||||
protected boolean isSyncingWithProgress;
|
||||
protected Long syncStartHeight;
|
||||
protected TaskLooper syncProgressLooper;
|
||||
protected CountDownLatch syncProgressLatch;
|
||||
protected Exception syncProgressError;
|
||||
protected Timer syncProgressTimeout;
|
||||
protected final DownloadListener downloadListener = new DownloadListener();
|
||||
protected final LongProperty walletHeight = new SimpleLongProperty(0);
|
||||
@Getter
|
||||
protected boolean isShutDownStarted;
|
||||
@Getter
|
||||
protected boolean isShutDown;
|
||||
|
||||
// private
|
||||
private boolean testReconnectOnStartup = false; // test reconnecting on startup while syncing so the wallet is blocked
|
||||
private String testReconnectMonerod1 = "http://node.community.rino.io:18081";
|
||||
private String testReconnectMonerod2 = "http://nodex.monerujo.io:18081";
|
||||
|
||||
public XmrWalletBase() {
|
||||
this.xmrConnectionService = HavenoUtils.xmrConnectionService;
|
||||
}
|
||||
|
||||
public void syncWithProgress() {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// set initial state
|
||||
isSyncingWithProgress = true;
|
||||
syncProgressError = null;
|
||||
updateSyncProgress(walletHeight.get());
|
||||
|
||||
// test connection changing on startup before wallet synced
|
||||
if (testReconnectOnStartup) {
|
||||
UserThread.runAfter(() -> {
|
||||
log.warn("Testing connection change on startup before wallet synced");
|
||||
if (xmrConnectionService.getConnection().getUri().equals(testReconnectMonerod1)) xmrConnectionService.setConnection(testReconnectMonerod2);
|
||||
else xmrConnectionService.setConnection(testReconnectMonerod1);
|
||||
}, 1);
|
||||
testReconnectOnStartup = false; // only run once
|
||||
}
|
||||
|
||||
// native wallet provides sync notifications
|
||||
if (wallet instanceof MoneroWalletFull) {
|
||||
if (testReconnectOnStartup) HavenoUtils.waitFor(1000); // delay sync to test
|
||||
wallet.sync(new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||
updateSyncProgress(height);
|
||||
}
|
||||
});
|
||||
setWalletSyncedWithProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
// start polling wallet for progress
|
||||
syncProgressLatch = new CountDownLatch(1);
|
||||
syncProgressLooper = new TaskLooper(() -> {
|
||||
if (wallet == null) return;
|
||||
long height;
|
||||
try {
|
||||
height = wallet.getHeight(); // can get read timeout while syncing
|
||||
} catch (Exception e) {
|
||||
log.warn("Error getting wallet height while syncing with progress: " + e.getMessage());
|
||||
if (wallet != null && !isShutDownStarted) e.printStackTrace();
|
||||
|
||||
// stop polling and release latch
|
||||
syncProgressError = e;
|
||||
syncProgressLatch.countDown();
|
||||
return;
|
||||
}
|
||||
updateSyncProgress(height);
|
||||
if (height >= xmrConnectionService.getTargetHeight()) {
|
||||
setWalletSyncedWithProgress();
|
||||
syncProgressLatch.countDown();
|
||||
}
|
||||
});
|
||||
wallet.startSyncing(xmrConnectionService.getRefreshPeriodMs());
|
||||
syncProgressLooper.start(1000);
|
||||
|
||||
// wait for sync to complete
|
||||
HavenoUtils.awaitLatch(syncProgressLatch);
|
||||
|
||||
// stop polling
|
||||
syncProgressLooper.stop();
|
||||
syncProgressTimeout.stop();
|
||||
if (wallet != null) wallet.stopSyncing(); // can become null if interrupted by force close
|
||||
isSyncingWithProgress = false;
|
||||
if (syncProgressError != null) throw new RuntimeException(syncProgressError);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSyncProgress(long height) {
|
||||
resetSyncProgressTimeout();
|
||||
UserThread.execute(() -> {
|
||||
|
||||
// set wallet height
|
||||
walletHeight.set(height);
|
||||
|
||||
// new wallet reports height 1 before synced
|
||||
if (height == 1) {
|
||||
downloadListener.progress(0, xmrConnectionService.getTargetHeight() - height, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// set progress
|
||||
long targetHeight = xmrConnectionService.getTargetHeight();
|
||||
long blocksLeft = targetHeight - walletHeight.get();
|
||||
if (syncStartHeight == null) syncStartHeight = walletHeight.get();
|
||||
double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) walletHeight.get() - syncStartHeight) / (double) (targetHeight - syncStartHeight));
|
||||
downloadListener.progress(percent, blocksLeft, null);
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void resetSyncProgressTimeout() {
|
||||
if (syncProgressTimeout != null) syncProgressTimeout.stop();
|
||||
syncProgressTimeout = UserThread.runAfter(() -> {
|
||||
if (isShutDownStarted) return;
|
||||
syncProgressError = new RuntimeException("Sync progress timeout called");
|
||||
syncProgressLatch.countDown();
|
||||
}, SYNC_PROGRESS_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void setWalletSyncedWithProgress() {
|
||||
wasWalletSynced = true;
|
||||
isSyncingWithProgress = false;
|
||||
syncProgressTimeout.stop();
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import com.google.inject.name.Named;
|
|||
|
||||
import common.utils.JsonUtils;
|
||||
import haveno.common.ThreadUtils;
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.config.Config;
|
||||
import haveno.common.file.FileUtil;
|
||||
|
@ -43,7 +42,6 @@ import haveno.core.user.User;
|
|||
import haveno.core.xmr.listeners.XmrBalanceListener;
|
||||
import haveno.core.xmr.model.XmrAddressEntry;
|
||||
import haveno.core.xmr.model.XmrAddressEntryList;
|
||||
import haveno.core.xmr.setup.DownloadListener;
|
||||
import haveno.core.xmr.setup.MoneroWalletRpcManager;
|
||||
import haveno.core.xmr.setup.WalletsSetup;
|
||||
import java.io.File;
|
||||
|
@ -55,26 +53,22 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import lombok.Getter;
|
||||
import monero.common.MoneroError;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.MoneroRpcError;
|
||||
|
@ -103,14 +97,13 @@ import monero.wallet.model.MoneroTxPriority;
|
|||
import monero.wallet.model.MoneroTxQuery;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
import monero.wallet.model.MoneroWalletConfig;
|
||||
import monero.wallet.model.MoneroWalletListener;
|
||||
import monero.wallet.model.MoneroWalletListenerI;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
public class XmrWalletService {
|
||||
public class XmrWalletService extends XmrWalletBase {
|
||||
private static final Logger log = LoggerFactory.getLogger(XmrWalletService.class);
|
||||
|
||||
// monero configuration
|
||||
|
@ -138,11 +131,8 @@ public class XmrWalletService {
|
|||
private final User user;
|
||||
private final Preferences preferences;
|
||||
private final CoreAccountService accountService;
|
||||
private final XmrConnectionService xmrConnectionService;
|
||||
private final XmrAddressEntryList xmrAddressEntryList;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final DownloadListener downloadListener = new DownloadListener();
|
||||
private final LongProperty walletHeight = new SimpleLongProperty(0);
|
||||
|
||||
private final File walletDir;
|
||||
private final File xmrWalletFile;
|
||||
|
@ -153,22 +143,10 @@ public class XmrWalletService {
|
|||
|
||||
private ChangeListener<? super Number> walletInitListener;
|
||||
private TradeManager tradeManager;
|
||||
private MoneroWallet wallet;
|
||||
public static final Object WALLET_LOCK = new Object();
|
||||
private boolean wasWalletSynced;
|
||||
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
||||
private boolean isClosingWallet;
|
||||
private boolean isShutDownStarted;
|
||||
private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type
|
||||
private boolean isSyncingWithProgress;
|
||||
private Long syncStartHeight;
|
||||
private TaskLooper syncProgressLooper;
|
||||
private CountDownLatch syncProgressLatch;
|
||||
private Exception syncProgressError;
|
||||
private Timer syncProgressTimeout;
|
||||
private static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60;
|
||||
|
||||
// wallet polling and cache
|
||||
@Getter
|
||||
public final Object lock = new Object();
|
||||
private TaskLooper pollLooper;
|
||||
private boolean pollInProgress;
|
||||
private Long pollPeriodMs;
|
||||
|
@ -180,9 +158,6 @@ public class XmrWalletService {
|
|||
private List<MoneroSubaddress> cachedSubaddresses;
|
||||
private List<MoneroOutputWallet> cachedOutputs;
|
||||
private List<MoneroTxWallet> cachedTxs;
|
||||
private boolean testReconnectOnStartup = false; // test reconnecting on startup while syncing so the wallet is blocked
|
||||
private String testReconnectMonerod1 = "http://node.community.rino.io:18081";
|
||||
private String testReconnectMonerod2 = "http://nodex.monerujo.io:18081";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Inject
|
||||
|
@ -198,7 +173,6 @@ public class XmrWalletService {
|
|||
this.user = user;
|
||||
this.preferences = preferences;
|
||||
this.accountService = accountService;
|
||||
this.xmrConnectionService = xmrConnectionService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.xmrAddressEntryList = xmrAddressEntryList;
|
||||
this.walletDir = walletDir;
|
||||
|
@ -206,6 +180,8 @@ public class XmrWalletService {
|
|||
this.useNativeXmrWallet = useNativeXmrWallet;
|
||||
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
|
||||
HavenoUtils.xmrWalletService = this;
|
||||
HavenoUtils.xmrConnectionService = xmrConnectionService;
|
||||
this.xmrConnectionService = xmrConnectionService; // TODO: super's is null unless set here from injection
|
||||
|
||||
// set monero logging
|
||||
if (MONERO_LOG_LEVEL >= 0) MoneroUtils.setLogLevel(MONERO_LOG_LEVEL);
|
||||
|
@ -320,10 +296,6 @@ public class XmrWalletService {
|
|||
return xmrConnectionService.getDaemon();
|
||||
}
|
||||
|
||||
public XmrConnectionService getConnectionService() {
|
||||
return xmrConnectionService;
|
||||
}
|
||||
|
||||
public boolean isProxyApplied() {
|
||||
return isProxyApplied(wasWalletSynced);
|
||||
}
|
||||
|
@ -464,7 +436,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
public MoneroTxWallet createTx(MoneroTxConfig txConfig) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
MoneroTxWallet tx = wallet.createTx(txConfig);
|
||||
if (Boolean.TRUE.equals(txConfig.getRelay())) {
|
||||
|
@ -478,7 +450,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
public String relayTx(String metadata) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
String txId = wallet.relayTx(metadata);
|
||||
requestSaveMainWallet();
|
||||
return txId;
|
||||
|
@ -495,7 +467,7 @@ public class XmrWalletService {
|
|||
* Freeze reserved outputs and thaw unreserved outputs.
|
||||
*/
|
||||
public void fixReservedOutputs() {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// collect reserved outputs
|
||||
Set<String> reservedKeyImages = new HashSet<String>();
|
||||
|
@ -514,7 +486,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void freezeReservedOutputs(Set<String> reservedKeyImages) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// ensure wallet is open
|
||||
if (wallet == null) {
|
||||
|
@ -538,7 +510,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void thawUnreservedOutputs(Set<String> reservedKeyImages) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// ensure wallet is open
|
||||
if (wallet == null) {
|
||||
|
@ -568,7 +540,7 @@ public class XmrWalletService {
|
|||
*/
|
||||
public void freezeOutputs(Collection<String> keyImages) {
|
||||
if (keyImages == null || keyImages.isEmpty()) return;
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// collect outputs to freeze
|
||||
List<String> unfrozenKeyImages = getOutputs(new MoneroOutputQuery().setIsFrozen(false).setIsSpent(false)).stream()
|
||||
|
@ -590,7 +562,7 @@ public class XmrWalletService {
|
|||
*/
|
||||
public void thawOutputs(Collection<String> keyImages) {
|
||||
if (keyImages == null || keyImages.isEmpty()) return;
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// collect outputs to thaw
|
||||
List<String> frozenKeyImages = getOutputs(new MoneroOutputQuery().setIsFrozen(true).setIsSpent(false)).stream()
|
||||
|
@ -643,7 +615,7 @@ public class XmrWalletService {
|
|||
* @return the reserve tx
|
||||
*/
|
||||
public MoneroTxWallet createReserveTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendTradeAmount, BigInteger securityDeposit, String returnAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress);
|
||||
long time = System.currentTimeMillis();
|
||||
|
@ -664,7 +636,7 @@ public class XmrWalletService {
|
|||
* @return MoneroTxWallet the multisig deposit tx
|
||||
*/
|
||||
public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
BigInteger feeAmount = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee();
|
||||
String feeAddress = trade.getProcessModel().getTradeFeeAddress();
|
||||
|
@ -682,7 +654,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private MoneroTxWallet createTradeTx(BigInteger feeAmount, String feeAddress, BigInteger sendAmount, String sendAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
MoneroWallet wallet = getWallet();
|
||||
|
||||
// create a list of subaddresses to attempt spending from in preferred order
|
||||
|
@ -919,7 +891,7 @@ public class XmrWalletService {
|
|||
Runnable shutDownTask = () -> {
|
||||
|
||||
// remove listeners
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
if (wallet != null) {
|
||||
for (MoneroWalletListenerI listener : new HashSet<>(wallet.getListeners())) {
|
||||
wallet.removeListener(listener);
|
||||
|
@ -929,7 +901,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
// shut down threads
|
||||
synchronized (this) {
|
||||
synchronized (getLock()) {
|
||||
List<Runnable> shutDownThreads = new ArrayList<>();
|
||||
shutDownThreads.add(() -> ThreadUtils.shutDown(THREAD_ID));
|
||||
ThreadUtils.awaitTasks(shutDownThreads);
|
||||
|
@ -1345,7 +1317,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void doMaybeInitMainWallet(boolean sync, int numAttempts) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// open or create wallet main wallet
|
||||
|
@ -1467,111 +1439,6 @@ public class XmrWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
private void syncWithProgress() {
|
||||
synchronized (WALLET_LOCK) {
|
||||
|
||||
// set initial state
|
||||
isSyncingWithProgress = true;
|
||||
syncProgressError = null;
|
||||
updateSyncProgress(walletHeight.get());
|
||||
|
||||
// test connection changing on startup before wallet synced
|
||||
if (testReconnectOnStartup) {
|
||||
UserThread.runAfter(() -> {
|
||||
log.warn("Testing connection change on startup before wallet synced");
|
||||
if (xmrConnectionService.getConnection().getUri().equals(testReconnectMonerod1)) xmrConnectionService.setConnection(testReconnectMonerod2);
|
||||
else xmrConnectionService.setConnection(testReconnectMonerod1);
|
||||
}, 1);
|
||||
testReconnectOnStartup = false; // only run once
|
||||
}
|
||||
|
||||
// native wallet provides sync notifications
|
||||
if (wallet instanceof MoneroWalletFull) {
|
||||
if (testReconnectOnStartup) HavenoUtils.waitFor(1000); // delay sync to test
|
||||
wallet.sync(new MoneroWalletListener() {
|
||||
@Override
|
||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||
updateSyncProgress(height);
|
||||
}
|
||||
});
|
||||
setWalletSyncedWithProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
// start polling wallet for progress
|
||||
syncProgressLatch = new CountDownLatch(1);
|
||||
syncProgressLooper = new TaskLooper(() -> {
|
||||
if (wallet == null) return;
|
||||
long height;
|
||||
try {
|
||||
height = wallet.getHeight(); // can get read timeout while syncing
|
||||
} catch (Exception e) {
|
||||
log.warn("Error getting wallet height while syncing with progress: " + e.getMessage());
|
||||
if (wallet != null && !isShutDownStarted) e.printStackTrace();
|
||||
|
||||
// stop polling and release latch
|
||||
syncProgressError = e;
|
||||
syncProgressLatch.countDown();
|
||||
return;
|
||||
}
|
||||
updateSyncProgress(height);
|
||||
if (height >= xmrConnectionService.getTargetHeight()) {
|
||||
setWalletSyncedWithProgress();
|
||||
syncProgressLatch.countDown();
|
||||
}
|
||||
});
|
||||
wallet.startSyncing(xmrConnectionService.getRefreshPeriodMs());
|
||||
syncProgressLooper.start(1000);
|
||||
|
||||
// wait for sync to complete
|
||||
HavenoUtils.awaitLatch(syncProgressLatch);
|
||||
|
||||
// stop polling
|
||||
syncProgressLooper.stop();
|
||||
syncProgressTimeout.stop();
|
||||
if (wallet != null) wallet.stopSyncing(); // can become null if interrupted by force close
|
||||
isSyncingWithProgress = false;
|
||||
if (syncProgressError != null) throw new RuntimeException(syncProgressError);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSyncProgress(long height) {
|
||||
resetSyncProgressTimeout();
|
||||
UserThread.execute(() -> {
|
||||
|
||||
// set wallet height
|
||||
walletHeight.set(height);
|
||||
|
||||
// new wallet reports height 1 before synced
|
||||
if (height == 1) {
|
||||
downloadListener.progress(0, xmrConnectionService.getTargetHeight() - height, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// set progress
|
||||
long targetHeight = xmrConnectionService.getTargetHeight();
|
||||
long blocksLeft = targetHeight - walletHeight.get();
|
||||
if (syncStartHeight == null) syncStartHeight = walletHeight.get();
|
||||
double percent = Math.min(1.0, targetHeight == syncStartHeight ? 1.0 : ((double) walletHeight.get() - syncStartHeight) / (double) (targetHeight - syncStartHeight));
|
||||
downloadListener.progress(percent, blocksLeft, null);
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void resetSyncProgressTimeout() {
|
||||
if (syncProgressTimeout != null) syncProgressTimeout.stop();
|
||||
syncProgressTimeout = UserThread.runAfter(() -> {
|
||||
if (isShutDownStarted) return;
|
||||
syncProgressError = new RuntimeException("Sync progress timeout called");
|
||||
syncProgressLatch.countDown();
|
||||
}, SYNC_PROGRESS_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void setWalletSyncedWithProgress() {
|
||||
wasWalletSynced = true;
|
||||
isSyncingWithProgress = false;
|
||||
syncProgressTimeout.stop();
|
||||
}
|
||||
|
||||
private MoneroWalletFull createWalletFull(MoneroWalletConfig config) {
|
||||
|
||||
// must be connected to daemon
|
||||
|
@ -1724,7 +1591,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void onConnectionChanged(MoneroRpcConnection connection) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
|
||||
// use current connection
|
||||
connection = xmrConnectionService.getConnection();
|
||||
|
@ -1798,7 +1665,7 @@ public class XmrWalletService {
|
|||
|
||||
private void closeMainWallet(boolean save) {
|
||||
stopPolling();
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
try {
|
||||
if (wallet != null) {
|
||||
isClosingWallet = true;
|
||||
|
@ -1834,7 +1701,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void startPolling() {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
if (isShutDownStarted || isPolling()) return;
|
||||
updatePollPeriod();
|
||||
pollLooper = new TaskLooper(() -> pollWallet());
|
||||
|
@ -1863,7 +1730,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void setPollPeriod(long pollPeriodMs) {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
if (this.isShutDownStarted) return;
|
||||
if (this.pollPeriodMs != null && this.pollPeriodMs == pollPeriodMs) return;
|
||||
this.pollPeriodMs = pollPeriodMs;
|
||||
|
@ -1900,7 +1767,7 @@ public class XmrWalletService {
|
|||
}
|
||||
// sync wallet if behind daemon
|
||||
if (walletHeight.get() < xmrConnectionService.getTargetHeight()) {
|
||||
synchronized (WALLET_LOCK) { // avoid long sync from blocking other operations
|
||||
synchronized (walletLock) { // avoid long sync from blocking other operations
|
||||
syncWithProgress();
|
||||
}
|
||||
}
|
||||
|
@ -1908,7 +1775,7 @@ public class XmrWalletService {
|
|||
// fetch transactions from pool and store to cache
|
||||
// TODO: ideally wallet should sync every poll and then avoid updating from pool on fetching txs?
|
||||
if (updateTxs) {
|
||||
synchronized (WALLET_LOCK) { // avoid long fetch from blocking other operations
|
||||
synchronized (walletLock) { // avoid long fetch from blocking other operations
|
||||
synchronized (HavenoUtils.getDaemonLock()) {
|
||||
MoneroRpcConnection sourceConnection = xmrConnectionService.getConnection();
|
||||
try {
|
||||
|
@ -1931,13 +1798,13 @@ public class XmrWalletService {
|
|||
if (wallet == null || isShutDownStarted) return;
|
||||
if (HavenoUtils.isUnresponsive(e)) forceRestartMainWallet();
|
||||
else if (isWalletConnectedToDaemon()) {
|
||||
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getConnectionService().getConnection());
|
||||
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getXmrConnectionService().getConnection());
|
||||
//e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
|
||||
// cache wallet info last
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
if (wallet != null && !isShutDownStarted) {
|
||||
try {
|
||||
cacheWalletInfo();
|
||||
|
@ -1954,7 +1821,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private MoneroSyncResult syncMainWallet() {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
MoneroSyncResult result = syncWallet(wallet);
|
||||
walletHeight.set(wallet.getHeight());
|
||||
return result;
|
||||
|
@ -1962,7 +1829,7 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
public boolean isWalletConnectedToDaemon() {
|
||||
synchronized (WALLET_LOCK) {
|
||||
synchronized (walletLock) {
|
||||
try {
|
||||
if (wallet == null) return false;
|
||||
return wallet.isConnectedToDaemon();
|
||||
|
|
|
@ -197,7 +197,7 @@ public class TxIdTextField extends AnchorPane {
|
|||
try {
|
||||
if (trade == null) {
|
||||
tx = useCache ? xmrWalletService.getDaemonTxWithCache(txId) : xmrWalletService.getDaemonTx(txId);
|
||||
tx.setNumConfirmations(tx.isConfirmed() ? (height == null ? xmrWalletService.getConnectionService().getLastInfo().getHeight() : height) - tx.getHeight(): 0l); // TODO: don't set if tx.getNumConfirmations() works reliably on non-local testnet
|
||||
tx.setNumConfirmations(tx.isConfirmed() ? (height == null ? xmrWalletService.getXmrConnectionService().getLastInfo().getHeight() : height) - tx.getHeight(): 0l); // TODO: don't set if tx.getNumConfirmations() works reliably on non-local testnet
|
||||
} else {
|
||||
if (txId.equals(trade.getMaker().getDepositTxHash())) tx = trade.getMakerDepositTx();
|
||||
else if (txId.equals(trade.getTaker().getDepositTxHash())) tx = trade.getTakerDepositTx();
|
||||
|
|
|
@ -257,7 +257,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
// create tx
|
||||
MoneroTxWallet tx = null;
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
MoneroRpcConnection sourceConnection = xmrWalletService.getConnectionService().getConnection();
|
||||
MoneroRpcConnection sourceConnection = xmrWalletService.getXmrConnectionService().getConnection();
|
||||
try {
|
||||
log.info("Creating withdraw tx");
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
@ -272,7 +272,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
if (isNotEnoughMoney(e.getMessage())) throw e;
|
||||
log.warn("Error creating creating withdraw tx, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (xmrWalletService.getConnectionService().isConnected()) xmrWalletService.requestSwitchToNextBestConnection(sourceConnection);
|
||||
if (xmrWalletService.getXmrConnectionService().isConnected()) xmrWalletService.requestSwitchToNextBestConnection(sourceConnection);
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
}
|
||||
|
|
|
@ -701,7 +701,7 @@ public class GUIUtil {
|
|||
}
|
||||
|
||||
public static boolean isReadyForTxBroadcastOrShowPopup(XmrWalletService xmrWalletService) {
|
||||
XmrConnectionService xmrConnectionService = xmrWalletService.getConnectionService();
|
||||
XmrConnectionService xmrConnectionService = xmrWalletService.getXmrConnectionService();
|
||||
if (!xmrConnectionService.hasSufficientPeersForBroadcast()) {
|
||||
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToXmrNetwork", xmrConnectionService.getMinBroadcastConnections())).show();
|
||||
return false;
|
||||
|
|
Loading…
Reference in a new issue