Add btc network selector to preferences

This commit is contained in:
Manfred Karrer 2015-05-10 13:31:09 +02:00
parent c1e0524090
commit 844a6dd66c
17 changed files with 248 additions and 82 deletions

View file

@ -22,7 +22,6 @@ import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node; import io.bitsquare.p2p.Node;
import java.util.Collection; import java.util.Collection;
import java.util.Random;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.tomp2p.dht.PeerBuilderDHT; import net.tomp2p.dht.PeerBuilderDHT;
@ -45,6 +44,9 @@ public class BootstrapNode {
private static final String VERSION = "0.1.3"; private static final String VERSION = "0.1.3";
public static final String P2P_ID = "node.p2pId";
public static int DEFAULT_P2P_ID = 1; // 0 | 1 | 2 for mainnet/testnet/regtest
private static Peer peer = null; private static Peer peer = null;
private final Environment env; private final Environment env;
@ -56,12 +58,13 @@ public class BootstrapNode {
public void start() { public void start() {
int port = env.getProperty(Node.PORT_KEY, Integer.class, BootstrapNodes.PORT); int p2pId = env.getProperty(P2P_ID, Integer.class, DEFAULT_P2P_ID);
String name = env.getProperty(Node.NAME_KEY, BootstrapNodes.DEFAULT_NODE_NAME); int port = env.getProperty(Node.PORT_KEY, Integer.class, BootstrapNodes.BASE_PORT + p2pId);
String name = env.getRequiredProperty(Node.NAME_KEY);
Logging.setup(name + "_" + port); Logging.setup(name + "_" + port);
try { try {
Number160 peerId = Number160.createHash(new Random().nextInt()); Number160 peerId = Number160.createHash(name);
/* /*
DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(50); DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(50);
ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration(); ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration();
@ -73,6 +76,7 @@ public class BootstrapNode {
peer = new PeerBuilder(peerId) peer = new PeerBuilder(peerId)
.ports(port) .ports(port)
.p2pId(p2pId)
/* .channelClientConfiguration(clientConf) /* .channelClientConfiguration(clientConf)
.channelServerConfiguration(serverConf)*/ .channelServerConfiguration(serverConf)*/
.start(); .start();
@ -87,7 +91,7 @@ public class BootstrapNode {
if (!name.equals(BootstrapNodes.LOCALHOST.getName())) { if (!name.equals(BootstrapNodes.LOCALHOST.getName())) {
Collection<PeerAddress> bootstrapNodes = BootstrapNodes.getAllBootstrapNodes().stream().filter(e -> !e.getName().equals(name)) Collection<PeerAddress> bootstrapNodes = BootstrapNodes.getAllBootstrapNodes().stream().filter(e -> !e.getName().equals(name))
.map(e -> e.toPeerAddress()).collect(Collectors.toList()); .map(e -> e.toPeerAddressWithPort(port)).collect(Collectors.toList());
log.info("Bootstrapping to " + bootstrapNodes.size() + " bootstrapNode(s)"); log.info("Bootstrapping to " + bootstrapNodes.size() + " bootstrapNode(s)");
log.info("Bootstrapping bootstrapNodes " + bootstrapNodes); log.info("Bootstrapping bootstrapNodes " + bootstrapNodes);

View file

@ -32,9 +32,13 @@ public class BootstrapNodeMain extends BitsquareExecutable {
} }
protected void customizeOptionParsing(OptionParser parser) { protected void customizeOptionParsing(OptionParser parser) {
parser.accepts(Node.NAME_KEY, description("Name of this node", BootstrapNodes.DEFAULT_NODE_NAME)) parser.accepts(Node.NAME_KEY, description("Name of this node", null))
.withRequiredArg(); .withRequiredArg()
parser.accepts(Node.PORT_KEY, description("Port to listen on", BootstrapNodes.PORT)) .isRequired();
parser.accepts(Node.PORT_KEY, description("Port to listen on", BootstrapNodes.BASE_PORT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(BootstrapNode.P2P_ID, description("P2P Network ID [0 | 1 | 2 for mainnet/testnet/regtest]", BootstrapNode.DEFAULT_P2P_ID))
.withRequiredArg() .withRequiredArg()
.ofType(int.class); .ofType(int.class);
} }

View file

@ -18,7 +18,6 @@
package io.bitsquare.app; package io.bitsquare.app;
import io.bitsquare.BitsquareException; import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.UserAgent; import io.bitsquare.btc.UserAgent;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.KeyStorage; import io.bitsquare.crypto.KeyStorage;
@ -88,12 +87,8 @@ public class BitsquareEnvironment extends StandardEnvironment {
(String) commandLineProperties.getProperty(APP_DATA_DIR_KEY) : (String) commandLineProperties.getProperty(APP_DATA_DIR_KEY) :
appDataDir(userDataDir, appName); appDataDir(userDataDir, appName);
String bitcoinNetwork = commandLineProperties.containsProperty(BitcoinNetwork.KEY) ?
(String) commandLineProperties.getProperty(BitcoinNetwork.KEY) :
BitcoinNetwork.DEFAULT.toString();
this.bootstrapNodePort = commandLineProperties.containsProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) ? this.bootstrapNodePort = commandLineProperties.containsProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) ?
(String) commandLineProperties.getProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) : String.valueOf(BootstrapNodes.PORT); (String) commandLineProperties.getProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) : String.valueOf(BootstrapNodes.BASE_PORT);
MutablePropertySources propertySources = this.getPropertySources(); MutablePropertySources propertySources = this.getPropertySources();
propertySources.addFirst(commandLineProperties); propertySources.addFirst(commandLineProperties);

View file

@ -40,7 +40,6 @@ public class BitcoinModule extends BitsquareModule {
@Override @Override
protected void configure() { protected void configure() {
bind(BitcoinNetwork.class).toInstance(env.getProperty(BitcoinNetwork.KEY, BitcoinNetwork.class, BitcoinNetwork.DEFAULT));
bind(RegTestHost.class).toInstance(env.getProperty(RegTestHost.KEY, RegTestHost.class, RegTestHost.DEFAULT)); bind(RegTestHost.class).toInstance(env.getProperty(RegTestHost.KEY, RegTestHost.class, RegTestHost.DEFAULT));
bind(FeePolicy.class).in(Singleton.class); bind(FeePolicy.class).in(Singleton.class);

View file

@ -22,7 +22,9 @@ import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams; import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.params.TestNet3Params;
public enum BitcoinNetwork { import java.io.Serializable;
public enum BitcoinNetwork implements Serializable {
MAINNET(MainNetParams.get()), MAINNET(MainNetParams.get()),
TESTNET(TestNet3Params.get()), TESTNET(TestNet3Params.get()),

View file

@ -18,6 +18,7 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.BitsquareException; import io.bitsquare.BitsquareException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
@ -41,8 +42,8 @@ public class FeePolicy {
private final String takeOfferFeeAddress; private final String takeOfferFeeAddress;
@Inject @Inject
public FeePolicy(BitcoinNetwork bitcoinNetwork) { public FeePolicy(Preferences preferences) {
this.bitcoinNetwork = bitcoinNetwork; this.bitcoinNetwork = preferences.getBitcoinNetwork();
switch (bitcoinNetwork) { switch (bitcoinNetwork) {
case TESTNET: case TESTNET:

View file

@ -20,6 +20,7 @@ package io.bitsquare.btc;
import io.bitsquare.btc.exceptions.SigningException; import io.bitsquare.btc.exceptions.SigningException;
import io.bitsquare.btc.exceptions.TransactionVerificationException; import io.bitsquare.btc.exceptions.TransactionVerificationException;
import io.bitsquare.btc.exceptions.WalletException; import io.bitsquare.btc.exceptions.WalletException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
@ -91,8 +92,8 @@ public class TradeWalletService {
private final FeePolicy feePolicy; private final FeePolicy feePolicy;
@Inject @Inject
public TradeWalletService(BitcoinNetwork bitcoinNetwork, FeePolicy feePolicy) { public TradeWalletService(Preferences preferences, FeePolicy feePolicy) {
this.params = bitcoinNetwork.getParameters(); this.params = preferences.getBitcoinNetwork().getParameters();
this.feePolicy = feePolicy; this.feePolicy = feePolicy;
} }

View file

@ -21,6 +21,7 @@ import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener; import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.crypto.CryptoService; import io.bitsquare.crypto.CryptoService;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.AbstractWalletEventListener; import org.bitcoinj.core.AbstractWalletEventListener;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
@ -127,15 +128,15 @@ public class WalletService {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public WalletService(BitcoinNetwork bitcoinNetwork, RegTestHost regTestHost, CryptoService cryptoService, public WalletService(RegTestHost regTestHost, CryptoService cryptoService,
TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent, TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent,
@Named(DIR_KEY) File walletDir, @Named(PREFIX_KEY) String walletPrefix) { @Named(DIR_KEY) File walletDir, @Named(PREFIX_KEY) String walletPrefix, Preferences preferences) {
this.regTestHost = regTestHost; this.regTestHost = regTestHost;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
this.addressEntryList = addressEntryList; this.addressEntryList = addressEntryList;
this.params = bitcoinNetwork.getParameters(); this.params = preferences.getBitcoinNetwork().getParameters();
this.cryptoService = cryptoService; this.cryptoService = cryptoService;
this.walletDir = walletDir; this.walletDir = new File(walletDir, preferences.getBitcoinNetwork().toString().toLowerCase());
this.walletPrefix = walletPrefix; this.walletPrefix = walletPrefix;
this.userAgent = userAgent; this.userAgent = userAgent;
} }

View file

@ -27,20 +27,19 @@ import org.slf4j.LoggerFactory;
public class BootstrapNodes { public class BootstrapNodes {
private static final Logger log = LoggerFactory.getLogger(BootstrapNodes.class); private static final Logger log = LoggerFactory.getLogger(BootstrapNodes.class);
public static final int PORT = 7366; public static final int BASE_PORT = 7366; // port will be evaluated from btc network 7366 for mainnet, 7367 for testnet and 7368 for regtest
public static final String DEFAULT_NODE_NAME = "default";
private static List<Node> bootstrapNodes = Arrays.asList( private static List<Node> bootstrapNodes = Arrays.asList(
Node.at(DEFAULT_NODE_NAME, "188.226.179.109", PORT), //Node.at("digitalocean1.bitsquare.io", "188.226.179.109", BASE_PORT),
Node.at(DEFAULT_NODE_NAME, "52.24.144.42", PORT), Node.at("aws1.bitsquare.io", "52.24.144.42", BASE_PORT),
Node.at(DEFAULT_NODE_NAME, "52.11.125.194", PORT) Node.at("aws2.bitsquare.io", "52.11.125.194", BASE_PORT)
); );
/** /**
* A locally-running BootstrapNode instance. * A locally-running BootstrapNode instance.
* Typically used only for testing. Not included in results from {@link #getAllBootstrapNodes()}. * Typically used only for testing. Not included in results from {@link #getAllBootstrapNodes()}.
*/ */
public static Node LOCALHOST = Node.at("localhost", "127.0.0.1", PORT); public static Node LOCALHOST = Node.at("localhost", "127.0.0.1", BASE_PORT);
private static Node selectedNode = bootstrapNodes.get(new Random().nextInt(bootstrapNodes.size())); private static Node selectedNode = bootstrapNodes.get(new Random().nextInt(bootstrapNodes.size()));

View file

@ -94,12 +94,12 @@ public final class Node {
return port; return port;
} }
public PeerAddress toPeerAddress() { public PeerAddress toPeerAddressWithPort(int port) {
try { try {
return new PeerAddress(Number160.createHash(getName()), return new PeerAddress(Number160.createHash(getName()),
InetAddress.getByName(getIp()), InetAddress.getByName(getIp()),
getPort(), port,
getPort()); port);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
log.error("toPeerAddress failed: " + e.getMessage()); log.error("toPeerAddress failed: " + e.getMessage());
return null; return null;

View file

@ -19,6 +19,7 @@ package io.bitsquare.p2p.tomp2p;
import io.bitsquare.p2p.BootstrapNodes; import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node; import io.bitsquare.p2p.Node;
import io.bitsquare.user.Preferences;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
@ -114,17 +115,17 @@ public class BootstrappedPeerBuilder {
} }
} }
private KeyPair keyPair;
private final int port; private final int port;
private final boolean useManualPortForwarding; private final boolean useManualPortForwarding;
private Node bootstrapNode;
private final String networkInterface; private final String networkInterface;
private final Preferences preferences;
private final SettableFuture<PeerDHT> settableFuture = SettableFuture.create(); private final SettableFuture<PeerDHT> settableFuture = SettableFuture.create();
private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.UNDEFINED); private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.UNDEFINED);
private final ObjectProperty<ConnectionType> connectionType = new SimpleObjectProperty<>(ConnectionType.UNDEFINED); private final ObjectProperty<ConnectionType> connectionType = new SimpleObjectProperty<>(ConnectionType.UNDEFINED);
private Node bootstrapNode;
private KeyPair keyPair;
private Peer peer; private Peer peer;
private PeerDHT peerDHT; private PeerDHT peerDHT;
private boolean retriedOtherBootstrapNode; private boolean retriedOtherBootstrapNode;
@ -139,13 +140,13 @@ public class BootstrappedPeerBuilder {
public BootstrappedPeerBuilder(@Named(Node.PORT_KEY) int port, public BootstrappedPeerBuilder(@Named(Node.PORT_KEY) int port,
@Named(USE_MANUAL_PORT_FORWARDING_KEY) boolean useManualPortForwarding, @Named(USE_MANUAL_PORT_FORWARDING_KEY) boolean useManualPortForwarding,
@Named(BOOTSTRAP_NODE_KEY) Node bootstrapNode, @Named(BOOTSTRAP_NODE_KEY) Node bootstrapNode,
@Named(NETWORK_INTERFACE_KEY) String networkInterface) { @Named(NETWORK_INTERFACE_KEY) String networkInterface,
Preferences preferences) {
this.port = port; this.port = port;
this.useManualPortForwarding = useManualPortForwarding; this.useManualPortForwarding = useManualPortForwarding;
this.bootstrapNode = bootstrapNode; this.bootstrapNode = bootstrapNode;
this.networkInterface = networkInterface; this.networkInterface = networkInterface;
this.preferences = preferences;
log.debug("Bootstrap to {}", bootstrapNode.toString());
} }
@ -168,6 +169,10 @@ public class BootstrappedPeerBuilder {
public SettableFuture<PeerDHT> start(int networkId) { public SettableFuture<PeerDHT> start(int networkId) {
try { try {
// port is evaluated from btc network. 7366 for mainnet, 7367 for testnet and 7368 for regtest
bootstrapNode = Node.at(bootstrapNode.getName(), bootstrapNode.getIp(), bootstrapNode.getPort() + networkId);
log.debug("Bootstrap to {}", bootstrapNode.toString());
DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(20); DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(20);
ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration(); ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration();
clientConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup)); clientConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup));
@ -219,7 +224,9 @@ public class BootstrappedPeerBuilder {
// log.debug("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics); // log.debug("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
} }
}); });
if (preferences.getUseUPnP())
discoverExternalAddressUsingUPnP();
else
discoverExternalAddress(); discoverExternalAddress();
} catch (IOException e) { } catch (IOException e) {
handleError(State.PEER_CREATION_FAILED, "Cannot create a peer with port: " + handleError(State.PEER_CREATION_FAILED, "Cannot create a peer with port: " +
@ -247,10 +254,11 @@ public class BootstrappedPeerBuilder {
// 4. If the port forwarding failed we can try as last resort to open a permanent TCP connection to the // 4. If the port forwarding failed we can try as last resort to open a permanent TCP connection to the
// bootstrap node and use that peer as relay // bootstrap node and use that peer as relay
private void discoverExternalAddress() { private void discoverExternalAddressUsingUPnP() {
FutureDiscover futureDiscover = peer.discover().peerAddress(getBootstrapAddress()).start(); FutureDiscover futureDiscover = peer.discover().peerAddress(getBootstrapAddress()).start();
setState(State.DISCOVERY_STARTED); setState(State.DISCOVERY_STARTED);
PeerNAT peerNAT = new PeerBuilderNAT(peer).start(); PeerNAT peerNAT = new PeerBuilderNAT(peer).start();
FutureNAT futureNAT = peerNAT.startSetupPortforwarding(futureDiscover); FutureNAT futureNAT = peerNAT.startSetupPortforwarding(futureDiscover);
FutureRelayNAT futureRelayNAT = peerNAT.startRelay(new TCPRelayClientConfig(), futureDiscover, futureNAT); FutureRelayNAT futureRelayNAT = peerNAT.startRelay(new TCPRelayClientConfig(), futureDiscover, futureNAT);
@ -312,6 +320,62 @@ public class BootstrappedPeerBuilder {
}); });
} }
private void discoverExternalAddress() {
FutureDiscover futureDiscover = peer.discover().peerAddress(getBootstrapAddress()).start();
setState(State.DISCOVERY_STARTED);
PeerNAT peerNAT = new PeerBuilderNAT(peer).start();
FutureRelayNAT futureRelayNAT = peerNAT.startRelay(new TCPRelayClientConfig(), futureDiscover);
futureRelayNAT.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture futureRelayNAT) throws Exception {
if (futureDiscover.isSuccess()) {
if (useManualPortForwarding) {
setState(State.DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED,
"NAT traversal successful with manual port forwarding.");
setConnectionType(ConnectionType.MANUAL_PORT_FORWARDING);
bootstrap();
}
else {
setState(State.DISCOVERY_DIRECT_SUCCEEDED, "Visible to the network. No NAT traversal needed.");
setConnectionType(ConnectionType.DIRECT);
bootstrap();
}
}
else {
if (futureRelayNAT.isSuccess()) {
// relay mode succeeded
setState(State.RELAY_SUCCEEDED, "Using relay mode.");
setConnectionType(ConnectionType.RELAY);
bootstrap();
}
else {
if (!retriedOtherBootstrapNode && BootstrapNodes.getAllBootstrapNodes().size() > 1) {
log.warn("Bootstrap failed with bootstrapNode: " + bootstrapNode + ". We try again with another node.");
retriedOtherBootstrapNode = true;
Optional<Node> optional = BootstrapNodes.getAllBootstrapNodes().stream().filter(e -> !e.equals(bootstrapNode)).findAny();
if (optional.isPresent()) {
bootstrapNode = optional.get();
executor.execute(() -> discoverExternalAddress());
}
}
else {
// All attempts failed. Give up...
handleError(State.RELAY_FAILED, "NAT traversal using relay mode failed " +
futureRelayNAT.failedReason());
}
}
}
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
handleError(State.RELAY_FAILED, "Exception at bootstrap: " + t.getMessage());
}
});
}
private void bootstrap() { private void bootstrap() {
log.trace("start bootstrap"); log.trace("start bootstrap");

View file

@ -18,6 +18,7 @@
package io.bitsquare.user; package io.bitsquare.user;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.MonetaryFormat;
@ -37,6 +38,8 @@ import javafx.beans.property.StringProperty;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
public class Preferences implements Serializable { public class Preferences implements Serializable {
// That object is saved to disc. We need to take care of changes to not break deserialization. // That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = Version.LOCAL_DB_VERSION; private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
@ -45,7 +48,6 @@ public class Preferences implements Serializable {
// Deactivate mBit for now as most screens are not supporting it yet // Deactivate mBit for now as most screens are not supporting it yet
transient private static final List<String> BTC_DENOMINATIONS = Arrays.asList(MonetaryFormat.CODE_BTC/*, MonetaryFormat.CODE_MBTC*/); transient private static final List<String> BTC_DENOMINATIONS = Arrays.asList(MonetaryFormat.CODE_BTC/*, MonetaryFormat.CODE_MBTC*/);
public static List<String> getBtcDenominations() { public static List<String> getBtcDenominations() {
return BTC_DENOMINATIONS; return BTC_DENOMINATIONS;
} }
@ -54,21 +56,25 @@ public class Preferences implements Serializable {
// Persisted fields // Persisted fields
private String btcDenomination = MonetaryFormat.CODE_BTC; private String btcDenomination = MonetaryFormat.CODE_BTC;
private boolean useAnimations = true; private boolean useAnimations = true;
private boolean useEffects = true; private boolean useEffects = true;
private boolean displaySecurityDepositInfo = true; private boolean displaySecurityDepositInfo = true;
private boolean useUPnP = true;
private BitcoinNetwork bitcoinNetwork;
// Observable wrappers // Observable wrappers
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination); transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
transient private final BooleanProperty useAnimationsProperty = new SimpleBooleanProperty(useAnimations); transient private final BooleanProperty useAnimationsProperty = new SimpleBooleanProperty(useAnimations);
transient private final BooleanProperty useEffectsProperty = new SimpleBooleanProperty(useEffects); transient private final BooleanProperty useEffectsProperty = new SimpleBooleanProperty(useEffects);
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public Preferences(Storage<Preferences> storage) { public Preferences(Storage<Preferences> storage, Environment environment) {
this.storage = storage; this.storage = storage;
Preferences persisted = storage.initAndGetPersisted(this); Preferences persisted = storage.initAndGetPersisted(this);
@ -76,8 +82,13 @@ public class Preferences implements Serializable {
setBtcDenomination(persisted.btcDenomination); setBtcDenomination(persisted.btcDenomination);
setUseAnimations(persisted.useAnimations); setUseAnimations(persisted.useAnimations);
setUseEffects(persisted.useEffects); setUseEffects(persisted.useEffects);
setUseUPnP(persisted.useUPnP);
setBitcoinNetwork(persisted.bitcoinNetwork);
displaySecurityDepositInfo = persisted.getDisplaySecurityDepositInfo(); displaySecurityDepositInfo = persisted.getDisplaySecurityDepositInfo();
} }
else {
setBitcoinNetwork(environment.getProperty(BitcoinNetwork.KEY, BitcoinNetwork.class, BitcoinNetwork.DEFAULT));
}
// Use that to guarantee update of the serializable field and to make a storage update in case of a change // Use that to guarantee update of the serializable field and to make a storage update in case of a change
btcDenominationProperty.addListener((ov) -> { btcDenominationProperty.addListener((ov) -> {
@ -116,6 +127,16 @@ public class Preferences implements Serializable {
storage.queueUpForSave(); storage.queueUpForSave();
} }
public void setUseUPnP(boolean useUPnP) {
this.useUPnP = useUPnP;
storage.queueUpForSave();
}
public void setBitcoinNetwork(BitcoinNetwork bitcoinNetwork) {
this.bitcoinNetwork = bitcoinNetwork;
storage.queueUpForSave();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getter // Getter
@ -149,5 +170,11 @@ public class Preferences implements Serializable {
return useEffectsProperty; return useEffectsProperty;
} }
public boolean getUseUPnP() {
return useUPnP;
}
public BitcoinNetwork getBitcoinNetwork() {
return bitcoinNetwork;
}
} }

View file

@ -69,6 +69,8 @@ public class BitsquareApp extends Application {
private List<String> corruptedDatabaseFiles = new ArrayList<>(); private List<String> corruptedDatabaseFiles = new ArrayList<>();
private MainView mainView; private MainView mainView;
public static Runnable shutDownHandler;
public static void setEnvironment(Environment env) { public static void setEnvironment(Environment env) {
BitsquareApp.env = env; BitsquareApp.env = env;
} }
@ -77,6 +79,8 @@ public class BitsquareApp extends Application {
public void start(Stage primaryStage) throws IOException { public void start(Stage primaryStage) throws IOException {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
shutDownHandler = this::stop;
// setup UncaughtExceptionHandler // setup UncaughtExceptionHandler
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> { Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
// Might come from another thread // Might come from another thread

View file

@ -20,7 +20,6 @@ package io.bitsquare.gui.main;
import io.bitsquare.app.UpdateProcess; import io.bitsquare.app.UpdateProcess;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitrationRepository; import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.KeyRing; import io.bitsquare.crypto.KeyRing;
import io.bitsquare.fiat.FiatAccount; import io.bitsquare.fiat.FiatAccount;
@ -33,6 +32,7 @@ import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.user.Preferences;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
@ -117,7 +117,7 @@ class MainViewModel implements ViewModel {
@Inject @Inject
public MainViewModel(User user, KeyRing keyRing, WalletService walletService, ArbitrationRepository arbitrationRepository, ClientNode clientNode, public MainViewModel(User user, KeyRing keyRing, WalletService walletService, ArbitrationRepository arbitrationRepository, ClientNode clientNode,
TradeManager tradeManager, OpenOfferManager openOfferManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences, UpdateProcess updateProcess,
BSFormatter formatter) { BSFormatter formatter) {
this.user = user; this.user = user;
this.keyRing = keyRing; this.keyRing = keyRing;
@ -129,8 +129,8 @@ class MainViewModel implements ViewModel {
this.updateProcess = updateProcess; this.updateProcess = updateProcess;
this.formatter = formatter; this.formatter = formatter;
bitcoinNetworkAsString = formatter.formatBitcoinNetwork(bitcoinNetwork); bitcoinNetworkAsString = formatter.formatBitcoinNetwork(preferences.getBitcoinNetwork());
networkId = bitcoinNetwork.ordinal(); networkId = preferences.getBitcoinNetwork().ordinal();
updateProcess.state.addListener((observableValue, oldValue, newValue) -> applyUpdateState(newValue)); updateProcess.state.addListener((observableValue, oldValue, newValue) -> applyUpdateState(newValue));
applyUpdateState(updateProcess.state.get()); applyUpdateState(updateProcess.state.get());

View file

@ -31,7 +31,7 @@
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/> <Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
</padding> </padding>
<TitledGroupBg text="Network information" GridPane.rowSpan="8"/> <TitledGroupBg text="Bitcoin network" GridPane.rowSpan="3"/>
<Label text="Bitcoin network type:" GridPane.rowIndex="0"> <Label text="Bitcoin network type:" GridPane.rowIndex="0">
<GridPane.margin> <GridPane.margin>
@ -45,46 +45,54 @@
</GridPane.margin> </GridPane.margin>
</TextField> </TextField>
<Label text="Bitcoin network connected peers:" GridPane.rowIndex="1"/> <Label text="Select Network:" GridPane.rowIndex="1"/>
<TextField fx:id="connectedPeersBTC" GridPane.rowIndex="1" GridPane.columnIndex="1" <ComboBox fx:id="netWorkComboBox" onAction="#onSelectNetwork" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
<Label text="Bitcoin network connected peers:" GridPane.rowIndex="2"/>
<TextField fx:id="connectedPeersBTC" GridPane.rowIndex="2" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/> mouseTransparent="true" focusTraversable="false"/>
<Label text="P2P network connection:" GridPane.rowIndex="2"/> <TitledGroupBg text="P2P network" GridPane.rowIndex="3" GridPane.rowSpan="5">
<TextField fx:id="connectionType" GridPane.rowIndex="2" GridPane.columnIndex="1" <padding>
mouseTransparent="true" focusTraversable="false"/> <Insets top="50.0"/>
</padding>
<Label text="P2P network connected peers:" GridPane.rowIndex="3"/>
<TextField fx:id="connectedPeersP2P" GridPane.rowIndex="3" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<Label text="My external visible P2P network address:" GridPane.rowIndex="4"/>
<TextField fx:id="nodeAddress" GridPane.rowIndex="4" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<Label text="P2P bootstrap node address:" GridPane.rowIndex="5">
<GridPane.margin> <GridPane.margin>
<Insets bottom="-15"/> <Insets bottom="-10" left="-10" right="-10" top="30"/>
</GridPane.margin>
</TitledGroupBg>
<Label text="P2P network connection:" GridPane.rowIndex="3">
<GridPane.margin>
<Insets top="50.0"/>
</GridPane.margin> </GridPane.margin>
</Label> </Label>
<TextField fx:id="bootstrapNodeAddress" GridPane.rowIndex="5" GridPane.columnIndex="1" <TextField fx:id="connectionType" GridPane.rowIndex="3" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"> mouseTransparent="true" focusTraversable="false">
<GridPane.margin> <GridPane.margin>
<Insets bottom="-15"/> <Insets top="50.0"/>
</GridPane.margin> </GridPane.margin>
</TextField> </TextField>
<Label text="Use UPnP:" GridPane.rowIndex="4"/>
<CheckBox fx:id="useUPnP" onAction="#onSelectUPnP" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
<Label text="P2P network connected peers:" GridPane.rowIndex="5"/>
<TextField fx:id="connectedPeersP2P" GridPane.rowIndex="5" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<Label text="My external visible P2P network address:" GridPane.rowIndex="6"/>
<TextField fx:id="nodeAddress" GridPane.rowIndex="6" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<Label text="P2P bootstrap node address:" GridPane.rowIndex="7"/>
<TextField fx:id="bootstrapNodeAddress" GridPane.rowIndex="7" GridPane.columnIndex="1"
mouseTransparent="true" focusTraversable="false"/>
<columnConstraints> <columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/> <ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="200.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/> <ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints> </columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="NEVER"/>
</rowConstraints>
</GridPane> </GridPane>

View file

@ -17,18 +17,30 @@
package io.bitsquare.gui.main.settings.network; package io.bitsquare.gui.main.settings.network;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.common.view.InitializableView; import io.bitsquare.gui.common.view.InitializableView;
import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.BSResources;
import io.bitsquare.p2p.ClientNode; import io.bitsquare.p2p.ClientNode;
import io.bitsquare.user.Preferences;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.controlsfx.control.action.AbstractAction;
import org.controlsfx.control.action.Action;
import static javafx.beans.binding.Bindings.createStringBinding; import static javafx.beans.binding.Bindings.createStringBinding;
@FxmlView @FxmlView
@ -36,14 +48,19 @@ public class NetworkSettingsView extends InitializableView {
private final String bitcoinNetworkString; private final String bitcoinNetworkString;
private final WalletService walletService; private final WalletService walletService;
private final Preferences preferences;
private final ClientNode clientNode; private final ClientNode clientNode;
@FXML TextField bitcoinNetwork, connectionType, nodeAddress, bootstrapNodeAddress, connectedPeersBTC, connectedPeersP2P; @FXML TextField bitcoinNetwork, connectionType, nodeAddress, bootstrapNodeAddress, connectedPeersBTC, connectedPeersP2P;
@FXML CheckBox useUPnP;
@FXML ComboBox<BitcoinNetwork> netWorkComboBox;
@Inject @Inject
public NetworkSettingsView(BitcoinNetwork bitcoinNetwork, WalletService walletService, ClientNode clientNode, BSFormatter formatter) { public NetworkSettingsView(WalletService walletService, ClientNode clientNode, Preferences preferences, BSFormatter
formatter) {
this.walletService = walletService; this.walletService = walletService;
this.bitcoinNetworkString = formatter.formatBitcoinNetwork(bitcoinNetwork); this.preferences = preferences;
this.bitcoinNetworkString = formatter.formatBitcoinNetwork(preferences.getBitcoinNetwork());
this.clientNode = clientNode; this.clientNode = clientNode;
} }
@ -56,6 +73,46 @@ public class NetworkSettingsView extends InitializableView {
connectedPeersP2P.textProperty().bind(createStringBinding(() -> String.valueOf(clientNode.numPeersProperty().get()), clientNode.numPeersProperty())); connectedPeersP2P.textProperty().bind(createStringBinding(() -> String.valueOf(clientNode.numPeersProperty().get()), clientNode.numPeersProperty()));
nodeAddress.setText(clientNode.getAddress().toString()); nodeAddress.setText(clientNode.getAddress().toString());
bootstrapNodeAddress.setText(clientNode.getBootstrapNodeAddress().toString()); bootstrapNodeAddress.setText(clientNode.getBootstrapNodeAddress().toString());
useUPnP.setSelected(preferences.getUseUPnP());
netWorkComboBox.setItems(FXCollections.observableArrayList(BitcoinNetwork.values()));
netWorkComboBox.getSelectionModel().select(preferences.getBitcoinNetwork());
}
@FXML
void onSelectUPnP() {
preferences.setUseUPnP(useUPnP.isSelected());
}
@FXML
void onSelectNetwork() {
preferences.setBitcoinNetwork(netWorkComboBox.getSelectionModel().getSelectedItem());
List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.no")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "NO");
org.controlsfx.dialog.Dialog.Actions.NO.handle(actionEvent);
}
});
actions.add(new AbstractAction(BSResources.get("shared.yes")) {
@Override
public void handle(ActionEvent actionEvent) {
getProperties().put("type", "YES");
org.controlsfx.dialog.Dialog.Actions.YES.handle(actionEvent);
}
});
Action response = Popups.openConfirmPopup("Info", null,
"You need to restart the application to apply the change of the Bitcoin network." +
"\n\nDo you want to shutdown now?",
actions);
if (Popups.isYes(response))
BitsquareApp.shutDownHandler.run();
} }
} }

View file

@ -17,7 +17,7 @@
package io.bitsquare.gui.util.validation; package io.bitsquare.gui.util.validation;
import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
@ -26,11 +26,11 @@ import javax.inject.Inject;
public final class BtcAddressValidator extends InputValidator { public final class BtcAddressValidator extends InputValidator {
private final BitcoinNetwork bitcoinNetwork; private Preferences preferences;
@Inject @Inject
public BtcAddressValidator(BitcoinNetwork bitcoinNetwork) { public BtcAddressValidator(Preferences preferences) {
this.bitcoinNetwork = bitcoinNetwork; this.preferences = preferences;
} }
@Override @Override
@ -45,7 +45,7 @@ public final class BtcAddressValidator extends InputValidator {
private ValidationResult validateBtcAddress(String input) { private ValidationResult validateBtcAddress(String input) {
try { try {
new Address(bitcoinNetwork.getParameters(), input); new Address(preferences.getBitcoinNetwork().getParameters(), input);
return new ValidationResult(true); return new ValidationResult(true);
} catch (AddressFormatException e) { } catch (AddressFormatException e) {
return new ValidationResult(false, "Bitcoin address is a valid format"); return new ValidationResult(false, "Bitcoin address is a valid format");