support local, stagenet, and mainnet xmr network configuration (#335)

remove btc wallet
disable local zmq
This commit is contained in:
woodser 2022-07-07 09:10:59 -04:00 committed by GitHub
parent b4112e50e9
commit e2208355b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 595 additions and 899 deletions

View file

@ -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) {

View file

@ -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(),

View file

@ -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());
}
}

View file

@ -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()) {

View file

@ -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");
}

View file

@ -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,
() -> {

View file

@ -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);

View file

@ -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() {

View file

@ -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);
});

View file

@ -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());
});
}

View file

@ -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() {

View file

@ -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() {

View file

@ -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());

View file

@ -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,

View file

@ -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");

View file

@ -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.

View file

@ -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,

View file

@ -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.

View file

@ -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" +
'}';
}
}

View file

@ -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");

View file

@ -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;
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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

View file

@ -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 + '\'' +

View file

@ -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.

View file

@ -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());

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,3 @@
# nodeaddress.onion:port [(@owner,@backup)]
localhost:2002 (@devtest1)
localhost:3002 (@devtest2)

View file

@ -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)

View file

@ -1,2 +0,0 @@
# nodeaddress.onion:port [(@owner)]
placeholder.onion:8001

View file

@ -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);

View file

@ -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(),

View file

@ -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());