mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 19:49:32 +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.
|
||||
*
|
||||
* TODO: move to monero-java?
|
||||
*/
|
||||
@Slf4j
|
||||
public class MoneroKeyImagePoller {
|
||||
|
@ -240,7 +242,7 @@ public class MoneroKeyImagePoller {
|
|||
setIsPolling(keyImages.size() > 0 && listeners.size() > 0);
|
||||
}
|
||||
|
||||
private void setIsPolling(boolean enabled) {
|
||||
private synchronized void setIsPolling(boolean enabled) {
|
||||
if (enabled) {
|
||||
if (!isPolling) {
|
||||
isPolling = true; // TODO monero-java: looper.isPolling()
|
||||
|
|
|
@ -69,6 +69,7 @@ public class OfferBookService {
|
|||
private final JsonFileManager jsonFileManager;
|
||||
private final CoreMoneroConnectionsService connectionsService;
|
||||
|
||||
// poll key images of offers
|
||||
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_REMOTE = 300000; // 5 minutes
|
||||
|
@ -96,7 +97,7 @@ public class OfferBookService {
|
|||
this.connectionsService = connectionsService;
|
||||
jsonFileManager = new JsonFileManager(storageDir);
|
||||
|
||||
// listen for monero connection changes
|
||||
// listen for connection changes to monerod
|
||||
connectionsService.addListener(new MoneroConnectionManagerListener() {
|
||||
@Override
|
||||
public void onConnectionChanged(MoneroRpcConnection connection) {
|
||||
|
@ -255,25 +256,25 @@ public class OfferBookService {
|
|||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void maybeInitializeKeyImagePoller() {
|
||||
synchronized (this) {
|
||||
if (keyImagePoller != null) return;
|
||||
keyImagePoller = new MoneroKeyImagePoller(connectionsService.getDaemon(), getKeyImageRefreshPeriodMs());
|
||||
keyImagePoller.addListener(new MoneroKeyImageListener() {
|
||||
@Override
|
||||
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
|
||||
for (String keyImage : spentStatuses.keySet()) {
|
||||
updateAffectedOffers(keyImage);
|
||||
}
|
||||
private synchronized void maybeInitializeKeyImagePoller() {
|
||||
if (keyImagePoller != null) return;
|
||||
keyImagePoller = new MoneroKeyImagePoller(connectionsService.getDaemon(), getKeyImageRefreshPeriodMs());
|
||||
|
||||
// handle when key images spent
|
||||
keyImagePoller.addListener(new MoneroKeyImageListener() {
|
||||
@Override
|
||||
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
|
||||
for (String keyImage : spentStatuses.keySet()) {
|
||||
updateAffectedOffers(keyImage);
|
||||
}
|
||||
});
|
||||
|
||||
// first poll after 5s
|
||||
new Thread(() -> {
|
||||
GenUtils.waitFor(5000);
|
||||
keyImagePoller.poll();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// first poll after 5s
|
||||
new Thread(() -> {
|
||||
GenUtils.waitFor(5000);
|
||||
keyImagePoller.poll();
|
||||
});
|
||||
}
|
||||
|
||||
private long getKeyImageRefreshPeriodMs() {
|
||||
|
|
|
@ -21,6 +21,8 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
|||
import bisq.core.api.CoreContext;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
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.XmrWalletService;
|
||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
|
@ -56,6 +58,7 @@ import bisq.network.p2p.P2PService;
|
|||
import bisq.network.p2p.SendDirectMessageListener;
|
||||
import bisq.network.p2p.peers.Broadcaster;
|
||||
import bisq.network.p2p.peers.PeerManager;
|
||||
import common.utils.GenUtils;
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.app.Capabilities;
|
||||
|
@ -84,6 +87,7 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -92,6 +96,9 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.MoneroTxQuery;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
@ -114,7 +121,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private final KeyRing keyRing;
|
||||
private final User user;
|
||||
private final P2PService p2PService;
|
||||
private final CoreMoneroConnectionsService connectionService;
|
||||
private final CoreMoneroConnectionsService connectionsService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
|
@ -141,6 +148,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
@Getter
|
||||
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
|
||||
|
@ -151,7 +163,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
KeyRing keyRing,
|
||||
User user,
|
||||
P2PService p2PService,
|
||||
CoreMoneroConnectionsService connectionService,
|
||||
CoreMoneroConnectionsService connectionsService,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
|
@ -171,7 +183,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
this.keyRing = keyRing;
|
||||
this.user = user;
|
||||
this.p2PService = p2PService;
|
||||
this.connectionService = connectionService;
|
||||
this.connectionsService = connectionsService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
|
@ -191,6 +203,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE);
|
||||
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
|
||||
offerBookService.addOfferBookChangedListener(new OfferBookChangedListener() {
|
||||
@Override
|
||||
|
@ -214,7 +236,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
persistenceManager.readPersisted(persisted -> {
|
||||
openOffers.setAll(persisted.getList());
|
||||
openOffers.forEach(openOffer -> openOffer.getOffer().setPriceFeedService(priceFeedService));
|
||||
|
||||
|
||||
// read signed offers
|
||||
signedOfferPersistenceManager.readPersisted(signedOfferPersisted -> {
|
||||
signedOffers.setAll(signedOfferPersisted.getList());
|
||||
|
@ -225,6 +247,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
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() {
|
||||
p2PService.addDecryptedDirectMessageListener(this);
|
||||
|
||||
|
@ -264,6 +313,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
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() {
|
||||
|
@ -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) {
|
||||
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
|
||||
if (!connectionService.isSyncedWithinTolerance()) {
|
||||
if (!connectionsService.isSyncedWithinTolerance()) {
|
||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||
log.info(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.api.CoreContext;
|
||||
import bisq.core.api.CoreMoneroConnectionsService;
|
||||
import bisq.core.trade.TradableList;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
|
@ -52,6 +53,7 @@ public class OpenOfferManagerTest {
|
|||
public void testStartEditOfferForActiveOffer() {
|
||||
P2PService p2PService = mock(P2PService.class);
|
||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||
|
||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||
|
||||
|
@ -59,7 +61,7 @@ public class OpenOfferManagerTest {
|
|||
null,
|
||||
null,
|
||||
p2PService,
|
||||
null,
|
||||
connectionsService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -100,13 +102,14 @@ public class OpenOfferManagerTest {
|
|||
public void testStartEditOfferForDeactivatedOffer() {
|
||||
P2PService p2PService = mock(P2PService.class);
|
||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||
|
||||
final OpenOfferManager manager = new OpenOfferManager(coreContext,
|
||||
null,
|
||||
null,
|
||||
p2PService,
|
||||
null,
|
||||
connectionsService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -139,6 +142,7 @@ public class OpenOfferManagerTest {
|
|||
public void testStartEditOfferForOfferThatIsCurrentlyEdited() {
|
||||
P2PService p2PService = mock(P2PService.class);
|
||||
OfferBookService offerBookService = mock(OfferBookService.class);
|
||||
CoreMoneroConnectionsService connectionsService = mock(CoreMoneroConnectionsService.class);
|
||||
|
||||
when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class));
|
||||
|
||||
|
@ -147,7 +151,7 @@ public class OpenOfferManagerTest {
|
|||
null,
|
||||
null,
|
||||
p2PService,
|
||||
null,
|
||||
connectionsService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
|
Loading…
Reference in a new issue