move trade wallet management functions from XmrWalletService to Trade
persist security deposits in TradePeer set deposit tx and reserved key images when deposit tx created listen to account service in trade manager
This commit is contained in:
parent
145157f84d
commit
60dc4901e4
@ -58,7 +58,6 @@ public class CoreDisputesService {
|
|||||||
private final CoinFormatter formatter;
|
private final CoinFormatter formatter;
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final XmrWalletService xmrWalletService;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoreDisputesService(ArbitrationManager arbitrationManager,
|
public CoreDisputesService(ArbitrationManager arbitrationManager,
|
||||||
@ -70,7 +69,6 @@ public class CoreDisputesService {
|
|||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.xmrWalletService = xmrWalletService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Dispute> getDisputes() {
|
public List<Dispute> getDisputes() {
|
||||||
@ -144,19 +142,19 @@ public class CoreDisputesService {
|
|||||||
|
|
||||||
// TODO: does not wait for success or error response
|
// TODO: does not wait for success or error response
|
||||||
public void resolveDispute(String tradeId, DisputeResult.Winner winner, DisputeResult.Reason reason, String summaryNotes, long customWinnerAmount) {
|
public void resolveDispute(String tradeId, DisputeResult.Winner winner, DisputeResult.Reason reason, String summaryNotes, long customWinnerAmount) {
|
||||||
try {
|
|
||||||
|
|
||||||
// get winning dispute
|
// get winning dispute
|
||||||
Dispute winningDispute;
|
Dispute winningDispute;
|
||||||
Trade trade = tradeManager.getTrade(tradeId);
|
Trade trade = tradeManager.getTrade(tradeId);
|
||||||
var winningDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream() // TODO (woodser): use getDispute()
|
var winningDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream() // TODO (woodser): use getDispute()
|
||||||
.filter(d -> tradeId.equals(d.getTradeId()))
|
.filter(d -> tradeId.equals(d.getTradeId()))
|
||||||
.filter(d -> trade.getTradePeer(d.getTraderPubKeyRing()) == (winner == DisputeResult.Winner.BUYER ? trade.getBuyer() : trade.getSeller()))
|
.filter(d -> trade.getTradePeer(d.getTraderPubKeyRing()) == (winner == DisputeResult.Winner.BUYER ? trade.getBuyer() : trade.getSeller()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (winningDisputeOptional.isPresent()) winningDispute = winningDisputeOptional.get();
|
if (winningDisputeOptional.isPresent()) winningDispute = winningDisputeOptional.get();
|
||||||
else throw new IllegalStateException(format("dispute for tradeId '%s' not found", tradeId));
|
else throw new IllegalStateException(format("dispute for tradeId '%s' not found", tradeId));
|
||||||
|
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
|
try {
|
||||||
var closeDate = new Date();
|
var closeDate = new Date();
|
||||||
var disputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
var disputeResult = createDisputeResult(winningDispute, winner, reason, summaryNotes, closeDate);
|
||||||
|
|
||||||
@ -193,9 +191,10 @@ public class CoreDisputesService {
|
|||||||
}, (errMessage, err) -> {
|
}, (errMessage, err) -> {
|
||||||
throw new IllegalStateException(errMessage, err);
|
throw new IllegalStateException(errMessage, err);
|
||||||
});
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new IllegalStateException(e.getMessage() == null ? ("Error resolving dispute for trade " + trade.getId()) : e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import bisq.core.provider.price.PriceFeedService;
|
|||||||
import bisq.core.setup.CorePersistedDataHost;
|
import bisq.core.setup.CorePersistedDataHost;
|
||||||
import bisq.core.setup.CoreSetup;
|
import bisq.core.setup.CoreSetup;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||||
@ -50,7 +51,7 @@ import com.google.inject.Guice;
|
|||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
import java.io.Console;
|
import java.io.Console;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@ -315,12 +316,14 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
|||||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||||
injector.getInstance(TradeManager.class).shutDown();
|
log.info("TradeManager and XmrWalletService shutdown started");
|
||||||
|
HavenoUtils.executeTasks(Arrays.asList( // shut down trade and main wallets at same time
|
||||||
|
() -> injector.getInstance(TradeManager.class).shutDown(),
|
||||||
|
() -> injector.getInstance(XmrWalletService.class).shutDown(!isReadOnly)));
|
||||||
log.info("OpenOfferManager shutdown started");
|
log.info("OpenOfferManager shutdown started");
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
||||||
log.info("OpenOfferManager shutdown completed");
|
log.info("OpenOfferManager shutdown completed");
|
||||||
|
|
||||||
injector.getInstance(XmrWalletService.class).shutDown(!isReadOnly);
|
|
||||||
injector.getInstance(BtcWalletService.class).shutDown();
|
injector.getInstance(BtcWalletService.class).shutDown();
|
||||||
|
|
||||||
// We need to shutdown BitcoinJ before the P2PService as it uses Tor.
|
// We need to shutdown BitcoinJ before the P2PService as it uses Tor.
|
||||||
|
@ -86,6 +86,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
|
|||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
JsonFileManager.shutDownAllInstances();
|
JsonFileManager.shutDownAllInstances();
|
||||||
injector.getInstance(ArbitratorManager.class).shutDown();
|
injector.getInstance(ArbitratorManager.class).shutDown();
|
||||||
|
injector.getInstance(XmrWalletService.class).shutDown(true);
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> {
|
||||||
injector.getInstance(WalletsSetup.class).shutDownComplete.addListener((ov, o, n) -> {
|
injector.getInstance(WalletsSetup.class).shutDownComplete.addListener((ov, o, n) -> {
|
||||||
module.close(injector);
|
module.close(injector);
|
||||||
@ -97,7 +98,6 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
injector.getInstance(WalletsSetup.class).shutDown();
|
injector.getInstance(WalletsSetup.class).shutDown();
|
||||||
injector.getInstance(XmrWalletService.class).shutDown(true);
|
|
||||||
injector.getInstance(BtcWalletService.class).shutDown();
|
injector.getInstance(BtcWalletService.class).shutDown();
|
||||||
}));
|
}));
|
||||||
// we wait max 5 sec.
|
// we wait max 5 sec.
|
||||||
|
@ -83,9 +83,8 @@ public class XmrWalletService {
|
|||||||
private static final String MONERO_WALLET_RPC_USERNAME = "haveno_user";
|
private static final String MONERO_WALLET_RPC_USERNAME = "haveno_user";
|
||||||
private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null
|
private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null
|
||||||
private static final String MONERO_WALLET_NAME = "haveno_XMR";
|
private static final String MONERO_WALLET_NAME = "haveno_XMR";
|
||||||
private static final String MONERO_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_";
|
|
||||||
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
|
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
|
||||||
private static final double SECURITY_DEPOSIT_TOLERANCE = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL ? 0.25 : 0.05; // security deposit can abosrb miner fee up to percent
|
private static final double SECURITY_DEPOSIT_TOLERANCE = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL ? 0.25 : 0.05; // security deposit can absorb miner fee up to percent
|
||||||
private static final double DUST_TOLERANCE = 0.01; // max dust as percent of mining fee
|
private static final double DUST_TOLERANCE = 0.01; // max dust as percent of mining fee
|
||||||
private static final int NUM_MAX_BACKUP_WALLETS = 10;
|
private static final int NUM_MAX_BACKUP_WALLETS = 10;
|
||||||
|
|
||||||
@ -101,8 +100,6 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
private TradeManager tradeManager;
|
private TradeManager tradeManager;
|
||||||
private MoneroWalletRpc wallet;
|
private MoneroWalletRpc wallet;
|
||||||
private Map<String, MoneroWallet> multisigWallets;
|
|
||||||
private Map<String, Object> walletLocks = new HashMap<String, Object>();
|
|
||||||
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
||||||
private boolean isShutDown = false;
|
private boolean isShutDown = false;
|
||||||
|
|
||||||
@ -117,7 +114,6 @@ public class XmrWalletService {
|
|||||||
this.connectionsService = connectionsService;
|
this.connectionsService = connectionsService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
this.xmrAddressEntryList = xmrAddressEntryList;
|
this.xmrAddressEntryList = xmrAddressEntryList;
|
||||||
this.multisigWallets = new HashMap<String, MoneroWallet>();
|
|
||||||
this.walletDir = walletDir;
|
this.walletDir = walletDir;
|
||||||
this.rpcBindPort = rpcBindPort;
|
this.rpcBindPort = rpcBindPort;
|
||||||
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
|
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
|
||||||
@ -133,20 +129,20 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAccountCreated() {
|
public void onAccountCreated() {
|
||||||
log.info(getClass() + ".accountService.onAccountCreated()");
|
log.info(getClass().getSimpleName() + ".accountService.onAccountCreated()");
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAccountOpened() {
|
public void onAccountOpened() {
|
||||||
log.info(getClass() + ".accountService.onAccountOpened()");
|
log.info(getClass().getSimpleName() + ".accountService.onAccountOpened()");
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAccountClosed() {
|
public void onAccountClosed() {
|
||||||
log.info(getClass() + ".accountService.onAccountClosed()");
|
log.info(getClass().getSimpleName() + ".accountService.onAccountClosed()");
|
||||||
closeAllWallets(true);
|
closeMainWallet(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -203,91 +199,68 @@ public class XmrWalletService {
|
|||||||
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
|
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void initWalletLock(String id) {
|
public boolean walletExists(String walletName) {
|
||||||
if (!walletLocks.containsKey(id)) walletLocks.put(id, new Object());
|
String path = walletDir.toString() + File.separator + walletName;
|
||||||
|
return new File(path + ".keys").exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean multisigWalletExists(String tradeId) {
|
public MoneroWalletRpc createWallet(String walletName) {
|
||||||
initWalletLock(tradeId);
|
log.info("{}.createWallet({})", getClass().getSimpleName(), walletName);
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
if (isShutDown) throw new IllegalStateException("Cannot create wallet because shutting down");
|
||||||
return walletExists(MONERO_MULTISIG_WALLET_PREFIX + tradeId);
|
return createWallet(new MoneroWalletConfig()
|
||||||
}
|
.setPath(walletName)
|
||||||
|
.setPassword(getWalletPassword()),
|
||||||
|
null,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroWallet createMultisigWallet(String tradeId) {
|
public MoneroWalletRpc openWallet(String walletName) {
|
||||||
log.info("{}.createMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
log.info("{}.openWallet({})", getClass().getSimpleName(), walletName);
|
||||||
initWalletLock(tradeId);
|
if (isShutDown) throw new IllegalStateException("Cannot open wallet because shutting down");
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
return openWallet(new MoneroWalletConfig()
|
||||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
.setPath(walletName)
|
||||||
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
.setPassword(getWalletPassword()),
|
||||||
MoneroWallet multisigWallet = createWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null, true); // auto-assign port
|
null);
|
||||||
multisigWallets.put(tradeId, multisigWallet);
|
|
||||||
return multisigWallet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): provide progress notifications during open?
|
public void saveWallet(MoneroWallet wallet, boolean backup) {
|
||||||
public MoneroWallet getMultisigWallet(String tradeId) {
|
|
||||||
if (isShutDown) throw new RuntimeException(getClass().getName() + " is shut down");
|
|
||||||
initWalletLock(tradeId);
|
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
|
||||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
|
||||||
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
|
||||||
if (!walletExists(path)) throw new RuntimeException("Multisig wallet does not exist for trade " + tradeId);
|
|
||||||
MoneroWallet multisigWallet = openWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null);
|
|
||||||
multisigWallets.put(tradeId, multisigWallet);
|
|
||||||
return multisigWallet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveMultisigWallet(String tradeId) {
|
|
||||||
log.info("{}.saveMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
|
||||||
initWalletLock(tradeId);
|
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
|
||||||
String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
|
||||||
if (!walletExists(walletName)) {
|
|
||||||
log.warn("Multisig wallet for trade {} does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!multisigWallets.containsKey(tradeId)) throw new RuntimeException("Multisig wallet to save was not previously opened for trade " + tradeId);
|
|
||||||
saveWallet(multisigWallets.get(tradeId), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveWallet(MoneroWallet wallet, boolean backup) {
|
|
||||||
wallet.save();
|
wallet.save();
|
||||||
if (backup) backupWallet(wallet.getPath());
|
if (backup) backupWallet(wallet.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeMultisigWallet(String tradeId) {
|
public void closeWallet(MoneroWallet wallet, boolean save) {
|
||||||
log.info("{}.closeMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
log.info("{}.closeWallet({}, {})", getClass().getSimpleName(), wallet.getPath(), save);
|
||||||
initWalletLock(tradeId);
|
MoneroError err = null;
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
try {
|
||||||
if (!multisigWallets.containsKey(tradeId)) throw new RuntimeException("Multisig wallet to close was not previously opened for trade " + tradeId);
|
String path = wallet.getPath();
|
||||||
MoneroWallet wallet = multisigWallets.remove(tradeId);
|
wallet.close(save);
|
||||||
closeWallet(wallet, true);
|
if (save) backupWallet(path);
|
||||||
|
} catch (MoneroError e) {
|
||||||
|
err = e;
|
||||||
}
|
}
|
||||||
|
MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) wallet);
|
||||||
|
if (err != null) throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteMultisigWallet(String tradeId) {
|
public void deleteWallet(String walletName) {
|
||||||
log.info("{}.deleteMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
log.info("{}.deleteWallet({})", getClass().getSimpleName(), walletName);
|
||||||
initWalletLock(tradeId);
|
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
String path = walletDir.toString() + File.separator + walletName;
|
||||||
String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||||
if (!walletExists(walletName)) return false;
|
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||||
if (multisigWallets.containsKey(tradeId)) closeMultisigWallet(tradeId);
|
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||||
deleteWallet(walletName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteMultisigWalletBackups(String tradeId) {
|
public void backupWallet(String walletName) {
|
||||||
log.info("{}.deleteMultisigWalletBackups({})", getClass().getSimpleName(), tradeId);
|
FileUtil.rollingBackup(walletDir, walletName, NUM_MAX_BACKUP_WALLETS);
|
||||||
initWalletLock(tradeId);
|
FileUtil.rollingBackup(walletDir, walletName + ".keys", NUM_MAX_BACKUP_WALLETS);
|
||||||
synchronized (walletLocks.get(tradeId)) {
|
FileUtil.rollingBackup(walletDir, walletName + ".address.txt", NUM_MAX_BACKUP_WALLETS);
|
||||||
String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
}
|
||||||
deleteWalletBackups(walletName);
|
|
||||||
}
|
public void deleteWalletBackups(String walletName) {
|
||||||
|
FileUtil.deleteRollingBackup(walletDir, walletName);
|
||||||
|
FileUtil.deleteRollingBackup(walletDir, walletName + ".keys");
|
||||||
|
FileUtil.deleteRollingBackup(walletDir, walletName + ".address.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
||||||
@ -404,56 +377,58 @@ public class XmrWalletService {
|
|||||||
public void verifyTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
|
public void verifyTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, String txHash, String txHex, String txKey, List<String> keyImages) {
|
||||||
MoneroDaemonRpc daemon = getDaemon();
|
MoneroDaemonRpc daemon = getDaemon();
|
||||||
MoneroWallet wallet = getWallet();
|
MoneroWallet wallet = getWallet();
|
||||||
try {
|
synchronized (daemon) {
|
||||||
|
|
||||||
// verify tx not submitted to pool
|
|
||||||
MoneroTx tx = daemon.getTx(txHash);
|
|
||||||
if (tx != null) throw new RuntimeException("Tx is already submitted");
|
|
||||||
|
|
||||||
// submit tx to pool
|
|
||||||
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
|
|
||||||
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
|
||||||
tx = getTx(txHash);
|
|
||||||
|
|
||||||
// verify key images
|
|
||||||
if (keyImages != null) {
|
|
||||||
Set<String> txKeyImages = new HashSet<String>();
|
|
||||||
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
|
||||||
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images");
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify unlock height
|
|
||||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
|
||||||
|
|
||||||
// verify trade fee
|
|
||||||
String feeAddress = HavenoUtils.getTradeFeeAddress();
|
|
||||||
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
|
||||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
|
||||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
|
||||||
|
|
||||||
// verify miner fee
|
|
||||||
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
|
|
||||||
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
|
||||||
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
|
|
||||||
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
|
|
||||||
|
|
||||||
// verify sufficient security deposit
|
|
||||||
check = wallet.checkTxKey(txHash, txKey, address);
|
|
||||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
|
||||||
BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
|
|
||||||
BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount);
|
|
||||||
if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit);
|
|
||||||
|
|
||||||
// verify deposit amount + miner fee within dust tolerance
|
|
||||||
BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger());
|
|
||||||
BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee());
|
|
||||||
if (actualDepositAndFee.compareTo(minDepositAndFee) < 0) throw new RuntimeException("Deposit amount + fee is not enough, needed " + minDepositAndFee + " but was " + actualDepositAndFee);
|
|
||||||
} finally {
|
|
||||||
try {
|
try {
|
||||||
daemon.flushTxPool(txHash); // flush tx from pool
|
|
||||||
} catch (MoneroRpcError err) {
|
// verify tx not submitted to pool
|
||||||
System.out.println(daemon.getRpcConnection());
|
MoneroTx tx = daemon.getTx(txHash);
|
||||||
throw err.getCode() == -32601 ? new RuntimeException("Failed to flush tx from pool. Arbitrator must use trusted, unrestricted daemon") : err;
|
if (tx != null) throw new RuntimeException("Tx is already submitted");
|
||||||
|
|
||||||
|
// submit tx to pool
|
||||||
|
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
|
||||||
|
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
|
||||||
|
tx = getTx(txHash);
|
||||||
|
|
||||||
|
// verify key images
|
||||||
|
if (keyImages != null) {
|
||||||
|
Set<String> txKeyImages = new HashSet<String>();
|
||||||
|
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images");
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify unlock height
|
||||||
|
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||||
|
|
||||||
|
// verify trade fee
|
||||||
|
String feeAddress = HavenoUtils.getTradeFeeAddress();
|
||||||
|
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
||||||
|
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||||
|
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||||
|
|
||||||
|
// verify miner fee
|
||||||
|
BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
|
||||||
|
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||||
|
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
|
||||||
|
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
|
||||||
|
|
||||||
|
// verify sufficient security deposit
|
||||||
|
check = wallet.checkTxKey(txHash, txKey, address);
|
||||||
|
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
|
||||||
|
BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
|
||||||
|
BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount);
|
||||||
|
if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit);
|
||||||
|
|
||||||
|
// verify deposit amount + miner fee within dust tolerance
|
||||||
|
BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger());
|
||||||
|
BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee());
|
||||||
|
if (actualDepositAndFee.compareTo(minDepositAndFee) < 0) throw new RuntimeException("Deposit amount + fee is not enough, needed " + minDepositAndFee + " but was " + actualDepositAndFee);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
daemon.flushTxPool(txHash); // flush tx from pool
|
||||||
|
} catch (MoneroRpcError err) {
|
||||||
|
System.out.println(daemon.getRpcConnection());
|
||||||
|
throw err.getCode() == -32601 ? new RuntimeException("Failed to flush tx from pool. Arbitrator must use trusted, unrestricted daemon") : err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,9 +503,19 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void closeMainWallet(boolean save) {
|
||||||
|
try {
|
||||||
|
closeWallet(wallet, true);
|
||||||
|
wallet = null;
|
||||||
|
walletListeners.clear();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void shutDown(boolean save) {
|
public void shutDown(boolean save) {
|
||||||
this.isShutDown = true;
|
this.isShutDown = true;
|
||||||
closeAllWallets(save);
|
closeMainWallet(save);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------ PRIVATE HELPERS -------------------------
|
// ------------------------------ PRIVATE HELPERS -------------------------
|
||||||
@ -544,11 +529,6 @@ public class XmrWalletService {
|
|||||||
connectionsService.addListener(newConnection -> setDaemonConnection(newConnection));
|
connectionsService.addListener(newConnection -> setDaemonConnection(newConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean walletExists(String walletName) {
|
|
||||||
String path = walletDir.toString() + File.separator + walletName;
|
|
||||||
return new File(path + ".keys").exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeInitMainWallet() {
|
private void maybeInitMainWallet() {
|
||||||
if (wallet != null) throw new RuntimeException("Main wallet is already initialized");
|
if (wallet != null) throw new RuntimeException("Main wallet is already initialized");
|
||||||
|
|
||||||
@ -605,7 +585,7 @@ public class XmrWalletService {
|
|||||||
} else {
|
} else {
|
||||||
walletRpc.setDaemonConnection(connection);
|
walletRpc.setDaemonConnection(connection);
|
||||||
}
|
}
|
||||||
log.info("Done creating wallet " + config.getPath());
|
log.info("Done creating wallet " + walletRpc.getPath());
|
||||||
return walletRpc;
|
return walletRpc;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -624,6 +604,7 @@ public class XmrWalletService {
|
|||||||
log.info("Opening wallet " + config.getPath());
|
log.info("Opening wallet " + config.getPath());
|
||||||
walletRpc.openWallet(config);
|
walletRpc.openWallet(config);
|
||||||
walletRpc.setDaemonConnection(connectionsService.getConnection());
|
walletRpc.setDaemonConnection(connectionsService.getConnection());
|
||||||
|
log.info("Done opening wallet " + walletRpc.getPath());
|
||||||
return walletRpc;
|
return walletRpc;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -718,80 +699,18 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// create tasks to change multisig wallet passwords
|
// create tasks to change trade wallet passwords
|
||||||
List<String> tradeIds = tradeManager.getOpenTrades().stream().map(Trade::getId).collect(Collectors.toList());
|
List<Trade> trades = tradeManager.getAllTrades();
|
||||||
for (String tradeId : tradeIds) {
|
for (Trade trade : trades) {
|
||||||
tasks.add(() -> {
|
tasks.add(() -> {
|
||||||
MoneroWallet multisigWallet = getMultisigWallet(tradeId); // TODO (woodser): this unnecessarily connects and syncs unopen wallets and leaves open
|
if (trade.walletExists()) {
|
||||||
if (multisigWallet == null) return;
|
trade.changeWalletPassword(oldPassword, newPassword); // TODO (woodser): this unnecessarily connects and syncs unopen wallets and leaves open
|
||||||
multisigWallet.changePassword(oldPassword, newPassword);
|
}
|
||||||
saveMultisigWallet(tradeId);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// excute tasks in parallel
|
// excute tasks in parallel
|
||||||
HavenoUtils.executeTasks(tasks, Math.min(10, 1 + tradeIds.size()));
|
HavenoUtils.executeTasks(tasks, Math.min(10, 1 + trades.size()));
|
||||||
}
|
|
||||||
|
|
||||||
private void closeWallet(MoneroWallet walletRpc, boolean save) {
|
|
||||||
log.info("{}.closeWallet({}, {})", getClass().getSimpleName(), walletRpc.getPath(), save);
|
|
||||||
MoneroError err = null;
|
|
||||||
try {
|
|
||||||
String path = walletRpc.getPath();
|
|
||||||
walletRpc.close(save);
|
|
||||||
if (save) backupWallet(path);
|
|
||||||
} catch (MoneroError e) {
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc);
|
|
||||||
if (err != null) throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteWallet(String walletName) {
|
|
||||||
log.info("{}.deleteWallet({})", getClass().getSimpleName(), walletName);
|
|
||||||
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
|
|
||||||
String path = walletDir.toString() + File.separator + walletName;
|
|
||||||
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeAllWallets(boolean save) {
|
|
||||||
|
|
||||||
// collect wallets to shutdown
|
|
||||||
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
|
|
||||||
if (wallet != null) openWallets.add(wallet);
|
|
||||||
for (String multisigWalletKey : multisigWallets.keySet()) {
|
|
||||||
openWallets.add(multisigWallets.get(multisigWalletKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// close wallets in parallel
|
|
||||||
Set<Runnable> tasks = new HashSet<Runnable>();
|
|
||||||
for (MoneroWallet wallet : openWallets) tasks.add(() -> {
|
|
||||||
try {
|
|
||||||
closeWallet(wallet, true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
HavenoUtils.executeTasks(tasks);
|
|
||||||
|
|
||||||
// clear wallets
|
|
||||||
wallet = null;
|
|
||||||
multisigWallets.clear();
|
|
||||||
walletListeners.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void backupWallet(String walletName) {
|
|
||||||
FileUtil.rollingBackup(walletDir, walletName, NUM_MAX_BACKUP_WALLETS);
|
|
||||||
FileUtil.rollingBackup(walletDir, walletName + ".keys", NUM_MAX_BACKUP_WALLETS);
|
|
||||||
FileUtil.rollingBackup(walletDir, walletName + ".address.txt", NUM_MAX_BACKUP_WALLETS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteWalletBackups(String walletName) {
|
|
||||||
FileUtil.deleteRollingBackup(walletDir, walletName);
|
|
||||||
FileUtil.deleteRollingBackup(walletDir, walletName + ".keys");
|
|
||||||
FileUtil.deleteRollingBackup(walletDir, walletName + ".address.txt");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------- LEGACY APP -------------------------------
|
// ----------------------------- LEGACY APP -------------------------------
|
||||||
|
@ -445,7 +445,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
PubKeyRing senderPubKeyRing = null;
|
PubKeyRing senderPubKeyRing = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// intialize
|
// initialize
|
||||||
T disputeList = getDisputeList();
|
T disputeList = getDisputeList();
|
||||||
if (disputeList == null) {
|
if (disputeList == null) {
|
||||||
log.warn("disputes is null");
|
log.warn("disputes is null");
|
||||||
|
@ -86,6 +86,10 @@ public class HavenoUtils {
|
|||||||
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
|
return atomicUnitsToXmr(centinerosToAtomicUnits(centineros));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Coin centinerosToCoin(long centineros) {
|
||||||
|
return atomicUnitsToCoin(centinerosToAtomicUnits(centineros));
|
||||||
|
}
|
||||||
|
|
||||||
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
|
public static long atomicUnitsToCentineros(long atomicUnits) { // TODO: atomic units should be BigInteger; remove this?
|
||||||
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
|
return atomicUnits / CENTINEROS_AU_MULTIPLIER.longValue();
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,10 @@ import monero.wallet.model.MoneroWalletListener;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class Trade implements Tradable, Model {
|
public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
|
private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_";
|
||||||
|
private MoneroWallet wallet; // trade wallet
|
||||||
|
private Object walletLock = new Object();
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Enums
|
// Enums
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -412,7 +416,6 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private long lockTime;
|
private long lockTime;
|
||||||
@Getter
|
|
||||||
@Setter
|
@Setter
|
||||||
private long startTime; // added for haveno
|
private long startTime; // added for haveno
|
||||||
@Getter
|
@Getter
|
||||||
@ -583,7 +586,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// API
|
// INITIALIZATION
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void initialize(ProcessModelServiceProvider serviceProvider) {
|
public void initialize(ProcessModelServiceProvider serviceProvider) {
|
||||||
@ -680,11 +683,42 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
return getArbitrator() == null ? null : getArbitrator().getNodeAddress();
|
return getArbitrator() == null ? null : getArbitrator().getNodeAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// WALLET MANAGEMENT
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean walletExists() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
return xmrWalletService.walletExists(MONERO_TRADE_WALLET_PREFIX + getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroWallet createWallet() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
if (walletExists()) throw new RuntimeException("Cannot create trade wallet because it already exists");
|
||||||
|
wallet = xmrWalletService.createWallet(getWalletName());
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroWallet getWallet() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
if (wallet != null) return wallet;
|
||||||
|
if (!walletExists()) return null;
|
||||||
|
if (isInitialized) wallet = xmrWalletService.openWallet(getWalletName());
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getWalletName() {
|
||||||
|
return MONERO_TRADE_WALLET_PREFIX + getId();
|
||||||
|
}
|
||||||
|
|
||||||
public void checkWalletConnection() {
|
public void checkWalletConnection() {
|
||||||
CoreMoneroConnectionsService connectionService = xmrWalletService.getConnectionsService();
|
CoreMoneroConnectionsService connectionService = xmrWalletService.getConnectionsService();
|
||||||
connectionService.checkConnection();
|
connectionService.checkConnection();
|
||||||
connectionService.verifyConnection();
|
connectionService.verifyConnection();
|
||||||
if (!getWallet().isConnectedToDaemon()) throw new RuntimeException("Wallet is not connected to a Monero node");
|
if (!getWallet().isConnectedToDaemon()) throw new RuntimeException("Trade wallet is not connected to a Monero node");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWalletConnected() {
|
public boolean isWalletConnected() {
|
||||||
@ -696,6 +730,88 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void syncWallet() {
|
||||||
|
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
||||||
|
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
||||||
|
log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
getWallet().sync();
|
||||||
|
pollWallet();
|
||||||
|
log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
updateWalletRefreshPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trySyncWallet() {
|
||||||
|
try {
|
||||||
|
syncWallet();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (isInitialized) {
|
||||||
|
log.warn("Error syncing trade wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncWalletNormallyForMs(long syncNormalDuration) {
|
||||||
|
syncNormalStartTime = System.currentTimeMillis();
|
||||||
|
setWalletRefreshPeriod(xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs());
|
||||||
|
UserThread.runAfter(() -> {
|
||||||
|
if (isInitialized && System.currentTimeMillis() >= syncNormalStartTime + syncNormalDuration) updateWalletRefreshPeriod();
|
||||||
|
}, syncNormalDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeWalletPassword(String oldPassword, String newPassword) {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
getWallet().changePassword(oldPassword, newPassword);
|
||||||
|
saveWallet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveWallet() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
if (wallet == null) throw new RuntimeException("Trade wallet is not open for trade " + getId());
|
||||||
|
xmrWalletService.saveWallet(wallet, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeWallet() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
if (wallet == null) throw new RuntimeException("Trade wallet to close was not previously opened for trade " + getId());
|
||||||
|
if (wallet.getPath() == null) log.warn("HOW DID PATH BECOME NULL?");
|
||||||
|
xmrWalletService.closeWallet(wallet, true);
|
||||||
|
wallet = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteWallet() {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
if (walletExists()) {
|
||||||
|
|
||||||
|
// check if funds deposited but payout not unlocked
|
||||||
|
if (isDepositsPublished() && !isPayoutUnlocked()) {
|
||||||
|
log.warn("Refusing to delete wallet for {} {} because it could be funded", getClass().getSimpleName(), getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close and delete trade wallet
|
||||||
|
if (wallet != null) closeWallet();
|
||||||
|
xmrWalletService.deleteWallet(getWalletName());
|
||||||
|
|
||||||
|
// delete trade wallet backups unless deposits requested and payouts not unlocked
|
||||||
|
if (isDepositRequested() && !isPayoutUnlocked()) {
|
||||||
|
log.warn("Refusing to delete backup wallet for {} {} in the small chance it becomes funded", getClass().getSimpleName(), getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xmrWalletService.deleteWalletBackups(getWalletName());
|
||||||
|
} else {
|
||||||
|
log.warn("Multisig wallet to delete for trade {} does not exist", getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// PROTOCOL API
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a contract based on the current state.
|
* Create a contract based on the current state.
|
||||||
*
|
*
|
||||||
@ -959,70 +1075,17 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroWallet getWallet() {
|
|
||||||
return xmrWalletService.multisigWalletExists(getId()) ? xmrWalletService.getMultisigWallet(getId()) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void syncWallet() {
|
|
||||||
if (getWallet() == null) throw new RuntimeException("Cannot sync multisig wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
|
||||||
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync multisig wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
|
||||||
log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
|
||||||
getWallet().sync();
|
|
||||||
pollWallet();
|
|
||||||
log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
|
||||||
updateWalletRefreshPeriod();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void trySyncWallet() {
|
|
||||||
try {
|
|
||||||
syncWallet();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (isInitialized) log.warn("Error syncing wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void syncWalletNormallyForMs(long syncNormalDuration) {
|
|
||||||
syncNormalStartTime = System.currentTimeMillis();
|
|
||||||
setWalletRefreshPeriod(xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs());
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
if (isInitialized && System.currentTimeMillis() >= syncNormalStartTime + syncNormalDuration) updateWalletRefreshPeriod();
|
|
||||||
}, syncNormalDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void saveWallet() {
|
|
||||||
xmrWalletService.saveMultisigWallet(getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteWallet() {
|
|
||||||
if (xmrWalletService.multisigWalletExists(getId())) {
|
|
||||||
|
|
||||||
// delete trade wallet unless funded
|
|
||||||
if (isDepositsPublished() && !isPayoutUnlocked()) {
|
|
||||||
log.warn("Refusing to delete wallet for {} {} because it could be funded", getClass().getSimpleName(), getId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xmrWalletService.deleteMultisigWallet(getId());
|
|
||||||
|
|
||||||
// delete trade wallet backups unless possibly funded
|
|
||||||
boolean possiblyFunded = isDepositRequested() && !isPayoutUnlocked();
|
|
||||||
if (possiblyFunded) {
|
|
||||||
log.warn("Refusing to delete backup wallet for {} {} in the small chance it becomes funded", getClass().getSimpleName(), getId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xmrWalletService.deleteMultisigWalletBackups(getId());
|
|
||||||
} else {
|
|
||||||
log.warn("Multisig wallet to delete for trade {} does not exist", getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
isInitialized = false;
|
synchronized (walletLock) {
|
||||||
if (txPollLooper != null) {
|
isInitialized = false;
|
||||||
txPollLooper.stop();
|
if (wallet != null) closeWallet();
|
||||||
txPollLooper = null;
|
if (txPollLooper != null) {
|
||||||
|
txPollLooper.stop();
|
||||||
|
txPollLooper = null;
|
||||||
|
}
|
||||||
|
if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe();
|
||||||
|
if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe();
|
|
||||||
if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -1458,23 +1521,13 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Coin getBuyerSecurityDeposit() {
|
public Coin getBuyerSecurityDeposit() {
|
||||||
if (this.getBuyer().getDepositTxHash() == null) return null;
|
if (getBuyer().getDepositTxHash() == null) return null;
|
||||||
try {
|
return HavenoUtils.centinerosToCoin(getBuyer().getSecurityDeposit());
|
||||||
MoneroTxWallet depositTx = getWallet().getTx(this.getBuyer().getDepositTxHash()); // TODO (monero-java): return null if tx id not found instead of throw exception
|
|
||||||
return HavenoUtils.atomicUnitsToCoin(depositTx.getIncomingAmount());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Coin getSellerSecurityDeposit() {
|
public Coin getSellerSecurityDeposit() {
|
||||||
if (this.getSeller().getDepositTxHash() == null) return null;
|
if (getSeller().getDepositTxHash() == null) return null;
|
||||||
try {
|
return HavenoUtils.centinerosToCoin(getSeller().getSecurityDeposit());
|
||||||
MoneroTxWallet depositTx = getWallet().getTx(this.getSeller().getDepositTxHash()); // TODO (monero-java): return null if tx id not found instead of throw exception
|
|
||||||
return HavenoUtils.atomicUnitsToCoin(depositTx.getIncomingAmount()).subtract(getAmount());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -1603,11 +1656,23 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// check deposit txs
|
// check deposit txs
|
||||||
if (!isDepositsUnlocked()) {
|
if (!isDepositsUnlocked()) {
|
||||||
if (txs.size() == 2) {
|
if (txs.size() == 2) {
|
||||||
setStateDepositsPublished();
|
|
||||||
|
// update trader state
|
||||||
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
|
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
|
||||||
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
|
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
|
||||||
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
|
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
|
||||||
|
|
||||||
|
// set security deposits
|
||||||
|
if (getBuyer().getSecurityDeposit() == 0) {
|
||||||
|
BigInteger buyerSecurityDeposit = ((MoneroTxWallet) getBuyer().getDepositTx()).getIncomingAmount();
|
||||||
|
BigInteger sellerSecurityDeposit = ((MoneroTxWallet) getSeller().getDepositTx()).getIncomingAmount().subtract(HavenoUtils.coinToAtomicUnits(getAmount()));
|
||||||
|
getBuyer().setSecurityDeposit(HavenoUtils.atomicUnitsToCentineros(buyerSecurityDeposit));
|
||||||
|
getSeller().setSecurityDeposit(HavenoUtils.atomicUnitsToCentineros(sellerSecurityDeposit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set deposits published state
|
||||||
|
setStateDepositsPublished();
|
||||||
|
|
||||||
// check if deposit txs confirmed
|
// check if deposit txs confirmed
|
||||||
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) setStateDepositsConfirmed();
|
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) setStateDepositsConfirmed();
|
||||||
if (!txs.get(0).isLocked() && !txs.get(1).isLocked()) setStateDepositsUnlocked();
|
if (!txs.get(0).isLocked() && !txs.get(1).isLocked()) setStateDepositsUnlocked();
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package bisq.core.trade;
|
package bisq.core.trade;
|
||||||
|
|
||||||
|
import bisq.core.api.AccountServiceListener;
|
||||||
|
import bisq.core.api.CoreAccountService;
|
||||||
import bisq.core.api.CoreNotificationService;
|
import bisq.core.api.CoreNotificationService;
|
||||||
import bisq.core.btc.model.XmrAddressEntry;
|
import bisq.core.btc.model.XmrAddressEntry;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
@ -121,6 +123,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
private final User user;
|
private final User user;
|
||||||
@Getter
|
@Getter
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
|
private final CoreAccountService accountService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final CoreNotificationService notificationService;
|
private final CoreNotificationService notificationService;
|
||||||
private final OfferBookService offerBookService;
|
private final OfferBookService offerBookService;
|
||||||
@ -158,6 +161,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
@Inject
|
@Inject
|
||||||
public TradeManager(User user,
|
public TradeManager(User user,
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
|
CoreAccountService accountService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
CoreNotificationService notificationService,
|
CoreNotificationService notificationService,
|
||||||
OfferBookService offerBookService,
|
OfferBookService offerBookService,
|
||||||
@ -177,6 +181,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
ReferralIdService referralIdService) {
|
ReferralIdService referralIdService) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
|
this.accountService = accountService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
this.offerBookService = offerBookService;
|
this.offerBookService = offerBookService;
|
||||||
@ -250,6 +255,39 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
// listen for account updates
|
||||||
|
accountService.addListener(new AccountServiceListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccountCreated() {
|
||||||
|
log.info(getClass().getSimpleName() + ".accountService.onAccountCreated()");
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccountOpened() {
|
||||||
|
log.info(getClass().getSimpleName() + ".accountService.onAccountOpened()");
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccountClosed() {
|
||||||
|
log.info(getClass().getSimpleName() + ".accountService.onAccountClosed()");
|
||||||
|
closeAllTrades();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPasswordChanged(String oldPassword, String newPassword) {
|
||||||
|
// handled in XmrWalletService
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
if (p2PService.isBootstrapped()) {
|
if (p2PService.isBootstrapped()) {
|
||||||
new Thread(() -> initPersistedTrades()).start(); // initialize trades off main thread
|
new Thread(() -> initPersistedTrades()).start(); // initialize trades off main thread
|
||||||
} else {
|
} else {
|
||||||
@ -272,6 +310,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
|
|
||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
isShutDown = true;
|
isShutDown = true;
|
||||||
|
closeAllTrades();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeAllTrades() {
|
||||||
|
|
||||||
// collect trades to shutdown
|
// collect trades to shutdown
|
||||||
Set<Trade> trades = new HashSet<Trade>();
|
Set<Trade> trades = new HashSet<Trade>();
|
||||||
@ -341,11 +383,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
|
|
||||||
private void initPersistedTrades() {
|
private void initPersistedTrades() {
|
||||||
|
|
||||||
// get all trades // TODO: getAllTrades()
|
// get all trades
|
||||||
List<Trade> trades = new ArrayList<Trade>();
|
List<Trade> trades = getAllTrades();
|
||||||
trades.addAll(tradableList.getList());
|
|
||||||
trades.addAll(closedTradableManager.getClosedTrades());
|
|
||||||
trades.addAll(failedTradesManager.getObservableList());
|
|
||||||
|
|
||||||
// open trades in parallel since each may open a multisig wallet
|
// open trades in parallel since each may open a multisig wallet
|
||||||
int threadPoolSize = 10;
|
int threadPoolSize = 10;
|
||||||
@ -1037,6 +1076,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Trade> getAllTrades() {
|
||||||
|
List<Trade> trades = new ArrayList<Trade>();
|
||||||
|
trades.addAll(tradableList.getList());
|
||||||
|
trades.addAll(closedTradableManager.getClosedTrades());
|
||||||
|
trades.addAll(failedTradesManager.getObservableList());
|
||||||
|
return trades;
|
||||||
|
}
|
||||||
|
|
||||||
public List<Trade> getOpenTrades() {
|
public List<Trade> getOpenTrades() {
|
||||||
synchronized (tradableList) {
|
synchronized (tradableList) {
|
||||||
return ImmutableList.copyOf(getObservableList().stream()
|
return ImmutableList.copyOf(getObservableList().stream()
|
||||||
@ -1085,7 +1132,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove trade if wallet deleted
|
// remove trade if wallet deleted
|
||||||
if (!xmrWalletService.multisigWalletExists(trade.getId())) {
|
if (!trade.walletExists()) {
|
||||||
removeTrade(trade);
|
removeTrade(trade);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1093,7 +1140,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
// remove trade and wallet unless deposit requested without nack
|
// remove trade and wallet unless deposit requested without nack
|
||||||
if (!trade.isDepositRequested() || trade.isDepositFailed()) {
|
if (!trade.isDepositRequested() || trade.isDepositFailed()) {
|
||||||
removeTrade(trade);
|
removeTrade(trade);
|
||||||
if (xmrWalletService.multisigWalletExists(trade.getId())) trade.deleteWallet();
|
if (trade.walletExists()) trade.deleteWallet();
|
||||||
} else {
|
} else {
|
||||||
scheduleDeletionIfUnfunded(trade);
|
scheduleDeletionIfUnfunded(trade);
|
||||||
}
|
}
|
||||||
@ -1115,7 +1162,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
log.warn("Deleting {} {} after protocol timeout", trade.getClass().getSimpleName(), trade.getId());
|
log.warn("Deleting {} {} after protocol timeout", trade.getClass().getSimpleName(), trade.getId());
|
||||||
removeTrade(trade);
|
removeTrade(trade);
|
||||||
failedTradesManager.removeTrade(trade);
|
failedTradesManager.removeTrade(trade);
|
||||||
if (xmrWalletService.multisigWalletExists(trade.getId())) trade.deleteWallet();
|
if (trade.walletExists()) trade.deleteWallet();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Refusing to delete {} {} after protocol timeout because its wallet might be funded", trade.getClass().getSimpleName(), trade.getId());
|
log.warn("Refusing to delete {} {} after protocol timeout because its wallet might be funded", trade.getClass().getSimpleName(), trade.getId());
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ public final class TradePeer implements PersistablePayload {
|
|||||||
private String depositTxHex;
|
private String depositTxHex;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String depositTxKey;
|
private String depositTxKey;
|
||||||
|
private long securityDeposit;
|
||||||
@Nullable
|
@Nullable
|
||||||
private String updatedMultisigHex;
|
private String updatedMultisigHex;
|
||||||
|
|
||||||
@ -168,6 +169,7 @@ public final class TradePeer implements PersistablePayload {
|
|||||||
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
Optional.ofNullable(depositTxHash).ifPresent(e -> builder.setDepositTxHash(depositTxHash));
|
||||||
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
Optional.ofNullable(depositTxHex).ifPresent(e -> builder.setDepositTxHex(depositTxHex));
|
||||||
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
Optional.ofNullable(depositTxKey).ifPresent(e -> builder.setDepositTxKey(depositTxKey));
|
||||||
|
Optional.ofNullable(securityDeposit).ifPresent(e -> builder.setSecurityDeposit(securityDeposit));
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||||
|
|
||||||
builder.setCurrentDate(currentDate);
|
builder.setCurrentDate(currentDate);
|
||||||
@ -218,6 +220,7 @@ public final class TradePeer implements PersistablePayload {
|
|||||||
tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
tradePeer.setDepositTxHash(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHash()));
|
||||||
tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
tradePeer.setDepositTxHex(ProtoUtil.stringOrNullFromProto(proto.getDepositTxHex()));
|
||||||
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
tradePeer.setDepositTxKey(ProtoUtil.stringOrNullFromProto(proto.getDepositTxKey()));
|
||||||
|
tradePeer.setSecurityDeposit(proto.getSecurityDeposit());
|
||||||
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
tradePeer.setUpdatedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||||
return tradePeer;
|
return tradePeer;
|
||||||
}
|
}
|
||||||
|
@ -19,16 +19,11 @@ package bisq.core.trade.protocol.tasks;
|
|||||||
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.Sig;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.protocol.TradeListener;
|
|
||||||
import bisq.network.p2p.AckMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
import bisq.network.p2p.SendDirectMessageListener;
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -123,7 +118,7 @@ public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create wallet for multisig
|
// create wallet for multisig
|
||||||
MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId());
|
MoneroWallet multisigWallet = trade.createWallet();
|
||||||
|
|
||||||
// prepare multisig
|
// prepare multisig
|
||||||
String preparedHex = multisigWallet.prepareMultisig();
|
String preparedHex = multisigWallet.prepareMultisig();
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
@ -67,8 +66,7 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||||
|
|
||||||
// get multisig wallet
|
// get multisig wallet
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
MoneroWallet multisigWallet = trade.getWallet();
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
|
||||||
|
|
||||||
// import multisig hex
|
// import multisig hex
|
||||||
List<String> updatedMultisigHexes = new ArrayList<String>();
|
List<String> updatedMultisigHexes = new ArrayList<String>();
|
||||||
|
@ -27,12 +27,16 @@ import bisq.core.trade.Trade;
|
|||||||
import bisq.core.trade.Trade.State;
|
import bisq.core.trade.Trade.State;
|
||||||
import bisq.core.trade.messages.SignContractRequest;
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
import bisq.network.p2p.SendDirectMessageListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
|
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
|
||||||
@ -73,9 +77,15 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
// create deposit tx and freeze inputs
|
// create deposit tx and freeze inputs
|
||||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
||||||
|
|
||||||
|
// collect reserved key images
|
||||||
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
|
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
|
||||||
// save process state
|
// save process state
|
||||||
processModel.setDepositTxXmr(depositTx); // TODO: trade.getSelf().setDepositTx()
|
processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove?
|
||||||
|
trade.getSelf().setDepositTx(depositTx);
|
||||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||||
|
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
||||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address?
|
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address?
|
||||||
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade));
|
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade));
|
||||||
|
|
||||||
|
@ -82,12 +82,12 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
|||||||
boolean updateParticipants = false;
|
boolean updateParticipants = false;
|
||||||
if (trade.getSelf().getPreparedMultisigHex() == null) {
|
if (trade.getSelf().getPreparedMultisigHex() == null) {
|
||||||
log.info("Preparing multisig wallet for trade {}", trade.getId());
|
log.info("Preparing multisig wallet for trade {}", trade.getId());
|
||||||
multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
|
multisigWallet = trade.createWallet();
|
||||||
trade.getSelf().setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
trade.getSelf().setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_PREPARED);
|
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_PREPARED);
|
||||||
updateParticipants = true;
|
updateParticipants = true;
|
||||||
} else if (processModel.getMultisigAddress() == null) {
|
} else if (processModel.getMultisigAddress() == null) {
|
||||||
multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
multisigWallet = trade.getWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// make multisig if applicable
|
// make multisig if applicable
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
|
||||||
import bisq.core.trade.HavenoUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
@ -27,7 +26,6 @@ import bisq.common.crypto.PubKeyRing;
|
|||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||||
@ -62,9 +60,7 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
|||||||
|
|
||||||
// export multisig hex once
|
// export multisig hex once
|
||||||
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(tradeId);
|
|
||||||
trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex());
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,9 +89,6 @@ import javax.inject.Named;
|
|||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import monero.daemon.model.MoneroTx;
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
public class PendingTradesDataModel extends ActivatableDataModel {
|
public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
@Getter
|
@Getter
|
||||||
public final TradeManager tradeManager;
|
public final TradeManager tradeManager;
|
||||||
@ -466,7 +463,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||||||
|
|
||||||
byte[] payoutTxSerialized = null;
|
byte[] payoutTxSerialized = null;
|
||||||
String payoutTxHashAsString = null;
|
String payoutTxHashAsString = null;
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
|
||||||
if (trade.getPayoutTxId() != null) {
|
if (trade.getPayoutTxId() != null) {
|
||||||
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
|
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
|
||||||
// payoutTxHashAsString = payoutTx.getHashAsString();
|
// payoutTxHashAsString = payoutTx.getHashAsString();
|
||||||
|
@ -1749,7 +1749,8 @@ message TradePeer {
|
|||||||
string deposit_tx_hash = 1008;
|
string deposit_tx_hash = 1008;
|
||||||
string deposit_tx_hex = 1009;
|
string deposit_tx_hex = 1009;
|
||||||
string deposit_tx_key = 1010;
|
string deposit_tx_key = 1010;
|
||||||
string updated_multisig_hex = 1011;
|
int64 security_deposit = 1011;
|
||||||
|
string updated_multisig_hex = 1012;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -33,7 +33,6 @@ import bisq.common.UserThread;
|
|||||||
import bisq.common.app.AppModule;
|
import bisq.common.app.AppModule;
|
||||||
import bisq.common.app.Capabilities;
|
import bisq.common.app.Capabilities;
|
||||||
import bisq.common.app.Capability;
|
import bisq.common.app.Capability;
|
||||||
import bisq.common.app.DevEnv;
|
|
||||||
import bisq.common.config.BaseCurrencyNetwork;
|
import bisq.common.config.BaseCurrencyNetwork;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
Loading…
Reference in New Issue
Block a user