mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-05-21 07:50:44 -04:00
support local, stagenet, and mainnet xmr network configuration (#335)
remove btc wallet disable local zmq
This commit is contained in:
parent
b4112e50e9
commit
e2208355b1
74 changed files with 595 additions and 899 deletions
|
@ -548,15 +548,11 @@ public class CoreApi {
|
|||
String paymentAccountId,
|
||||
Consumer<Trade> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
try {
|
||||
Offer offer = coreOffersService.getOffer(offerId);
|
||||
coreTradesService.takeOffer(offer,
|
||||
paymentAccountId,
|
||||
resultHandler,
|
||||
errorMessageHandler);
|
||||
} catch (Exception e) {
|
||||
errorMessageHandler.handleErrorMessage(e.getMessage());
|
||||
}
|
||||
Offer offer = coreOffersService.getOffer(offerId);
|
||||
coreTradesService.takeOffer(offer,
|
||||
paymentAccountId,
|
||||
resultHandler,
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
||||
public void confirmPaymentStarted(String tradeId) {
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
package bisq.core.api;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.support.SupportType;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
@ -45,7 +44,6 @@ import java.util.Optional;
|
|||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY;
|
||||
import static bisq.core.support.SupportType.ARBITRATION;
|
||||
import static bisq.core.support.SupportType.MEDIATION;
|
||||
import static bisq.core.support.SupportType.REFUND;
|
||||
|
@ -61,7 +59,7 @@ class CoreDisputeAgentsService {
|
|||
private final User user;
|
||||
private final Config config;
|
||||
private final KeyRing keyRing;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
private final MediatorManager mediatorManager;
|
||||
private final RefundAgentManager refundAgentManager;
|
||||
|
@ -73,7 +71,7 @@ class CoreDisputeAgentsService {
|
|||
public CoreDisputeAgentsService(User user,
|
||||
Config config,
|
||||
KeyRing keyRing,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
ArbitratorManager arbitratorManager,
|
||||
MediatorManager mediatorManager,
|
||||
RefundAgentManager refundAgentManager,
|
||||
|
@ -81,7 +79,7 @@ class CoreDisputeAgentsService {
|
|||
this.user = user;
|
||||
this.config = config;
|
||||
this.keyRing = keyRing;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
this.mediatorManager = mediatorManager;
|
||||
this.refundAgentManager = refundAgentManager;
|
||||
|
@ -98,9 +96,6 @@ class CoreDisputeAgentsService {
|
|||
|| !config.useLocalhostForP2P)
|
||||
throw new IllegalStateException("dispute agents must be registered in a Bisq UI");
|
||||
|
||||
if (!registrationKey.equals(DEV_PRIVILEGE_PRIV_KEY))
|
||||
throw new IllegalArgumentException("invalid registration key");
|
||||
|
||||
Optional<SupportType> supportType = getSupportType(disputeAgentType);
|
||||
if (supportType.isPresent()) {
|
||||
ECKey ecKey;
|
||||
|
@ -112,6 +107,7 @@ class CoreDisputeAgentsService {
|
|||
return;
|
||||
}
|
||||
ecKey = arbitratorManager.getRegistrationKey(registrationKey);
|
||||
if (ecKey == null) throw new IllegalStateException("invalid registration key");
|
||||
signature = arbitratorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
|
||||
registerArbitrator(nodeAddress, languageCodes, ecKey, signature);
|
||||
return;
|
||||
|
@ -121,6 +117,7 @@ class CoreDisputeAgentsService {
|
|||
return;
|
||||
}
|
||||
ecKey = mediatorManager.getRegistrationKey(registrationKey);
|
||||
if (ecKey == null) throw new IllegalStateException("invalid registration key");
|
||||
signature = mediatorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
|
||||
registerMediator(nodeAddress, languageCodes, ecKey, signature);
|
||||
return;
|
||||
|
@ -130,6 +127,7 @@ class CoreDisputeAgentsService {
|
|||
return;
|
||||
}
|
||||
ecKey = refundAgentManager.getRegistrationKey(registrationKey);
|
||||
if (ecKey == null) throw new IllegalStateException("invalid registration key");
|
||||
signature = refundAgentManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
|
||||
registerRefundAgent(nodeAddress, languageCodes, ecKey, signature);
|
||||
return;
|
||||
|
@ -145,11 +143,9 @@ class CoreDisputeAgentsService {
|
|||
List<String> languageCodes,
|
||||
ECKey ecKey,
|
||||
String signature) {
|
||||
AddressEntry arbitratorAddressEntry = btcWalletService.getArbitratorAddressEntry(); // TODO (woodser): switch to XMR; no reason for arbitrator to have BTC address / pub key
|
||||
Arbitrator arbitrator = new Arbitrator(
|
||||
p2PService.getAddress(),
|
||||
arbitratorAddressEntry.getPubKey(),
|
||||
arbitratorAddressEntry.getAddressString(),
|
||||
xmrWalletService.getWallet().getPrimaryAddress(), // TODO: how is this used?
|
||||
keyRing.getPubKeyRing(),
|
||||
new ArrayList<>(languageCodes),
|
||||
new Date().getTime(),
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package bisq.core.api;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.config.BaseCurrencyNetwork;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.core.btc.model.EncryptedConnectionList;
|
||||
import bisq.core.btc.setup.DownloadListener;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
|
@ -18,13 +22,15 @@ import javafx.beans.property.SimpleLongProperty;
|
|||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroConnectionManager;
|
||||
import monero.common.MoneroConnectionManagerListener;
|
||||
import monero.common.MoneroError;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.common.TaskLooper;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.daemon.model.MoneroDaemonInfo;
|
||||
import monero.daemon.model.MoneroPeer;
|
||||
|
||||
@Slf4j
|
||||
|
@ -32,16 +38,37 @@ import monero.daemon.model.MoneroPeer;
|
|||
public final class CoreMoneroConnectionsService {
|
||||
|
||||
private static final int MIN_BROADCAST_CONNECTIONS = 0; // TODO: 0 for stagenet, 5+ for mainnet
|
||||
private static final long DAEMON_REFRESH_PERIOD_MS = 15000L; // check connection periodically in ms
|
||||
private static final long DAEMON_INFO_POLL_PERIOD_MS = 20000L; // collect daemon info periodically in ms
|
||||
private static final long REFRESH_PERIOD_LOCAL_MS = 5000; // refresh period when connected to local node
|
||||
private static final long REFRESH_PERIOD_REMOTE_MS = 20000; // refresh period when connected to remote node
|
||||
|
||||
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
||||
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
||||
new MoneroRpcConnection("http://127.0.0.1:38081", "superuser", "abctesting123").setPriority(1), // localhost is first priority, use loopback address to match url generated by local node service
|
||||
new MoneroRpcConnection("http://haveno.exchange:38081", "", "").setPriority(2)
|
||||
);
|
||||
// default Monero nodes
|
||||
private static final Map<BaseCurrencyNetwork, List<MoneroRpcConnection>> DEFAULT_CONNECTIONS;
|
||||
static {
|
||||
DEFAULT_CONNECTIONS = new HashMap<BaseCurrencyNetwork, List<MoneroRpcConnection>>();
|
||||
DEFAULT_CONNECTIONS.put(BaseCurrencyNetwork.XMR_LOCAL, Arrays.asList(
|
||||
new MoneroRpcConnection("http://127.0.0.1:28081").setPriority(1)
|
||||
));
|
||||
DEFAULT_CONNECTIONS.put(BaseCurrencyNetwork.XMR_STAGENET, Arrays.asList(
|
||||
new MoneroRpcConnection("http://127.0.0.1:38081").setPriority(1), // localhost is first priority, use loopback address to match url generated by local node service
|
||||
new MoneroRpcConnection("http://stagenet.melo.tools:38081").setPriority(2),
|
||||
new MoneroRpcConnection("http://node.sethforprivacy.com:38089").setPriority(2),
|
||||
new MoneroRpcConnection("http://node2.sethforprivacy.com:38089").setPriority(2),
|
||||
new MoneroRpcConnection("http://stagenet.community.rino.io:38081").setPriority(2),
|
||||
new MoneroRpcConnection("http://ct36dsbe3oubpbebpxmiqz4uqk6zb6nhmkhoekileo4fts23rvuse2qd.onion:38081").setPriority(2)
|
||||
));
|
||||
DEFAULT_CONNECTIONS.put(BaseCurrencyNetwork.XMR_MAINNET, Arrays.asList(
|
||||
new MoneroRpcConnection("http://127.0.0.1:18081").setPriority(1),
|
||||
new MoneroRpcConnection("http://node.community.rino.io:18081").setPriority(2),
|
||||
new MoneroRpcConnection("http://xmr-node.cakewallet.com:18081").setPriority(2),
|
||||
new MoneroRpcConnection("http://xmr-node-eu.cakewallet.com:18081").setPriority(2),
|
||||
new MoneroRpcConnection("http://xmr-node-usa-east.cakewallet.com:18081").setPriority(2),
|
||||
new MoneroRpcConnection("http://xmr-node-uk.cakewallet.com:18081").setPriority(2),
|
||||
new MoneroRpcConnection("http://node.sethforprivacy.com:18089").setPriority(2)
|
||||
));
|
||||
}
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final CoreContext coreContext;
|
||||
private final CoreAccountService accountService;
|
||||
private final CoreMoneroNodeService nodeService;
|
||||
private final MoneroConnectionManager connectionManager;
|
||||
|
@ -51,15 +78,20 @@ public final class CoreMoneroConnectionsService {
|
|||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||
private final DownloadListener downloadListener = new DownloadListener();
|
||||
|
||||
private MoneroDaemon daemon;
|
||||
private MoneroDaemonRpc daemon;
|
||||
@Getter
|
||||
private MoneroDaemonInfo lastInfo;
|
||||
private boolean isInitialized = false;
|
||||
private TaskLooper updateDaemonLooper;;
|
||||
|
||||
@Inject
|
||||
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
|
||||
public CoreMoneroConnectionsService(CoreContext coreContext,
|
||||
WalletsSetup walletsSetup,
|
||||
CoreAccountService accountService,
|
||||
CoreMoneroNodeService nodeService,
|
||||
MoneroConnectionManager connectionManager,
|
||||
EncryptedConnectionList connectionList) {
|
||||
this.coreContext = coreContext;
|
||||
this.accountService = accountService;
|
||||
this.nodeService = nodeService;
|
||||
this.connectionManager = connectionManager;
|
||||
|
@ -96,7 +128,7 @@ public final class CoreMoneroConnectionsService {
|
|||
|
||||
// ------------------------ CONNECTION MANAGEMENT -------------------------
|
||||
|
||||
public MoneroDaemon getDaemon() {
|
||||
public MoneroDaemonRpc getDaemon() {
|
||||
accountService.checkAccountOpen();
|
||||
return this.daemon;
|
||||
}
|
||||
|
@ -171,7 +203,7 @@ public final class CoreMoneroConnectionsService {
|
|||
public void startCheckingConnection(Long refreshPeriod) {
|
||||
synchronized (lock) {
|
||||
accountService.checkAccountOpen();
|
||||
connectionManager.startCheckingConnection(refreshPeriod == null ? DAEMON_REFRESH_PERIOD_MS : refreshPeriod);
|
||||
connectionManager.startCheckingConnection(refreshPeriod == null ? getDefaultRefreshPeriodMs() : refreshPeriod);
|
||||
connectionList.setRefreshPeriod(refreshPeriod);
|
||||
}
|
||||
}
|
||||
|
@ -199,14 +231,28 @@ public final class CoreMoneroConnectionsService {
|
|||
}
|
||||
}
|
||||
|
||||
public long getDefaultRefreshPeriodMs() {
|
||||
if (daemon == null) return REFRESH_PERIOD_LOCAL_MS;
|
||||
else {
|
||||
boolean isLocal = CoreMoneroNodeService.isLocalHost(daemon.getRpcConnection().getUri());
|
||||
if (isLocal) {
|
||||
updateDaemonInfo();
|
||||
if (lastInfo.isBusySyncing() || (lastInfo.getHeightWithoutBootstrap() != null && lastInfo.getHeightWithoutBootstrap() > 0 && lastInfo.getHeightWithoutBootstrap() < lastInfo.getHeight())) return REFRESH_PERIOD_REMOTE_MS; // refresh slower if syncing or bootstrapped
|
||||
else return REFRESH_PERIOD_LOCAL_MS; // TODO: announce faster refresh after done syncing
|
||||
} else {
|
||||
return REFRESH_PERIOD_REMOTE_MS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------- APP METHODS ------------------------------
|
||||
|
||||
public boolean isChainHeightSyncedWithinTolerance() {
|
||||
if (daemon == null) return false;
|
||||
Long targetHeight = daemon.getSyncInfo().getTargetHeight();
|
||||
Long targetHeight = lastInfo.getTargetHeight(); // the last time the node thought it was behind the network and was in active sync mode to catch up
|
||||
if (targetHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
|
||||
long currentHeight = chainHeight.get();
|
||||
if (Math.abs(targetHeight - currentHeight) <= 3) {
|
||||
if (targetHeight - currentHeight <= 3) { // synced if not more than 3 blocks behind target height
|
||||
return true;
|
||||
}
|
||||
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), targetHeight);
|
||||
|
@ -263,7 +309,7 @@ public final class CoreMoneroConnectionsService {
|
|||
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
|
||||
|
||||
// add default connections
|
||||
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
|
||||
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS.get(Config.baseCurrencyNetwork())) {
|
||||
if (connectionList.hasConnection(connection.getUri())) continue;
|
||||
addConnection(connection);
|
||||
}
|
||||
|
@ -276,7 +322,7 @@ public final class CoreMoneroConnectionsService {
|
|||
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
|
||||
long refreshPeriod = connectionList.getRefreshPeriod();
|
||||
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
|
||||
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DAEMON_REFRESH_PERIOD_MS);
|
||||
else if (refreshPeriod == 0) connectionManager.startCheckingConnection();
|
||||
else checkConnection();
|
||||
|
||||
// run once
|
||||
|
@ -301,33 +347,37 @@ public final class CoreMoneroConnectionsService {
|
|||
}
|
||||
});
|
||||
|
||||
// start local node if last connection is local and offline
|
||||
currentConnectionUri.ifPresent(uri -> {
|
||||
try {
|
||||
if (CoreMoneroNodeService.isLocalHost(uri) && !nodeService.isMoneroNodeRunning()) {
|
||||
nodeService.startMoneroNode();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Unable to start local monero node: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
// poll daemon periodically
|
||||
startPollingDaemon();
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
// if offline, connect to local node if available
|
||||
if (!connectionManager.isConnected() && nodeService.isMoneroNodeRunning()) {
|
||||
// start local node if offline and last connection is local
|
||||
currentConnectionUri.ifPresent(uri -> {
|
||||
try {
|
||||
if (CoreMoneroNodeService.isLocalHost(uri) && !nodeService.isMoneroNodeRunning()) {
|
||||
nodeService.startMoneroNode();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Unable to start local monero node: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
// connect to local node if available
|
||||
if (nodeService.isMoneroNodeRunning() && (connectionManager.getAutoSwitch() || !connectionManager.isConnected())) {
|
||||
MoneroRpcConnection connection = connectionManager.getConnectionByUri(nodeService.getDaemon().getRpcConnection().getUri());
|
||||
if (connection == null) connection = nodeService.getDaemon().getRpcConnection();
|
||||
connection.checkConnection(connectionManager.getTimeout());
|
||||
setConnection(connection);
|
||||
if (connection != null) {
|
||||
connection.checkConnection(connectionManager.getTimeout());
|
||||
setConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
// set the daemon based on the connection
|
||||
if (getConnection() != null) daemon = new MoneroDaemonRpc(connectionManager.getConnection());
|
||||
updateDaemonInfo();
|
||||
// if using legacy desktop app, connect to best available connection
|
||||
if (!coreContext.isApiUser()) {
|
||||
connectionManager.setAutoSwitch(true);
|
||||
connectionManager.setConnection(connectionManager.getBestAvailableConnection());
|
||||
}
|
||||
|
||||
// update connection
|
||||
onConnectionChanged(connectionManager.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -342,23 +392,35 @@ public final class CoreMoneroConnectionsService {
|
|||
connectionList.addConnection(currentConnection);
|
||||
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
||||
}
|
||||
startPollingDaemon();
|
||||
}
|
||||
}
|
||||
|
||||
private void startPollingDaemon() {
|
||||
UserThread.runPeriodically(() -> {
|
||||
if (updateDaemonLooper != null) updateDaemonLooper.stop();
|
||||
updateDaemonLooper = new TaskLooper(() -> {
|
||||
updateDaemonInfo();
|
||||
}, DAEMON_INFO_POLL_PERIOD_MS / 1000l);
|
||||
});
|
||||
updateDaemonLooper.start(getDefaultRefreshPeriodMs());
|
||||
}
|
||||
|
||||
private void updateDaemonInfo() {
|
||||
try {
|
||||
log.trace("Updating daemon info");
|
||||
if (daemon == null) throw new RuntimeException("No daemon connection");
|
||||
peers.set(getOnlinePeers());
|
||||
lastInfo = daemon.getInfo();
|
||||
//System.out.println(JsonUtils.serialize(lastInfo));
|
||||
//System.out.println(JsonUtils.serialize(daemon.getSyncInfo()));
|
||||
chainHeight.set(lastInfo.getTargetHeight() == 0 ? lastInfo.getHeight() : lastInfo.getTargetHeight());
|
||||
try {
|
||||
peers.set(getOnlinePeers());
|
||||
} catch (MoneroError err) {
|
||||
peers.set(new ArrayList<MoneroPeer>()); // TODO: peers unknown due to restricted RPC call
|
||||
}
|
||||
numPeers.set(peers.get().size());
|
||||
chainHeight.set(daemon.getHeight());
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not update daemon info: " + e.getMessage());
|
||||
if (connectionManager.getAutoSwitch()) connectionManager.setConnection(connectionManager.getBestAvailableConnection());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,14 +18,13 @@ package bisq.core.api;
|
|||
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.xmr.MoneroNodeSettings;
|
||||
|
||||
import bisq.common.config.BaseCurrencyNetwork;
|
||||
import bisq.common.config.Config;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -35,7 +34,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import monero.common.MoneroError;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
|
||||
/**
|
||||
|
@ -49,7 +48,7 @@ public class CoreMoneroNodeService {
|
|||
private static final String LOCALHOST = "localhost";
|
||||
private static final String MONERO_NETWORK_TYPE = Config.baseCurrencyNetwork().getNetwork().toLowerCase();
|
||||
private static final String MONEROD_PATH = System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + "monerod";
|
||||
private static final String MONEROD_DATADIR = System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + MONERO_NETWORK_TYPE;
|
||||
private static final String MONEROD_DATADIR = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL ? System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + Config.baseCurrencyNetwork().toString().toLowerCase() + File.separator + "node1" : null;
|
||||
|
||||
private final Preferences preferences;
|
||||
private final List<MoneroNodeServiceListener> listeners = new ArrayList<>();
|
||||
|
@ -59,8 +58,7 @@ public class CoreMoneroNodeService {
|
|||
MONEROD_PATH,
|
||||
"--" + MONERO_NETWORK_TYPE,
|
||||
"--no-igd",
|
||||
"--hide-my-port",
|
||||
"--rpc-login", "superuser:abctesting123" // TODO: remove authentication
|
||||
"--hide-my-port"
|
||||
);
|
||||
|
||||
// client to the local Monero node
|
||||
|
@ -69,21 +67,24 @@ public class CoreMoneroNodeService {
|
|||
@Inject
|
||||
public CoreMoneroNodeService(Preferences preferences) {
|
||||
this.preferences = preferences;
|
||||
int rpcPort = 18081; // mainnet
|
||||
if (Config.baseCurrencyNetwork().isTestnet()) {
|
||||
rpcPort = 28081;
|
||||
} else if (Config.baseCurrencyNetwork().isStagenet()) {
|
||||
rpcPort = 38081;
|
||||
}
|
||||
this.daemon = new MoneroDaemonRpc("http://" + LOOPBACK_HOST + ":" + rpcPort, "superuser", "abctesting123"); // TODO: remove authentication
|
||||
Integer rpcPort = null;
|
||||
if (Config.baseCurrencyNetwork().isMainnet()) rpcPort = 18081;
|
||||
else if (Config.baseCurrencyNetwork().isTestnet()) rpcPort = 28081;
|
||||
else if (Config.baseCurrencyNetwork().isStagenet()) rpcPort = 38081;
|
||||
else throw new RuntimeException("Base network is not local testnet, stagenet, or mainnet");
|
||||
this.daemon = new MoneroDaemonRpc("http://" + LOOPBACK_HOST + ":" + rpcPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given URI is on local host. // TODO: move to utils
|
||||
*/
|
||||
public static boolean isLocalHost(String uri) throws URISyntaxException {
|
||||
String host = new URI(uri).getHost();
|
||||
return host.equals(CoreMoneroNodeService.LOOPBACK_HOST) || host.equals(CoreMoneroNodeService.LOCALHOST);
|
||||
public static boolean isLocalHost(String uri) {
|
||||
try {
|
||||
String host = new URI(uri).getHost();
|
||||
return host.equals(CoreMoneroNodeService.LOOPBACK_HOST) || host.equals(CoreMoneroNodeService.LOCALHOST);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(MoneroNodeServiceListener listener) {
|
||||
|
@ -105,7 +106,13 @@ public class CoreMoneroNodeService {
|
|||
* Returns whether a local monero node is running.
|
||||
*/
|
||||
public boolean isMoneroNodeRunning() {
|
||||
return daemon.isConnected();
|
||||
//return daemon.isConnected(); // TODO: daemonRpc.isConnected() should use getVersion() instead of getHeight() which throws when unsynced
|
||||
try {
|
||||
daemon.getVersion();
|
||||
return true;
|
||||
} catch (MoneroError e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroNodeSettings getMoneroNodeSettings() {
|
||||
|
@ -135,7 +142,7 @@ public class CoreMoneroNodeService {
|
|||
if (dataDir == null || dataDir.isEmpty()) {
|
||||
dataDir = MONEROD_DATADIR;
|
||||
}
|
||||
args.add("--data-dir=" + dataDir);
|
||||
if (dataDir != null) args.add("--data-dir=" + dataDir);
|
||||
|
||||
var bootstrapUrl = settings.getBootstrapUrl();
|
||||
if (bootstrapUrl != null && !bootstrapUrl.isEmpty()) {
|
||||
|
|
|
@ -500,7 +500,7 @@ class CoreWalletsService {
|
|||
|
||||
// Throws a RuntimeException if wallets are encrypted and locked.
|
||||
void verifyEncryptedWalletIsUnlocked() {
|
||||
if (walletsManager.areWalletsEncrypted() && tempAesKey == null)
|
||||
if (walletsManager.areWalletsEncrypted() && !accountService.isAccountOpen())
|
||||
throw new IllegalStateException("wallet is locked");
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ public class WalletAppSetup {
|
|||
return result;
|
||||
|
||||
});
|
||||
btcInfoBinding.subscribe((observable, oldValue, newValue) -> getBtcInfo().set(newValue));
|
||||
btcInfoBinding.subscribe((observable, oldValue, newValue) -> UserThread.execute(() -> btcInfo.set(newValue)));
|
||||
|
||||
walletsSetup.initialize(null,
|
||||
() -> {
|
||||
|
|
|
@ -224,7 +224,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
|
|||
log.warn("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
||||
"We are over our memory limit ({}) and trigger a shutdown. usedMemory: {} MB. freeMemory: {} MB" +
|
||||
"\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n",
|
||||
(int) maxMemory, usedMemory, Profiler.getFreeMemoryInMB());
|
||||
maxMemory, usedMemory, Profiler.getFreeMemoryInMB());
|
||||
shutDown(gracefulShutDownHandler);
|
||||
}
|
||||
}, 5);
|
||||
|
|
|
@ -18,44 +18,29 @@
|
|||
package bisq.core.btc.setup;
|
||||
|
||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.btc.nodes.ProxySocketFactory;
|
||||
import bisq.core.btc.wallet.HavenoRiskAnalysis;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.FileUtil;
|
||||
|
||||
import org.bitcoinj.core.BlockChain;
|
||||
import org.bitcoinj.core.CheckpointManager;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.PeerAddress;
|
||||
import org.bitcoinj.core.PeerGroup;
|
||||
import org.bitcoinj.core.listeners.DownloadProgressTracker;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
import org.bitcoinj.net.BlockingClientManager;
|
||||
import org.bitcoinj.net.discovery.DnsDiscovery;
|
||||
import org.bitcoinj.net.discovery.PeerDiscovery;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.bitcoinj.store.SPVBlockStore;
|
||||
import org.bitcoinj.wallet.DeterministicKeyChain;
|
||||
import org.bitcoinj.wallet.DeterministicSeed;
|
||||
import org.bitcoinj.wallet.KeyChainGroup;
|
||||
import org.bitcoinj.wallet.KeyChainGroupStructure;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.bitcoinj.wallet.WalletExtension;
|
||||
import org.bitcoinj.wallet.WalletProtobufSerializer;
|
||||
|
||||
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
||||
|
||||
import com.google.common.io.Closeables;
|
||||
import com.google.common.util.concurrent.AbstractIdleService;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -63,11 +48,8 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
@ -107,9 +89,6 @@ import static com.google.common.base.Preconditions.checkState;
|
|||
*/
|
||||
public class WalletConfig extends AbstractIdleService {
|
||||
|
||||
private static final int TOR_SOCKET_TIMEOUT = 120 * 1000; // 1 sec used in bitcoinj, but since bisq uses Tor we allow more.
|
||||
private static final int TOR_VERSION_EXCHANGE_TIMEOUT = 125 * 1000; // 5 sec used in bitcoinj, but since bisq uses Tor we allow more.
|
||||
|
||||
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
||||
|
||||
protected final NetworkParameters params;
|
||||
|
@ -264,233 +243,16 @@ public class WalletConfig extends AbstractIdleService {
|
|||
|
||||
@Override
|
||||
protected void startUp() throws Exception {
|
||||
// Runs in a separate thread.
|
||||
Context.propagate(context);
|
||||
try {
|
||||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||
boolean chainFileExists = chainFile.exists();
|
||||
|
||||
String btcPrefix = "_BTC";
|
||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||
vBtcWallet = createOrLoadWallet(shouldReplayWallet, vBtcWalletFile);
|
||||
vBtcWallet.allowSpendingUnconfirmedTransactions();
|
||||
vBtcWallet.setRiskAnalyzer(new HavenoRiskAnalysis.Analyzer());
|
||||
|
||||
// Initiate Bitcoin network objects (block store, blockchain and peer group)
|
||||
vStore = new SPVBlockStore(params, chainFile);
|
||||
if (!chainFileExists || restoreFromSeed != null) {
|
||||
if (checkpoints == null) {
|
||||
checkpoints = CheckpointManager.openStream(params);
|
||||
}
|
||||
|
||||
if (checkpoints != null) {
|
||||
// Initialize the chain file with a checkpoint to speed up first-run sync.
|
||||
long time;
|
||||
if (restoreFromSeed != null) {
|
||||
time = restoreFromSeed.getCreationTimeSeconds();
|
||||
if (chainFileExists) {
|
||||
log.info("Clearing the chain file in preparation for restore.");
|
||||
vStore.clear();
|
||||
}
|
||||
} else {
|
||||
time = vBtcWallet.getEarliestKeyCreationTime();
|
||||
}
|
||||
if (time > 0)
|
||||
CheckpointManager.checkpoint(params, checkpoints, vStore, time);
|
||||
else
|
||||
log.warn("Creating a new uncheckpointed block store due to a wallet with a creation time of zero: this will result in a very slow chain sync");
|
||||
} else if (chainFileExists) {
|
||||
log.info("Clearing the chain file in preparation for restore.");
|
||||
vStore.clear();
|
||||
}
|
||||
}
|
||||
vChain = new BlockChain(params, vStore);
|
||||
vPeerGroup = createPeerGroup();
|
||||
if (minBroadcastConnections > 0)
|
||||
vPeerGroup.setMinBroadcastConnections(minBroadcastConnections);
|
||||
if (this.userAgent != null)
|
||||
vPeerGroup.setUserAgent(userAgent, version);
|
||||
|
||||
// Set up peer addresses or discovery first, so if wallet extensions try to broadcast a transaction
|
||||
// before we're actually connected the broadcast waits for an appropriate number of connections.
|
||||
if (peerAddresses != null) {
|
||||
for (PeerAddress addr : peerAddresses) vPeerGroup.addAddress(addr);
|
||||
int maxConnections = Math.min(numConnectionsForBtc, peerAddresses.length);
|
||||
log.info("We try to connect to {} btc nodes", maxConnections);
|
||||
vPeerGroup.setMaxConnections(maxConnections);
|
||||
vPeerGroup.setAddPeersFromAddressMessage(false);
|
||||
peerAddresses = null;
|
||||
} else if (!params.getId().equals(NetworkParameters.ID_REGTEST)) {
|
||||
vPeerGroup.addPeerDiscovery(discovery != null ? discovery : new DnsDiscovery(params));
|
||||
}
|
||||
vChain.addWallet(vBtcWallet);
|
||||
vPeerGroup.addWallet(vBtcWallet);
|
||||
onSetupCompleted();
|
||||
|
||||
if (migratedWalletToSegwit.get()) {
|
||||
startPeerGroup();
|
||||
} else {
|
||||
migratedWalletToSegwit.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
startPeerGroup();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (BlockStoreException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void startPeerGroup() {
|
||||
Futures.addCallback((ListenableFuture<?>) vPeerGroup.startAsync(), new FutureCallback<Object>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Object result) {
|
||||
//completeExtensionInitiations(vPeerGroup);
|
||||
DownloadProgressTracker tracker = new DownloadProgressTracker();
|
||||
vPeerGroup.startBlockChainDownload(tracker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private Wallet createOrLoadWallet(boolean shouldReplayWallet,
|
||||
File walletFile) throws Exception {
|
||||
Wallet wallet;
|
||||
|
||||
maybeMoveOldWalletOutOfTheWay(walletFile);
|
||||
|
||||
if (walletFile.exists()) {
|
||||
wallet = loadWallet(shouldReplayWallet, walletFile);
|
||||
} else {
|
||||
wallet = createWallet();
|
||||
//wallet.freshReceiveKey();
|
||||
|
||||
// Currently the only way we can be sure that an extension is aware of its containing wallet is by
|
||||
// deserializing the extension (see WalletExtension#deserializeWalletExtension(Wallet, byte[]))
|
||||
// Hence, we first save and then load wallet to ensure any extensions are correctly initialized.
|
||||
wallet.saveToFile(walletFile);
|
||||
wallet = loadWallet(false, walletFile);
|
||||
}
|
||||
|
||||
this.setupAutoSave(wallet, walletFile);
|
||||
|
||||
return wallet;
|
||||
onSetupCompleted();
|
||||
}
|
||||
|
||||
protected void setupAutoSave(Wallet wallet, File walletFile) {
|
||||
wallet.autosaveToFile(walletFile, 5, TimeUnit.SECONDS, null);
|
||||
}
|
||||
|
||||
private Wallet loadWallet(boolean shouldReplayWallet, File walletFile) throws Exception {
|
||||
Wallet wallet;
|
||||
try (FileInputStream walletStream = new FileInputStream(walletFile)) {
|
||||
WalletExtension[] extArray = new WalletExtension[]{};
|
||||
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(walletStream);
|
||||
final WalletProtobufSerializer serializer;
|
||||
serializer = new WalletProtobufSerializer();
|
||||
// Hack to convert bitcoinj 0.14 wallets to bitcoinj 0.15 format
|
||||
serializer.setKeyChainFactory(new HavenoKeyChainFactory());
|
||||
wallet = serializer.readWallet(params, extArray, proto);
|
||||
if (shouldReplayWallet)
|
||||
wallet.reset();
|
||||
maybeAddSegwitKeychain(wallet, null);
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
|
||||
protected Wallet createWallet() {
|
||||
Script.ScriptType preferredOutputScriptType = Script.ScriptType.P2WPKH;
|
||||
KeyChainGroupStructure structure = new HavenoKeyChainGroupStructure();
|
||||
KeyChainGroup.Builder kcgBuilder = KeyChainGroup.builder(params, structure);
|
||||
if (restoreFromSeed != null) {
|
||||
kcgBuilder.fromSeed(restoreFromSeed, preferredOutputScriptType);
|
||||
} else {
|
||||
// new wallet
|
||||
// btc wallet uses a new random seed.
|
||||
kcgBuilder.fromRandom(preferredOutputScriptType);
|
||||
}
|
||||
return new Wallet(params, kcgBuilder.build());
|
||||
}
|
||||
|
||||
private void maybeMoveOldWalletOutOfTheWay(File walletFile) {
|
||||
if (restoreFromSeed == null) return;
|
||||
if (!walletFile.exists()) return;
|
||||
int counter = 1;
|
||||
File newName;
|
||||
do {
|
||||
newName = new File(walletFile.getParent(), "Backup " + counter + " for " + walletFile.getName());
|
||||
counter++;
|
||||
} while (newName.exists());
|
||||
log.info("Renaming old wallet file {} to {}", walletFile, newName);
|
||||
if (!walletFile.renameTo(newName)) {
|
||||
// This should not happen unless something is really messed up.
|
||||
throw new RuntimeException("Failed to rename wallet for restore");
|
||||
}
|
||||
}
|
||||
|
||||
private PeerGroup createPeerGroup() {
|
||||
PeerGroup peerGroup;
|
||||
// no proxy case.
|
||||
if (socks5Proxy == null) {
|
||||
peerGroup = new PeerGroup(params, vChain);
|
||||
} else {
|
||||
// proxy case (tor).
|
||||
Proxy proxy = new Proxy(Proxy.Type.SOCKS,
|
||||
new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort()));
|
||||
|
||||
ProxySocketFactory proxySocketFactory = new ProxySocketFactory(proxy);
|
||||
// We don't use tor mode if we have a local node running
|
||||
BlockingClientManager blockingClientManager = localBitcoinNode.shouldBeUsed() ?
|
||||
new BlockingClientManager() :
|
||||
new BlockingClientManager(proxySocketFactory);
|
||||
|
||||
peerGroup = new PeerGroup(params, vChain, blockingClientManager);
|
||||
|
||||
blockingClientManager.setConnectTimeoutMillis(TOR_SOCKET_TIMEOUT);
|
||||
peerGroup.setConnectTimeoutMillis(TOR_VERSION_EXCHANGE_TIMEOUT);
|
||||
}
|
||||
|
||||
if (!localBitcoinNode.shouldBeUsed())
|
||||
peerGroup.setUseLocalhostPeerWhenPossible(false);
|
||||
|
||||
return peerGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception {
|
||||
// Runs in a separate thread.
|
||||
try {
|
||||
Context.propagate(context);
|
||||
|
||||
vBtcWallet.saveToFile(vBtcWalletFile);
|
||||
vBtcWallet = null;
|
||||
log.info("BtcWallet saved to file");
|
||||
|
||||
vStore.close();
|
||||
vStore = null;
|
||||
log.info("SPV file closed");
|
||||
|
||||
vChain = null;
|
||||
|
||||
// vPeerGroup.stop has no timeout and can take very long (10 sec. in my test). So we call it at the end.
|
||||
// We might get likely interrupted by the parent call timeout.
|
||||
if (vPeerGroup.isRunning()) {
|
||||
vPeerGroup.stop();
|
||||
log.info("PeerGroup stopped");
|
||||
} else {
|
||||
log.info("PeerGroup not stopped because it was not running");
|
||||
}
|
||||
vPeerGroup = null;
|
||||
} catch (BlockStoreException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public NetworkParameters params() {
|
||||
|
|
|
@ -196,31 +196,11 @@ public class WalletsSetup {
|
|||
//We are here in the btcj thread Thread[ STARTING,5,main]
|
||||
super.onSetupCompleted();
|
||||
|
||||
final PeerGroup peerGroup = walletConfig.peerGroup();
|
||||
|
||||
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
|
||||
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
|
||||
peerGroup.setAddPeersFromAddressMessage(false);
|
||||
|
||||
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
|
||||
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
|
||||
if (message instanceof RejectMessage) {
|
||||
UserThread.execute(() -> {
|
||||
RejectMessage rejectMessage = (RejectMessage) message;
|
||||
String msg = rejectMessage.toString();
|
||||
log.warn(msg);
|
||||
exceptionHandler.handleException(new RejectedTxException(msg, rejectMessage));
|
||||
});
|
||||
}
|
||||
return message;
|
||||
});
|
||||
|
||||
// run external startup handlers
|
||||
setupTaskHandlers.forEach(Runnable::run);
|
||||
|
||||
// Map to user thread
|
||||
UserThread.execute(() -> {
|
||||
addressEntryList.onWalletReady(walletConfig.btcWallet());
|
||||
timeoutTimer.stop();
|
||||
setupCompletedHandlers.forEach(Runnable::run);
|
||||
});
|
||||
|
|
|
@ -45,7 +45,6 @@ import org.bitcoinj.core.TransactionOutput;
|
|||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.script.ScriptPattern;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
@ -97,12 +96,13 @@ public class BtcWalletService extends WalletService {
|
|||
|
||||
this.addressEntryList = addressEntryList;
|
||||
|
||||
// TODO: set and use chainHeightProperty in XmrWalletService
|
||||
walletsSetup.addSetupCompletedHandler(() -> {
|
||||
wallet = walletsSetup.getBtcWallet();
|
||||
addListenersToWallet();
|
||||
|
||||
walletsSetup.getChain().addNewBestBlockListener(block -> chainHeightProperty.set(block.getHeight()));
|
||||
chainHeightProperty.set(walletsSetup.getChain().getBestChainHeight());
|
||||
// wallet = walletsSetup.getBtcWallet();
|
||||
// addListenersToWallet();
|
||||
//
|
||||
// walletsSetup.getChain().addNewBestBlockListener(block -> chainHeightProperty.set(block.getHeight()));
|
||||
// chainHeightProperty.set(walletsSetup.getChain().getBestChainHeight());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -160,11 +160,13 @@ public abstract class WalletService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void addListenersToWallet() {
|
||||
wallet.addCoinsReceivedEventListener(walletEventListener);
|
||||
wallet.addCoinsSentEventListener(walletEventListener);
|
||||
wallet.addReorganizeEventListener(walletEventListener);
|
||||
wallet.addTransactionConfidenceEventListener(walletEventListener);
|
||||
wallet.addChangeEventListener(Threading.SAME_THREAD, cacheInvalidationListener);
|
||||
if (wallet != null) {
|
||||
wallet.addCoinsReceivedEventListener(walletEventListener);
|
||||
wallet.addCoinsSentEventListener(walletEventListener);
|
||||
wallet.addReorganizeEventListener(walletEventListener);
|
||||
wallet.addTransactionConfidenceEventListener(walletEventListener);
|
||||
wallet.addChangeEventListener(Threading.SAME_THREAD, cacheInvalidationListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
|
|
|
@ -44,6 +44,7 @@ public class WalletsManager {
|
|||
private static final Logger log = LoggerFactory.getLogger(WalletsManager.class);
|
||||
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
|
||||
|
@ -53,9 +54,11 @@ public class WalletsManager {
|
|||
|
||||
@Inject
|
||||
public WalletsManager(BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeWalletService tradeWalletService,
|
||||
WalletsSetup walletsSetup) {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
}
|
||||
|
@ -96,12 +99,11 @@ public class WalletsManager {
|
|||
}
|
||||
|
||||
public boolean areWalletsEncrypted() {
|
||||
return areWalletsAvailable() &&
|
||||
btcWalletService.isEncrypted();
|
||||
return xmrWalletService.isWalletEncrypted();
|
||||
}
|
||||
|
||||
public boolean areWalletsAvailable() {
|
||||
return btcWalletService.isWalletReady();
|
||||
return xmrWalletService.isWalletReady();
|
||||
}
|
||||
|
||||
public KeyCrypterScrypt getKeyCrypterScrypt() {
|
||||
|
|
|
@ -42,8 +42,9 @@ import java.util.stream.Stream;
|
|||
import javax.inject.Inject;
|
||||
import monero.common.MoneroError;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.MoneroRpcError;
|
||||
import monero.common.MoneroUtils;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.MoneroDaemonRpc;
|
||||
import monero.daemon.model.MoneroNetworkType;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
|
@ -69,7 +70,7 @@ public class XmrWalletService {
|
|||
|
||||
// Monero configuration
|
||||
// TODO: don't hard code configuration, inject into classes?
|
||||
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
|
||||
private static final MoneroNetworkType MONERO_NETWORK_TYPE = getMoneroNetworkType();
|
||||
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
|
||||
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
|
||||
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
|
||||
|
@ -77,7 +78,6 @@ public class XmrWalletService {
|
|||
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_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_";
|
||||
private static final long MONERO_WALLET_SYNC_PERIOD = 5000l;
|
||||
|
||||
private final CoreAccountService accountService;
|
||||
private final CoreMoneroConnectionsService connectionsService;
|
||||
|
@ -155,8 +155,16 @@ public class XmrWalletService {
|
|||
checkState(state == State.STARTING || state == State.RUNNING, "Cannot call until startup is complete");
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public boolean isWalletReady() {
|
||||
return getWallet() != null;
|
||||
}
|
||||
|
||||
public boolean isWalletEncrypted() {
|
||||
return accountService.getPassword() != null;
|
||||
}
|
||||
|
||||
public MoneroDaemon getDaemon() {
|
||||
public MoneroDaemonRpc getDaemon() {
|
||||
return connectionsService.getDaemon();
|
||||
}
|
||||
|
||||
|
@ -253,14 +261,14 @@ public class XmrWalletService {
|
|||
// get expected mining fee
|
||||
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(returnAddress, depositAmount));
|
||||
BigInteger miningFee = miningFeeTx.getFee();
|
||||
|
||||
// create reserve tx
|
||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
||||
|
||||
// freeze inputs
|
||||
|
@ -288,7 +296,7 @@ public class XmrWalletService {
|
|||
// create deposit tx
|
||||
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.addDestination(TradeUtils.FEE_ADDRESS, tradeFee)
|
||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
||||
.addDestination(multisigAddress, depositAmount));
|
||||
|
||||
// freeze deposit inputs
|
||||
|
@ -315,36 +323,31 @@ public class XmrWalletService {
|
|||
* @param miningFeePadding verifies depositAmount has additional funds to cover mining fee increase
|
||||
*/
|
||||
public void verifyTradeTx(String depositAddress, BigInteger depositAmount, BigInteger tradeFee, String txHash, String txHex, String txKey, List<String> keyImages, boolean miningFeePadding) {
|
||||
boolean submittedToPool = false;
|
||||
MoneroDaemon daemon = getDaemon();
|
||||
MoneroDaemonRpc daemon = getDaemon();
|
||||
MoneroWallet wallet = getWallet();
|
||||
try {
|
||||
|
||||
// get tx from daemon
|
||||
|
||||
// verify tx not submitted to pool
|
||||
MoneroTx tx = daemon.getTx(txHash);
|
||||
|
||||
// if tx is not submitted, submit but do not relay
|
||||
if (tx == null) {
|
||||
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));
|
||||
submittedToPool = true;
|
||||
tx = daemon.getTx(txHash);
|
||||
} else if (tx.isRelayed()) {
|
||||
throw new RuntimeException("Trade tx must not be relayed");
|
||||
}
|
||||
|
||||
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 = daemon.getTx(txHash);
|
||||
|
||||
// verify reserved 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("Reserve tx's inputs do not match claimed key images");
|
||||
}
|
||||
|
||||
|
||||
// verify the unlock height
|
||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||
|
||||
// verify trade fee
|
||||
String feeAddress = TradeUtils.FEE_ADDRESS;
|
||||
String feeAddress = TradeUtils.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());
|
||||
|
@ -364,9 +367,12 @@ public class XmrWalletService {
|
|||
if (miningFeePadding) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee)
|
||||
if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount());
|
||||
} finally {
|
||||
|
||||
// flush tx from pool if we added it
|
||||
if (submittedToPool) daemon.flushTxPool(txHash);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,11 +399,13 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void tryInitMainWallet() {
|
||||
|
||||
// open or create wallet
|
||||
MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword());
|
||||
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) {
|
||||
wallet = openWallet(walletConfig, rpcBindPort);
|
||||
} else if (connectionsService.getConnection() != null && Boolean.TRUE.equals(connectionsService.getConnection().isConnected())) {
|
||||
wallet = createWallet(walletConfig, rpcBindPort); // wallet requires connection to daemon to correctly set height
|
||||
wallet = createWallet(walletConfig, rpcBindPort);
|
||||
}
|
||||
|
||||
// wallet is not initialized until connected to a daemon
|
||||
|
@ -410,9 +418,15 @@ public class XmrWalletService {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println("Monero wallet path: " + wallet.getPath());
|
||||
System.out.println("Monero wallet address: " + wallet.getPrimaryAddress());
|
||||
if (connectionsService.getDaemon() == null) System.out.println("Daemon: null");
|
||||
else {
|
||||
System.out.println("Daemon uri: " + connectionsService.getDaemon().getRpcConnection().getUri());
|
||||
System.out.println("Daemon height: " + connectionsService.getDaemon().getInfo().getHeight());
|
||||
}
|
||||
System.out.println("Monero wallet uri: " + wallet.getRpcConnection().getUri());
|
||||
System.out.println("Monero wallet path: " + wallet.getPath());
|
||||
System.out.println("Monero wallet seed: " + wallet.getMnemonic());
|
||||
System.out.println("Monero wallet primary address: " + wallet.getPrimaryAddress());
|
||||
System.out.println("Monero wallet height: " + wallet.getHeight());
|
||||
System.out.println("Monero wallet balance: " + wallet.getBalance(0));
|
||||
System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0));
|
||||
|
@ -433,8 +447,10 @@ public class XmrWalletService {
|
|||
|
||||
// create wallet
|
||||
try {
|
||||
log.info("Creating wallet " + config.getPath());
|
||||
walletRpc.createWallet(config);
|
||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_PERIOD);
|
||||
log.info("Syncing wallet " + config.getPath());
|
||||
walletRpc.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
|
||||
return walletRpc;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -450,8 +466,10 @@ public class XmrWalletService {
|
|||
|
||||
// open wallet
|
||||
try {
|
||||
log.info("Opening wallet " + config.getPath());
|
||||
walletRpc.openWallet(config);
|
||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_PERIOD);
|
||||
log.info("Syncing wallet " + config.getPath());
|
||||
walletRpc.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
|
||||
return walletRpc;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -489,10 +507,16 @@ public class XmrWalletService {
|
|||
}
|
||||
|
||||
private void setWalletDaemonConnections(MoneroRpcConnection connection) {
|
||||
log.info("Setting wallet daemon connections: " + (connection == null ? null : connection.getUri()));
|
||||
log.info("Setting wallet daemon connection: " + (connection == null ? null : connection.getUri()));
|
||||
if (wallet == null) tryInitMainWallet();
|
||||
if (wallet != null) wallet.setDaemonConnection(connection);
|
||||
for (MoneroWallet multisigWallet : multisigWallets.values()) multisigWallet.setDaemonConnection(connection);
|
||||
if (wallet != null) {
|
||||
wallet.setDaemonConnection(connection);
|
||||
wallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
|
||||
}
|
||||
for (MoneroWallet multisigWallet : multisigWallets.values()) {
|
||||
multisigWallet.setDaemonConnection(connection);
|
||||
multisigWallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs()); // TODO: optimize when multisig wallets are open and syncing
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyBalanceListeners() {
|
||||
|
@ -803,6 +827,19 @@ public class XmrWalletService {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
public static MoneroNetworkType getMoneroNetworkType() {
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
return MoneroNetworkType.TESTNET;
|
||||
case XMR_STAGENET:
|
||||
return MoneroNetworkType.STAGENET;
|
||||
case XMR_MAINNET:
|
||||
return MoneroNetworkType.MAINNET;
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
}
|
||||
|
||||
public static void printTxs(String tracePrefix, MoneroTxWallet... txs) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString());
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package bisq.core.offer;
|
||||
|
||||
import bisq.core.btc.TxFeeEstimationService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.Restrictions;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Price;
|
||||
|
@ -69,7 +69,7 @@ public class CreateOfferService {
|
|||
private final P2PService p2PService;
|
||||
private final PubKeyRingProvider pubKeyRingProvider;
|
||||
private final User user;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
|
||||
|
@ -85,7 +85,7 @@ public class CreateOfferService {
|
|||
P2PService p2PService,
|
||||
PubKeyRingProvider pubKeyRingProvider,
|
||||
User user,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
ArbitratorManager arbitratorManager) {
|
||||
this.offerUtil = offerUtil;
|
||||
|
@ -94,7 +94,7 @@ public class CreateOfferService {
|
|||
this.p2PService = p2PService;
|
||||
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||
this.user = user;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.arbitratorManager = arbitratorManager;
|
||||
}
|
||||
|
@ -167,8 +167,6 @@ public class CreateOfferService {
|
|||
String bankId = PaymentAccountUtil.getBankId(paymentAccount);
|
||||
List<String> acceptedBanks = PaymentAccountUtil.getAcceptedBanks(paymentAccount);
|
||||
double sellerSecurityDeposit = getSellerSecurityDepositAsDouble(buyerSecurityDepositAsDouble);
|
||||
Coin txFeeFromFeeService = getEstimatedFeeAndTxVsize(amount, direction, buyerSecurityDepositAsDouble, sellerSecurityDeposit).first;
|
||||
Coin txFeeToUse = txFee.isPositive() ? txFee : txFeeFromFeeService;
|
||||
Coin makerFeeAsCoin = offerUtil.getMakerFee(amount);
|
||||
Coin buyerSecurityDepositAsCoin = getBuyerSecurityDeposit(amount, buyerSecurityDepositAsDouble);
|
||||
Coin sellerSecurityDepositAsCoin = getSellerSecurityDeposit(amount, sellerSecurityDeposit);
|
||||
|
@ -195,6 +193,7 @@ public class CreateOfferService {
|
|||
|
||||
// select signing arbitrator
|
||||
Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager);
|
||||
if (arbitrator == null) throw new RuntimeException("No arbitrators available");
|
||||
|
||||
OfferPayload offerPayload = new OfferPayload(offerId,
|
||||
creationTime,
|
||||
|
@ -216,8 +215,8 @@ public class CreateOfferService {
|
|||
bankId,
|
||||
acceptedBanks,
|
||||
Version.VERSION,
|
||||
btcWalletService.getLastBlockSeenHeight(), // TODO (woodser): switch to XMR
|
||||
txFeeToUse.value,
|
||||
xmrWalletService.getWallet().getHeight(),
|
||||
0, // todo: remove txFeeToUse from data model
|
||||
makerFeeAsCoin.value,
|
||||
buyerSecurityDepositAsCoin.value,
|
||||
sellerSecurityDepositAsCoin.value,
|
||||
|
|
|
@ -34,7 +34,7 @@ public class OfferRestrictions {
|
|||
private static final Date REQUIRE_TOR_NODE_ADDRESS_V3_DATE = Utilities.getUTCDate(2021, GregorianCalendar.AUGUST, 15);
|
||||
|
||||
public static boolean requiresNodeAddressUpdate() {
|
||||
return new Date().after(REQUIRE_TOR_NODE_ADDRESS_V3_DATE) && !Config.baseCurrencyNetwork().isStagenet();
|
||||
return new Date().after(REQUIRE_TOR_NODE_ADDRESS_V3_DATE) && Config.baseCurrencyNetwork().isMainnet();
|
||||
}
|
||||
|
||||
public static Coin TOLERATED_SMALL_TRADE_AMOUNT = Coin.parseCoin("1.0");
|
||||
|
|
|
@ -33,7 +33,6 @@ import bisq.core.provider.price.PriceFeedService;
|
|||
import bisq.core.trade.statistics.ReferralIdService;
|
||||
import bisq.core.user.AutoConfirmSettings;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.AveragePriceUtil;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
import bisq.core.util.coin.CoinUtil;
|
||||
|
||||
|
@ -68,7 +67,6 @@ import static bisq.core.btc.wallet.Restrictions.getMinBuyerSecurityDepositAsPerc
|
|||
import static bisq.core.offer.OfferPayload.*;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* This class holds utility methods for creating, editing and taking an Offer.
|
||||
|
|
|
@ -24,7 +24,6 @@ import bisq.core.btc.wallet.TradeWalletService;
|
|||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
||||
import bisq.core.offer.messages.OfferAvailabilityRequest;
|
||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||
|
@ -72,7 +71,6 @@ import bisq.common.persistence.PersistenceManager;
|
|||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -1004,12 +1002,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
availabilityResult = AvailabilityResult.OFFER_TAKEN;
|
||||
}
|
||||
|
||||
if (btcWalletService.isUnconfirmedTransactionsLimitHit()) {
|
||||
errorMessage = Res.get("shared.unconfirmedTransactionsLimitReached");
|
||||
log.warn(errorMessage);
|
||||
availabilityResult = AvailabilityResult.UNCONF_TX_LIMIT_HIT;
|
||||
}
|
||||
|
||||
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
|
||||
availabilityResult,
|
||||
makerSignature,
|
||||
|
|
|
@ -46,7 +46,7 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||
checkCoinNotNullOrZero(offer.getMakerFee(), "MakerFee");
|
||||
checkCoinNotNullOrZero(offer.getBuyerSecurityDeposit(), "buyerSecurityDeposit");
|
||||
checkCoinNotNullOrZero(offer.getSellerSecurityDeposit(), "sellerSecurityDeposit");
|
||||
checkCoinNotNullOrZero(offer.getTxFee(), "txFee");
|
||||
//checkCoinNotNullOrZero(offer.getTxFee(), "txFee"); // TODO: remove from data model
|
||||
checkCoinNotNullOrZero(offer.getMaxTradeLimit(), "MaxTradeLimit");
|
||||
|
||||
// We remove those checks to be more flexible with future changes.
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package bisq.core.offer.takeoffer;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.model.XmrAddressEntry;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
|
@ -45,7 +45,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.core.btc.model.AddressEntry.Context.OFFER_FUNDING;
|
||||
import static bisq.core.btc.model.XmrAddressEntry.Context.OFFER_FUNDING;
|
||||
import static bisq.core.offer.OfferDirection.SELL;
|
||||
import static bisq.core.util.VolumeUtil.getAdjustedVolumeForHalCash;
|
||||
import static bisq.core.util.VolumeUtil.getRoundedFiatVolume;
|
||||
|
@ -59,14 +59,14 @@ import static org.bitcoinj.core.Coin.valueOf;
|
|||
public class TakeOfferModel implements Model {
|
||||
// Immutable
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final FeeService feeService;
|
||||
private final OfferUtil offerUtil;
|
||||
private final PriceFeedService priceFeedService;
|
||||
|
||||
// Mutable
|
||||
@Getter
|
||||
private AddressEntry addressEntry;
|
||||
private XmrAddressEntry addressEntry;
|
||||
@Getter
|
||||
private Coin amount;
|
||||
private Offer offer;
|
||||
|
@ -91,18 +91,18 @@ public class TakeOfferModel implements Model {
|
|||
@Getter
|
||||
private Coin balance;
|
||||
@Getter
|
||||
private boolean isBtcWalletFunded;
|
||||
private boolean isXmrWalletFunded;
|
||||
@Getter
|
||||
private Volume volume;
|
||||
|
||||
@Inject
|
||||
public TakeOfferModel(AccountAgeWitnessService accountAgeWitnessService,
|
||||
BtcWalletService btcWalletService,
|
||||
XmrWalletService xmrWalletService,
|
||||
FeeService feeService,
|
||||
OfferUtil offerUtil,
|
||||
PriceFeedService priceFeedService) {
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.feeService = feeService;
|
||||
this.offerUtil = offerUtil;
|
||||
this.priceFeedService = priceFeedService;
|
||||
|
@ -114,7 +114,7 @@ public class TakeOfferModel implements Model {
|
|||
this.clearModel();
|
||||
this.offer = offer;
|
||||
this.paymentAccount = paymentAccount;
|
||||
this.addressEntry = btcWalletService.getOrCreateAddressEntry(offer.getId(), OFFER_FUNDING); // TODO (woodser): replace with xmr or remove
|
||||
this.addressEntry = xmrWalletService.getOrCreateAddressEntry(offer.getId(), OFFER_FUNDING); // TODO (woodser): replace with xmr or remove
|
||||
validateModelInputs();
|
||||
|
||||
this.useSavingsWallet = useSavingsWallet;
|
||||
|
@ -200,18 +200,10 @@ public class TakeOfferModel implements Model {
|
|||
}
|
||||
|
||||
private void updateBalance() {
|
||||
Coin tradeWalletBalance = btcWalletService.getBalanceForAddress(addressEntry.getAddress());
|
||||
if (useSavingsWallet) {
|
||||
Coin savingWalletBalance = btcWalletService.getSavingWalletBalance();
|
||||
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
|
||||
if (totalToPayAsCoin != null)
|
||||
balance = minCoin(totalToPayAsCoin, totalAvailableBalance);
|
||||
|
||||
} else {
|
||||
balance = tradeWalletBalance;
|
||||
}
|
||||
totalAvailableBalance = xmrWalletService.getSavingWalletBalance();
|
||||
if (totalToPayAsCoin != null) balance = minCoin(totalToPayAsCoin, totalAvailableBalance);
|
||||
missingCoin = offerUtil.getBalanceShortage(totalToPayAsCoin, balance);
|
||||
isBtcWalletFunded = offerUtil.isBalanceSufficient(totalToPayAsCoin, balance);
|
||||
isXmrWalletFunded = offerUtil.isBalanceSufficient(totalToPayAsCoin, balance);
|
||||
}
|
||||
|
||||
private long getMaxTradeLimit() {
|
||||
|
@ -264,7 +256,7 @@ public class TakeOfferModel implements Model {
|
|||
this.addressEntry = null;
|
||||
this.amount = null;
|
||||
this.balance = null;
|
||||
this.isBtcWalletFunded = false;
|
||||
this.isXmrWalletFunded = false;
|
||||
this.missingCoin = ZERO;
|
||||
this.offer = null;
|
||||
this.paymentAccount = null;
|
||||
|
@ -299,7 +291,7 @@ public class TakeOfferModel implements Model {
|
|||
", balance=" + balance + "\n" +
|
||||
", volume=" + volume + "\n" +
|
||||
", fundsNeededForTrade=" + getFundsNeededForTrade() + "\n" +
|
||||
", isBtcWalletFunded=" + isBtcWalletFunded + "\n" +
|
||||
", isXmrWalletFunded=" + isXmrWalletFunded + "\n" +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -568,10 +568,10 @@ public abstract class PaymentAccount implements PersistablePayload {
|
|||
throw new IllegalArgumentException("Not implemented");
|
||||
case CITY:
|
||||
field.setComponent(PaymentAccountFormField.Component.TEXT);
|
||||
field.setLabel(Res.get("Contact"));
|
||||
field.setLabel("City");
|
||||
case CONTACT:
|
||||
field.setComponent(PaymentAccountFormField.Component.TEXT);
|
||||
field.setLabel("City");
|
||||
field.setLabel("Contact info");
|
||||
case COUNTRY:
|
||||
field.setComponent(PaymentAccountFormField.Component.SELECT_ONE);
|
||||
field.setLabel("Country");
|
||||
|
|
|
@ -86,7 +86,6 @@ public final class StrikeAccount extends CountryBasedPaymentAccount {
|
|||
@Override
|
||||
@Nullable
|
||||
public @NotNull List<Country> getSupportedCountries() {
|
||||
System.out.println("STIKE RETURNING SUPPORTED COUNTRIES: " + SUPPORTED_COUNTRIES);
|
||||
return SUPPORTED_COUNTRIES;
|
||||
}
|
||||
|
||||
|
|
|
@ -416,11 +416,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
boolean senderIsCosigner = senderIsWinner || disputeResult.isLoserPublisher();
|
||||
boolean receiverIsArbitrator = pubKeyRing.equals(dispute.getAgentPubKeyRing());
|
||||
|
||||
System.out.println("TESTING PUB KEY RINGS");
|
||||
System.out.println(pubKeyRing);
|
||||
System.out.println(dispute.getAgentPubKeyRing());
|
||||
System.out.println("Receiver is arbitrator: " + receiverIsArbitrator);
|
||||
|
||||
if (!senderIsCosigner) {
|
||||
log.warn("Received ArbitratorPayoutTxRequest but sender is not co-signer for trade id " + tradeId);
|
||||
return;
|
||||
|
|
|
@ -24,7 +24,6 @@ import bisq.network.p2p.NodeAddress;
|
|||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.util.CollectionUtils;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
|
@ -43,12 +42,10 @@ import javax.annotation.Nullable;
|
|||
@Slf4j
|
||||
@Getter
|
||||
public final class Arbitrator extends DisputeAgent {
|
||||
private final byte[] btcPubKey;
|
||||
private final String btcAddress;
|
||||
private final String xmrAddress;
|
||||
|
||||
public Arbitrator(NodeAddress nodeAddress,
|
||||
byte[] btcPubKey,
|
||||
String btcAddress,
|
||||
String xmrAddress,
|
||||
PubKeyRing pubKeyRing,
|
||||
List<String> languageCodes,
|
||||
long registrationDate,
|
||||
|
@ -68,8 +65,7 @@ public final class Arbitrator extends DisputeAgent {
|
|||
info,
|
||||
extraDataMap);
|
||||
|
||||
this.btcPubKey = btcPubKey;
|
||||
this.btcAddress = btcAddress;
|
||||
this.xmrAddress = xmrAddress;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -80,8 +76,7 @@ public final class Arbitrator extends DisputeAgent {
|
|||
public protobuf.StoragePayload toProtoMessage() {
|
||||
protobuf.Arbitrator.Builder builder = protobuf.Arbitrator.newBuilder()
|
||||
.setNodeAddress(nodeAddress.toProtoMessage())
|
||||
.setBtcPubKey(ByteString.copyFrom(btcPubKey))
|
||||
.setBtcAddress(btcAddress)
|
||||
.setXmrAddress(xmrAddress)
|
||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||
.addAllLanguageCodes(languageCodes)
|
||||
.setRegistrationDate(registrationDate)
|
||||
|
@ -95,8 +90,7 @@ public final class Arbitrator extends DisputeAgent {
|
|||
|
||||
public static Arbitrator fromProto(protobuf.Arbitrator proto) {
|
||||
return new Arbitrator(NodeAddress.fromProto(proto.getNodeAddress()),
|
||||
proto.getBtcPubKey().toByteArray(),
|
||||
proto.getBtcAddress(),
|
||||
proto.getXmrAddress(),
|
||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||
new ArrayList<>(proto.getLanguageCodesList()),
|
||||
proto.getRegistrationDate(),
|
||||
|
@ -115,8 +109,7 @@ public final class Arbitrator extends DisputeAgent {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "Arbitrator{" +
|
||||
"\n btcPubKey=" + Utilities.bytesAsHexString(btcPubKey) +
|
||||
",\n btcAddress='" + btcAddress + '\'' +
|
||||
",\n xmrAddress='" + xmrAddress + '\'' +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,13 @@ import bisq.core.support.dispute.agent.DisputeAgentManager;
|
|||
import bisq.core.user.User;
|
||||
|
||||
import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.inject.Named;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -49,21 +48,26 @@ public class ArbitratorManager extends DisputeAgentManager<Arbitrator> {
|
|||
|
||||
@Override
|
||||
protected List<String> getPubKeyList() {
|
||||
return List.of("0365c6af94681dbee69de1851f98d4684063bf5c2d64b1c73ed5d90434f375a054",
|
||||
"031c502a60f9dbdb5ae5e438a79819e4e1f417211dd537ac12c9bc23246534c4bd",
|
||||
"02c1e5a242387b6d5319ce27246cea6edaaf51c3550591b528d2578a4753c56c2c",
|
||||
"025c319faf7067d9299590dd6c97fe7e56cd4dac61205ccee1cd1fc390142390a2",
|
||||
"038f6e24c2bfe5d51d0a290f20a9a657c270b94ef2b9c12cd15ca3725fa798fc55",
|
||||
"0255256ff7fb615278c4544a9bbd3f5298b903b8a011cd7889be19b6b1c45cbefe",
|
||||
"024a3a37289f08c910fbd925ebc72b946f33feaeff451a4738ee82037b4cda2e95",
|
||||
"02a88b75e9f0f8afba1467ab26799dcc38fd7a6468fb2795444b425eb43e2c10bd",
|
||||
"02349a51512c1c04c67118386f4d27d768c5195a83247c150a4b722d161722ba81",
|
||||
"03f718a2e0dc672c7cdec0113e72c3322efc70412bb95870750d25c32cd98de17d",
|
||||
"028ff47ee2c56e66313928975c58fa4f1b19a0f81f3a96c4e9c9c3c6768075509e",
|
||||
"02b517c0cbc3a49548f448ddf004ed695c5a1c52ec110be1bfd65fa0ca0761c94b",
|
||||
"03df837a3a0f3d858e82f3356b71d1285327f101f7c10b404abed2abc1c94e7169",
|
||||
"0203a90fb2ab698e524a5286f317a183a84327b8f8c3f7fa4a98fec9e1cefd6b72",
|
||||
"023c99cc073b851c892d8c43329ca3beb5d2213ee87111af49884e3ce66cbd5ba5");
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
throw new RuntimeException("No arbitrator pub key list for local XMR testnet. Set useDevPrivilegeKeys=true");
|
||||
case XMR_STAGENET:
|
||||
return List.of(
|
||||
"03bb559ce207a4deb51d4c705076c95b85ad8581d35936b2a422dcb504eaf7cdb0",
|
||||
"026c581ad773d987e6bd10785ac7f7e0e64864aedeb8bce5af37046de812a37854",
|
||||
"025b058c9f2c60d839669dbfa5578cf5a8117d60e6b70e2f0946f8a691273c6a36",
|
||||
"036c7d3f4bf05ef39b9d1b0a5d453a18210de36220c3d83cd16e59bd6132b037ad",
|
||||
"030f7122a10ff73cd73808bddace95be77a94189c8a0eb24586265e125ce5ce6b9",
|
||||
"03aa23e062afa0dda465f46986f8aa8d0374ad3e3f256141b05681dcb1e39c3859",
|
||||
"02d3beb1293ca2ca14e6d42ca8bd18089a62aac62fd6bb23923ee6ead46ac60fba",
|
||||
"03fa0f38f27bdd324db6f933f7e57851dadf3b911e4db6b19dd0950492c4525a31",
|
||||
"02a1a458df5acf4ab08fdca748e28f33a955a30854c8c1a831ee733dca7f0d2fcd",
|
||||
"0374dd70f3fa6e47ec5ab97932e1cec6233e98e6ae3129036b17118650c44fd3de");
|
||||
case XMR_MAINNET:
|
||||
return new ArrayList<String>();
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -362,9 +362,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
@Setter
|
||||
private NodeAddress arbitratorNodeAddress;
|
||||
@Nullable
|
||||
@Setter
|
||||
private byte[] arbitratorBtcPubKey;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
private PubKeyRing arbitratorPubKeyRing;
|
||||
|
@ -603,7 +600,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(contractHash)));
|
||||
Optional.ofNullable(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(refundAgentNodeAddress).ifPresent(e -> builder.setRefundAgentNodeAddress(refundAgentNodeAddress.toProtoMessage()));
|
||||
Optional.ofNullable(arbitratorBtcPubKey).ifPresent(e -> builder.setArbitratorBtcPubKey(ByteString.copyFrom(arbitratorBtcPubKey)));
|
||||
Optional.ofNullable(takerPaymentAccountId).ifPresent(builder::setTakerPaymentAccountId);
|
||||
Optional.ofNullable(errorMessage).ifPresent(builder::setErrorMessage);
|
||||
Optional.ofNullable(arbitratorPubKeyRing).ifPresent(e -> builder.setArbitratorPubKeyRing(arbitratorPubKeyRing.toProtoMessage()));
|
||||
|
@ -633,7 +629,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
trade.setContractHash(ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()));
|
||||
trade.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
|
||||
trade.setRefundAgentNodeAddress(proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null);
|
||||
trade.setArbitratorBtcPubKey(ProtoUtil.byteArrayOrNullFromProto(proto.getArbitratorBtcPubKey()));
|
||||
trade.setTakerPaymentAccountId(ProtoUtil.stringOrNullFromProto(proto.getTakerPaymentAccountId()));
|
||||
trade.setErrorMessage(ProtoUtil.stringOrNullFromProto(proto.getErrorMessage()));
|
||||
trade.setArbitratorPubKeyRing(proto.hasArbitratorPubKeyRing() ? PubKeyRing.fromProto(proto.getArbitratorPubKeyRing()) : null);
|
||||
|
@ -670,7 +665,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
|
||||
public void initialize(ProcessModelServiceProvider serviceProvider) {
|
||||
serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> {
|
||||
arbitratorBtcPubKey = arbitrator.getBtcPubKey();
|
||||
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
|
||||
});
|
||||
|
||||
|
@ -1439,19 +1433,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
getDelayedPayoutTxBytes() == null;
|
||||
}
|
||||
|
||||
public byte[] getArbitratorBtcPubKey() {
|
||||
// In case we are already in a trade the arbitrator can have been revoked and we still can complete the trade
|
||||
// Only new trades cannot start without any arbitrator
|
||||
if (arbitratorBtcPubKey == null) {
|
||||
Arbitrator arbitrator = processModel.getUser().getAcceptedArbitratorByAddress(arbitratorNodeAddress);
|
||||
checkNotNull(arbitrator, "arbitrator must not be null");
|
||||
arbitratorBtcPubKey = arbitrator.getBtcPubKey();
|
||||
}
|
||||
|
||||
checkNotNull(arbitratorBtcPubKey, "ArbitratorPubKey must not be null");
|
||||
return arbitratorBtcPubKey;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -1526,7 +1507,6 @@ public abstract class Trade implements Tradable, Model {
|
|||
",\n contract=" + contract +
|
||||
",\n contractAsJson='" + contractAsJson + '\'' +
|
||||
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||
",\n arbitratorBtcPubKey=" + Utilities.bytesAsHexString(arbitratorBtcPubKey) +
|
||||
",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' +
|
||||
",\n errorMessage='" + errorMessage + '\'' +
|
||||
",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' +
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
import bisq.core.btc.wallet.XmrWalletService;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
|
@ -32,15 +32,28 @@ import java.util.concurrent.CountDownLatch;
|
|||
|
||||
/**
|
||||
* Collection of utilities for trading.
|
||||
*
|
||||
* TODO (woodser): combine with TradeUtil.java ?
|
||||
*/
|
||||
public class TradeUtils {
|
||||
|
||||
/**
|
||||
* Address to collect Haveno trade fees. TODO (woodser): move to config constants
|
||||
* Get address to collect trade fees.
|
||||
*
|
||||
* TODO: move to config constants?
|
||||
*
|
||||
* @return the address which collects trade fees
|
||||
*/
|
||||
public static String FEE_ADDRESS = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM";
|
||||
public static String getTradeFeeAddress() {
|
||||
switch (Config.baseCurrencyNetwork()) {
|
||||
case XMR_LOCAL:
|
||||
return "Bd37nTGHjL3RvPxc9dypzpWiXQrPzxxG4RsWAasD9CV2iZ1xfFZ7mzTKNDxWBfsqQSUimctAsGtTZ8c8bZJy35BYL9jYj88";
|
||||
case XMR_STAGENET:
|
||||
return "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM";
|
||||
case XMR_MAINNET:
|
||||
throw new RuntimeException("Mainnet fee address not implemented");
|
||||
default:
|
||||
throw new RuntimeException("Unhandled base currency network: " + Config.baseCurrencyNetwork());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the arbitrator signature for an offer is valid.
|
||||
|
|
|
@ -39,7 +39,7 @@ public class MakerSetsLockTime extends TradeTask {
|
|||
|
||||
// 10 days for altcoins, 20 days for other payment methods
|
||||
// For regtest dev environment we use 5 blocks
|
||||
int delay = Config.baseCurrencyNetwork().isStagenet() ?
|
||||
int delay = Config.baseCurrencyNetwork().isTestnet() ?
|
||||
5 :
|
||||
Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isBlockchain());
|
||||
|
||||
|
|
|
@ -778,9 +778,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
switch (baseCurrencyNetwork) {
|
||||
case XMR_MAINNET:
|
||||
return prefPayload.getBlockChainExplorerMainNet();
|
||||
case XMR_TESTNET:
|
||||
case XMR_STAGENET:
|
||||
return prefPayload.getBlockChainExplorerTestNet();
|
||||
case XMR_LOCAL:
|
||||
return prefPayload.getBlockChainExplorerTestNet(); // TODO: no testnet explorer for private testnet
|
||||
default:
|
||||
throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork);
|
||||
}
|
||||
|
@ -791,9 +792,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
switch (baseCurrencyNetwork) {
|
||||
case XMR_MAINNET:
|
||||
return BTC_MAIN_NET_EXPLORERS;
|
||||
case XMR_TESTNET:
|
||||
case XMR_STAGENET:
|
||||
return BTC_TEST_NET_EXPLORERS;
|
||||
case XMR_LOCAL:
|
||||
return BTC_TEST_NET_EXPLORERS; // TODO: no testnet explorer for private testnet
|
||||
default:
|
||||
throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork);
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util;
|
||||
|
||||
import bisq.core.filter.FilterManager;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class FeeReceiverSelector {
|
||||
public static final String BTC_FEE_RECEIVER_ADDRESS = "38bZBj5peYS3Husdz7AH3gEUiUbYRD951t";
|
||||
|
||||
public static String getMostRecentAddress() {
|
||||
return Config.baseCurrencyNetwork().isMainnet() ? BTC_FEE_RECEIVER_ADDRESS :
|
||||
Config.baseCurrencyNetwork().isTestnet() ? "2N4mVTpUZAnhm9phnxB7VrHB4aBhnWrcUrV" :
|
||||
"2MzBNTJDjjXgViKBGnatDU3yWkJ8pJkEg9w";
|
||||
}
|
||||
|
||||
public static String getAddress(FilterManager filterManager) {
|
||||
return getAddress(filterManager, new Random());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static String getAddress(FilterManager filterManager, Random rnd) {
|
||||
List<String> feeReceivers = Optional.ofNullable(filterManager.getFilter())
|
||||
.flatMap(f -> Optional.ofNullable(f.getBtcFeeReceiverAddresses()))
|
||||
.orElse(List.of());
|
||||
|
||||
List<Long> amountList = new ArrayList<>();
|
||||
List<String> receiverAddressList = new ArrayList<>();
|
||||
|
||||
feeReceivers.forEach(e -> {
|
||||
try {
|
||||
String[] tokens = e.split("#");
|
||||
amountList.add(Coin.parseCoin(tokens[1]).longValue()); // total amount the victim should receive
|
||||
receiverAddressList.add(tokens[0]); // victim's receiver address
|
||||
} catch (RuntimeException ignore) {
|
||||
// If input format is not as expected we ignore entry
|
||||
}
|
||||
});
|
||||
|
||||
if (!amountList.isEmpty()) {
|
||||
return receiverAddressList.get(weightedSelection(amountList, rnd));
|
||||
}
|
||||
|
||||
// If no fee address receiver is defined via filter we use the hard coded recent address
|
||||
return getMostRecentAddress();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static int weightedSelection(List<Long> weights, Random rnd) {
|
||||
long sum = weights.stream().mapToLong(n -> n).sum();
|
||||
long target = rnd.longs(0, sum).findFirst().orElseThrow();
|
||||
int i;
|
||||
for (i = 0; i < weights.size() && target >= 0; i++) {
|
||||
target -= weights.get(i);
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
}
|
|
@ -2391,7 +2391,7 @@ formatter.asTaker={0} {1} as taker
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} jako příjemce
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} als Abnehmer
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Bitcoin-Hauptnetzwerk
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Bitcoin-Testnetzwerk
|
||||
XMR_LOCAL=Bitcoin-Testnetzwerk
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Bitcoin-Regtest
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} como tomador
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Red principal de Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Red de prueba de Monero
|
||||
XMR_LOCAL=Red de prueba de Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Stagenet Monero
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} به عنوان پذیرنده
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1824,7 +1824,7 @@ formatter.asTaker={0} {1} en tant que taker
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} come taker
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Mainnet Bitcoin
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Testnet Bitcoin
|
||||
XMR_LOCAL=Testnet Bitcoin
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Regtest Bitcoin
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1}のテイカー
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1831,7 +1831,7 @@ formatter.asTaker={0} {1} como aceitador
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Mainnet do Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Testnet do Monero
|
||||
XMR_LOCAL=Testnet do Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Stagenet do Monero
|
||||
|
||||
|
|
|
@ -1821,7 +1821,7 @@ formatter.asTaker={0} {1} como aceitador
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Mainnet de Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Testnet de Monero
|
||||
XMR_LOCAL=Testnet de Monero
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Stagenet Monero
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} как тейкер
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=XMR Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=XMR Testnet
|
||||
XMR_LOCAL=XMR Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=XMR Stagenet
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} ในฐานะคนรับ
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Monero Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Monero Testnet
|
||||
XMR_LOCAL=Monero Local Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Monero Stagenet
|
||||
|
||||
|
|
|
@ -1825,7 +1825,7 @@ formatter.asTaker={0} {1} như người nhận
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=Bitcoin Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=Bitcoin Testnet
|
||||
XMR_LOCAL=Bitcoin Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=Bitcoin Regtest
|
||||
|
||||
|
|
|
@ -1827,7 +1827,7 @@ formatter.asTaker={0} {1} 是买家
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=XMR Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=XMR Testnet
|
||||
XMR_LOCAL=XMR Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=XMR Stagenet
|
||||
|
||||
|
|
|
@ -1823,7 +1823,7 @@ formatter.asTaker={0} {1} 是買家
|
|||
# suppress inspection "UnusedProperty"
|
||||
XMR_MAINNET=XMR Mainnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_TESTNET=XMR Testnet
|
||||
XMR_LOCAL=XMR Testnet
|
||||
# suppress inspection "UnusedProperty"
|
||||
XMR_STAGENET=XMR Stagenet
|
||||
|
||||
|
|
3
core/src/main/resources/xmr_local.seednodes
Normal file
3
core/src/main/resources/xmr_local.seednodes
Normal file
|
@ -0,0 +1,3 @@
|
|||
# nodeaddress.onion:port [(@owner,@backup)]
|
||||
localhost:2002 (@devtest1)
|
||||
localhost:3002 (@devtest2)
|
|
@ -1,3 +1,3 @@
|
|||
# nodeaddress.onion:port [(@owner,@backup)]
|
||||
localhost:2002 (@devtest1)
|
||||
localhost:3002 (@devtest2)
|
||||
# nodeaddress.onion:port [(@owner)]
|
||||
localhost:2002 (@devtest1) # TODO: replace with hosted stagenet seednodes
|
||||
localhost:3002 (@devtest2)
|
|
@ -1,2 +0,0 @@
|
|||
# nodeaddress.onion:port [(@owner)]
|
||||
placeholder.onion:8001
|
|
@ -56,11 +56,11 @@ public class ArbitratorManagerTest {
|
|||
add("es");
|
||||
}};
|
||||
|
||||
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null, null,
|
||||
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null,
|
||||
languagesOne, 0L, null, "", null,
|
||||
null, null);
|
||||
|
||||
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null, null,
|
||||
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null,
|
||||
languagesTwo, 0L, null, "", null,
|
||||
null, null);
|
||||
|
||||
|
@ -92,11 +92,11 @@ public class ArbitratorManagerTest {
|
|||
add("es");
|
||||
}};
|
||||
|
||||
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null, null,
|
||||
Arbitrator one = new Arbitrator(new NodeAddress("arbitrator:1"), null, null,
|
||||
languagesOne, 0L, null, "", null,
|
||||
null, null);
|
||||
|
||||
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null, null,
|
||||
Arbitrator two = new Arbitrator(new NodeAddress("arbitrator:2"), null, null,
|
||||
languagesTwo, 0L, null, "", null,
|
||||
null, null);
|
||||
|
||||
|
|
|
@ -44,8 +44,7 @@ public class ArbitratorTest {
|
|||
|
||||
public static Arbitrator getArbitratorMock() {
|
||||
return new Arbitrator(new NodeAddress("host", 1000),
|
||||
getBytes(100),
|
||||
"btcaddress",
|
||||
"xmraddress",
|
||||
new PubKeyRing(getBytes(100), getBytes(100)),
|
||||
Lists.newArrayList(),
|
||||
new Date().getTime(),
|
||||
|
|
|
@ -79,7 +79,7 @@ public class CurrencyUtilTest {
|
|||
|
||||
// For testnet its ok
|
||||
assertEquals(CurrencyUtil.findAsset(assetRegistry, "MOCK_COIN",
|
||||
BaseCurrencyNetwork.XMR_TESTNET).get().getTickerSymbol(), "MOCK_COIN");
|
||||
BaseCurrencyNetwork.XMR_LOCAL).get().getTickerSymbol(), "MOCK_COIN");
|
||||
assertEquals(Coin.Network.TESTNET, mockTestnetCoin.getNetwork());
|
||||
|
||||
// For regtest its still found
|
||||
|
@ -90,7 +90,7 @@ public class CurrencyUtilTest {
|
|||
// We test if we are not on mainnet to get the mainnet coin
|
||||
Coin ether = new Ether();
|
||||
assertEquals(CurrencyUtil.findAsset(assetRegistry, "ETH",
|
||||
BaseCurrencyNetwork.XMR_TESTNET).get().getTickerSymbol(), "ETH");
|
||||
BaseCurrencyNetwork.XMR_LOCAL).get().getTickerSymbol(), "ETH");
|
||||
assertEquals(CurrencyUtil.findAsset(assetRegistry, "ETH",
|
||||
BaseCurrencyNetwork.XMR_STAGENET).get().getTickerSymbol(), "ETH");
|
||||
assertEquals(Coin.Network.MAINNET, ether.getNetwork());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue