mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-23 03:59:36 +00:00
arbitrator deletes signed offer when key image confirmed spent
This commit is contained in:
parent
60dc4901e4
commit
5feb487039
4 changed files with 112 additions and 30 deletions
|
@ -17,6 +17,8 @@ import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Poll for changes to the spent status of key images.
|
* Poll for changes to the spent status of key images.
|
||||||
|
*
|
||||||
|
* TODO: move to monero-java?
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MoneroKeyImagePoller {
|
public class MoneroKeyImagePoller {
|
||||||
|
@ -240,7 +242,7 @@ public class MoneroKeyImagePoller {
|
||||||
setIsPolling(keyImages.size() > 0 && listeners.size() > 0);
|
setIsPolling(keyImages.size() > 0 && listeners.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setIsPolling(boolean enabled) {
|
private synchronized void setIsPolling(boolean enabled) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!isPolling) {
|
if (!isPolling) {
|
||||||
isPolling = true; // TODO monero-java: looper.isPolling()
|
isPolling = true; // TODO monero-java: looper.isPolling()
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class OfferBookService {
|
||||||
private final JsonFileManager jsonFileManager;
|
private final JsonFileManager jsonFileManager;
|
||||||
private final CoreMoneroConnectionsService connectionsService;
|
private final CoreMoneroConnectionsService connectionsService;
|
||||||
|
|
||||||
|
// poll key images of offers
|
||||||
private MoneroKeyImagePoller keyImagePoller;
|
private MoneroKeyImagePoller keyImagePoller;
|
||||||
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
|
||||||
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
|
||||||
|
@ -96,7 +97,7 @@ public class OfferBookService {
|
||||||
this.connectionsService = connectionsService;
|
this.connectionsService = connectionsService;
|
||||||
jsonFileManager = new JsonFileManager(storageDir);
|
jsonFileManager = new JsonFileManager(storageDir);
|
||||||
|
|
||||||
// listen for monero connection changes
|
// listen for connection changes to monerod
|
||||||
connectionsService.addListener(new MoneroConnectionManagerListener() {
|
connectionsService.addListener(new MoneroConnectionManagerListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionChanged(MoneroRpcConnection connection) {
|
public void onConnectionChanged(MoneroRpcConnection connection) {
|
||||||
|
@ -255,10 +256,11 @@ public class OfferBookService {
|
||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void maybeInitializeKeyImagePoller() {
|
private synchronized void maybeInitializeKeyImagePoller() {
|
||||||
synchronized (this) {
|
|
||||||
if (keyImagePoller != null) return;
|
if (keyImagePoller != null) return;
|
||||||
keyImagePoller = new MoneroKeyImagePoller(connectionsService.getDaemon(), getKeyImageRefreshPeriodMs());
|
keyImagePoller = new MoneroKeyImagePoller(connectionsService.getDaemon(), getKeyImageRefreshPeriodMs());
|
||||||
|
|
||||||
|
// handle when key images spent
|
||||||
keyImagePoller.addListener(new MoneroKeyImageListener() {
|
keyImagePoller.addListener(new MoneroKeyImageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
|
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
|
||||||
|
@ -274,7 +276,6 @@ public class OfferBookService {
|
||||||
keyImagePoller.poll();
|
keyImagePoller.poll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private long getKeyImageRefreshPeriodMs() {
|
private long getKeyImageRefreshPeriodMs() {
|
||||||
return connectionsService.isConnectionLocal() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE;
|
return connectionsService.isConnectionLocal() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE;
|
||||||
|
|
|
@ -21,6 +21,8 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.api.CoreContext;
|
import bisq.core.api.CoreContext;
|
||||||
import bisq.core.api.CoreMoneroConnectionsService;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
|
import bisq.core.btc.wallet.MoneroKeyImageListener;
|
||||||
|
import bisq.core.btc.wallet.MoneroKeyImagePoller;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||||
|
@ -56,6 +58,7 @@ import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
import bisq.network.p2p.SendDirectMessageListener;
|
||||||
import bisq.network.p2p.peers.Broadcaster;
|
import bisq.network.p2p.peers.Broadcaster;
|
||||||
import bisq.network.p2p.peers.PeerManager;
|
import bisq.network.p2p.peers.PeerManager;
|
||||||
|
import common.utils.GenUtils;
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
|
@ -84,6 +87,7 @@ import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -92,6 +96,9 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import monero.common.MoneroConnectionManagerListener;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
|
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
import monero.wallet.model.MoneroIncomingTransfer;
|
import monero.wallet.model.MoneroIncomingTransfer;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
import monero.wallet.model.MoneroTxQuery;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
@ -114,7 +121,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final CoreMoneroConnectionsService connectionService;
|
private final CoreMoneroConnectionsService connectionsService;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final TradeWalletService tradeWalletService;
|
private final TradeWalletService tradeWalletService;
|
||||||
|
@ -141,6 +148,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
@Getter
|
@Getter
|
||||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
|
|
||||||
|
// poll key images of signed offers
|
||||||
|
private MoneroKeyImagePoller signedOfferKeyImagePoller;
|
||||||
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
|
||||||
|
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor, Initialization
|
// Constructor, Initialization
|
||||||
|
@ -151,7 +163,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
User user,
|
User user,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
CoreMoneroConnectionsService connectionService,
|
CoreMoneroConnectionsService connectionsService,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
|
@ -171,7 +183,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.connectionService = connectionService;
|
this.connectionsService = connectionsService;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
|
@ -191,6 +203,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
||||||
this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers
|
this.signedOfferPersistenceManager.initialize(signedOffers, "SignedOffers", PersistenceManager.Source.PRIVATE); // arbitrator stores reserve tx for signed offers
|
||||||
|
|
||||||
|
// listen for connection changes to monerod
|
||||||
|
connectionsService.addListener(new MoneroConnectionManagerListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnectionChanged(MoneroRpcConnection connection) {
|
||||||
|
maybeInitializeKeyImagePoller();
|
||||||
|
signedOfferKeyImagePoller.setDaemon(connectionsService.getDaemon());
|
||||||
|
signedOfferKeyImagePoller.setRefreshPeriodMs(getKeyImageRefreshPeriodMs());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// remove open offer if reserved funds spent
|
// remove open offer if reserved funds spent
|
||||||
offerBookService.addOfferBookChangedListener(new OfferBookChangedListener() {
|
offerBookService.addOfferBookChangedListener(new OfferBookChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -225,6 +247,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
completeHandler);
|
completeHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void maybeInitializeKeyImagePoller() {
|
||||||
|
if (signedOfferKeyImagePoller != null) return;
|
||||||
|
signedOfferKeyImagePoller = new MoneroKeyImagePoller(connectionsService.getDaemon(), getKeyImageRefreshPeriodMs());
|
||||||
|
|
||||||
|
// handle when key images confirmed spent
|
||||||
|
signedOfferKeyImagePoller.addListener(new MoneroKeyImageListener() {
|
||||||
|
@Override
|
||||||
|
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
|
||||||
|
for (Entry<String, MoneroKeyImageSpentStatus> entry : spentStatuses.entrySet()) {
|
||||||
|
if (entry.getValue() == MoneroKeyImageSpentStatus.CONFIRMED) {
|
||||||
|
removeSignedOffers(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// first poll in 5s
|
||||||
|
new Thread(() -> {
|
||||||
|
GenUtils.waitFor(5000);
|
||||||
|
signedOfferKeyImagePoller.poll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getKeyImageRefreshPeriodMs() {
|
||||||
|
return connectionsService.isConnectionLocal() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE;
|
||||||
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
p2PService.addDecryptedDirectMessageListener(this);
|
p2PService.addDecryptedDirectMessageListener(this);
|
||||||
|
|
||||||
|
@ -264,6 +313,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
lastUnlockedBalance = newUnlockedBalance;
|
lastUnlockedBalance = newUnlockedBalance;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initialize key image poller for signed offers
|
||||||
|
maybeInitializeKeyImagePoller();
|
||||||
|
|
||||||
|
// poll spent status of key images
|
||||||
|
for (SignedOffer signedOffer : signedOffers.getList()) {
|
||||||
|
signedOfferKeyImagePoller.addKeyImages(signedOffer.getReserveTxKeyImages());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanUpAddressEntries() {
|
private void cleanUpAddressEntries() {
|
||||||
|
@ -672,9 +729,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSignedOffer(SignedOffer openOffer) {
|
private void addSignedOffer(SignedOffer signedOffer) {
|
||||||
|
log.info("Adding SignedOffer offer for offer {}", signedOffer.getOfferId());
|
||||||
synchronized (signedOffers) {
|
synchronized (signedOffers) {
|
||||||
signedOffers.add(openOffer);
|
signedOffers.add(signedOffer);
|
||||||
|
signedOfferKeyImagePoller.addKeyImages(signedOffer.getReserveTxKeyImages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSignedOffer(SignedOffer signedOffer) {
|
||||||
|
log.info("Removing SignedOffer for offer {}", signedOffer.getOfferId());
|
||||||
|
synchronized (signedOffers) {
|
||||||
|
signedOffers.remove(signedOffer);
|
||||||
|
signedOfferKeyImagePoller.removeKeyImages(signedOffer.getReserveTxKeyImages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSignedOffers(String keyImage) {
|
||||||
|
for (SignedOffer signedOffer : new ArrayList<SignedOffer>(signedOffers.getList())) {
|
||||||
|
if (signedOffer.getReserveTxKeyImages().contains(keyImage)) {
|
||||||
|
removeSignedOffer(signedOffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,7 +1083,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow trade start if Monero node is not fully synced
|
// Don't allow trade start if Monero node is not fully synced
|
||||||
if (!connectionService.isSyncedWithinTolerance()) {
|
if (!connectionsService.isSyncedWithinTolerance()) {
|
||||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||||
log.info(errorMessage);
|
log.info(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package bisq.core.offer;
|
package bisq.core.offer;
|
||||||
|
|
||||||
import bisq.core.api.CoreContext;
|
import bisq.core.api.CoreContext;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.trade.TradableList;
|
import bisq.core.trade.TradableList;
|
||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
@ -52,6 +53,7 @@ public class OpenOfferManagerTest {
|
||||||
public void testStartEditOfferForActiveOffer() {
|
public void testStartEditOfferForActiveOffer() {
|
||||||
P2PService p2PService = mock(P2PService.class);
|
P2PService p2PService = mock(P2PService.class);
|
||||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||||
|
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||||
|
|
||||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ public class OpenOfferManagerTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
p2PService,
|
p2PService,
|
||||||
null,
|
connectionsService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -100,13 +102,14 @@ public class OpenOfferManagerTest {
|
||||||
public void testStartEditOfferForDeactivatedOffer() {
|
public void testStartEditOfferForDeactivatedOffer() {
|
||||||
P2PService p2PService = mock(P2PService.class);
|
P2PService p2PService = mock(P2PService.class);
|
||||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||||
|
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||||
|
|
||||||
final OpenOfferManager manager = new OpenOfferManager(coreContext,
|
final OpenOfferManager manager = new OpenOfferManager(coreContext,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
p2PService,
|
p2PService,
|
||||||
null,
|
connectionsService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -139,6 +142,7 @@ public class OpenOfferManagerTest {
|
||||||
public void testStartEditOfferForOfferThatIsCurrentlyEdited() {
|
public void testStartEditOfferForOfferThatIsCurrentlyEdited() {
|
||||||
P2PService p2PService = mock(P2PService.class);
|
P2PService p2PService = mock(P2PService.class);
|
||||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||||
|
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||||
|
|
||||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||||
|
|
||||||
|
@ -147,7 +151,7 @@ public class OpenOfferManagerTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
p2PService,
|
p2PService,
|
||||||
null,
|
connectionsService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
Loading…
Reference in a new issue