move network code to module

This commit is contained in:
Manfred Karrer 2015-10-28 02:11:59 +01:00
parent 105a63847a
commit c6ece486ed
384 changed files with 11571 additions and 21763 deletions

2
.gitignore vendored
View File

@ -19,3 +19,5 @@ build
.project .project
.settings .settings
*.java.hsp *.java.hsp
gui/updatefx

View File

@ -3,88 +3,9 @@
<component name="ProjectCodeStyleSettingsManager"> <component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS"> <option name="PER_PROJECT_SETTINGS">
<value> <value>
<option name="LINE_SEPARATOR" value="&#10;" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="2" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value>
<package name="java.awt" withSubpackages="false" static="false" />
<package name="javafx.scene" withSubpackages="true" static="false" />
<package name="javax.swing" withSubpackages="false" static="false" />
<package name="org.junit.Assert" withSubpackages="true" static="true" />
</value>
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="io.bitsquare" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.bitcoinj" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.google.common" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.google.gson" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.google.inject" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.awt" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.io" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.math" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.net" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.nio" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.security" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.text" withSubpackages="true" static="false" />
<emptyLine />
<package name="java.util" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax.annotation" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax.inject" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax.swing" withSubpackages="true" static="false" />
<emptyLine />
<package name="viewfx" withSubpackages="true" static="false" />
<emptyLine />
<package name="javafx" withSubpackages="true" static="false" />
<emptyLine />
<package name="com.sun.javafx" withSubpackages="true" static="false" />
<emptyLine />
<package name="de.jensd.fx" withSubpackages="true" static="false" />
<emptyLine />
<package name="net.glxn" withSubpackages="true" static="false" />
<emptyLine />
<package name="net.tomp2p" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.controlsfx" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.jetbrains" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.slf4j" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.spongycastle" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
<option name="RIGHT_MARGIN" value="160" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<XML> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>
<codeStyleSettings language="JAVA">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WRAP_LONG_LINES" value="true" />
<option name="FIELD_ANNOTATION_WRAP" value="0" />
</codeStyleSettings>
</value> </value>
</option> </option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" /> <option name="USE_PER_PROJECT_SETTINGS" value="true" />

View File

@ -1,9 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="This file is part of Bitsquare.&#10;&#10;Bitsquare is free software: you can redistribute it and/or modify it&#10;under the terms of the GNU Affero General Public License as published by&#10;the Free Software Foundation, either version 3 of the License, or (at&#10;your option) any later version.&#10;&#10;Bitsquare is distributed in the hope that it will be useful, but WITHOUT&#10;ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or&#10;FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public&#10;License for more details.&#10;&#10;You should have received a copy of the GNU Affero General Public License&#10;along with Bitsquare. If not, see &lt;http://www.gnu.org/licenses/&gt;." />
<option name="keyword" value="GNU Affero General Public License" />
<option name="allowReplaceKeyword" value="" />
<option name="myName" value="Bitsquare Affero GPLv3" />
<option name="myLocal" value="true" />
</copyright>
</component>

View File

@ -1,3 +1,3 @@
<component name="CopyrightManager"> <component name="CopyrightManager">
<settings default="Bitsquare Affero GPLv3" /> <settings default="" />
</component> </component>

View File

@ -1,157 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app.bootstrap;
import io.bitsquare.app.Logging;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import java.util.List;
import java.util.stream.Collectors;
import net.tomp2p.connection.ChannelClientConfiguration;
import net.tomp2p.connection.ChannelServerConfiguration;
import net.tomp2p.dht.PeerBuilderDHT;
import net.tomp2p.nat.PeerBuilderNAT;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMapChangeListener;
import net.tomp2p.peers.PeerStatistic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import org.springframework.core.env.Environment;
public class BootstrapNode {
private static final Logger log = LoggerFactory.getLogger(BootstrapNode.class);
private static Peer peer = null;
private final Environment env;
private boolean noPeersInfoPrinted;
public BootstrapNode(Environment env) {
this.env = env;
}
public void start() {
BootstrapNodes bootstrapNodes = new BootstrapNodes();
int p2pId = env.getProperty(Node.P2P_ID_KEY, Integer.class, Node.REG_TEST_P2P_ID); // use regtest as default
bootstrapNodes.initWithNetworkId(p2pId);
String name = env.getProperty(Node.NAME_KEY, bootstrapNodes.getLocalhostNode().getName());
int port = env.getProperty(Node.PORT_KEY, Integer.class, bootstrapNodes.getLocalhostNode().getPort());
Logging.setup(name + "_" + port);
try {
Number160 peerId = Number160.createHash(name);
DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(50);
ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration();
clientConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup));
ChannelServerConfiguration serverConf = PeerBuilder.createDefaultChannelServerConfiguration();
serverConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup));
serverConf.connectionTimeoutTCPMillis(5000);
peer = new PeerBuilder(peerId)
.ports(port)
.p2pId(p2pId)
.channelClientConfiguration(clientConf)
.channelServerConfiguration(serverConf)
.start();
/*peer.objectDataReply((sender, request) -> {
log.trace("received request: " + request.toString());
return "pong";
});*/
new PeerBuilderDHT(peer).start();
new PeerBuilderNAT(peer).start();
final int _port = port;
if (!name.equals(bootstrapNodes.getLocalhostNode().getName())) {
List<Node> bootstrapNodesExcludingMyself = bootstrapNodes.getBootstrapNodes().stream().filter(e -> !e.getName().equals
(name)).collect(Collectors.toList());
log.info("Bootstrapping to bootstrapNodes " + bootstrapNodesExcludingMyself);
long ts = System.currentTimeMillis();
List<PeerAddress> bootstrapAddressesExcludingMyself = bootstrapNodesExcludingMyself.stream()
.map(e -> e.toPeerAddressWithPort(_port)).collect(Collectors.toList());
peer.bootstrap().bootstrapTo(bootstrapAddressesExcludingMyself).start().awaitUninterruptibly();
log.info("Bootstrapping done after {} msec", System.currentTimeMillis() - ts);
}
else {
log.info("When using localhost we do not bootstrap to other nodes");
}
peer.peerBean().peerMap().addPeerMapChangeListener(new PeerMapChangeListener() {
@Override
public void peerInserted(PeerAddress peerAddress, boolean verified) {
log.info("Peer inserted: peerAddress=" + peerAddress + ", verified=" + verified);
}
@Override
public void peerRemoved(PeerAddress peerAddress, PeerStatistic peerStatistics) {
log.info("Peer removed: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
}
@Override
public void peerUpdated(PeerAddress peerAddress, PeerStatistic peerStatistics) {
//log.info("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
}
});
log.info("Bootstrap node started with name=" + name + " ,p2pId=" + p2pId + " ,port=" + port +
" and network protocol version=" + Version.NETWORK_PROTOCOL_VERSION);
new Thread(() -> {
while (true) {
if (peer.peerBean().peerMap().all().size() > 0) {
noPeersInfoPrinted = false;
int relayed = 0;
for (PeerAddress peerAddress : peer.peerBean().peerMap().all()) {
log.info("Peer: " + peerAddress.toString());
if (peerAddress.isRelayed())
relayed++;
}
log.info("Number of peers online = " + peer.peerBean().peerMap().all().size());
log.info("Relayed peers = " + relayed);
}
else if (noPeersInfoPrinted) {
log.info("No peers online");
noPeersInfoPrinted = true;
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}).start();
} catch (Throwable t) {
log.error("Fatal exception " + t.getMessage());
if (peer != null)
peer.shutdown().awaitUninterruptibly();
}
}
}

View File

@ -1,52 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app.bootstrap;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.BitsquareExecutable;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
public class BootstrapNodeMain extends BitsquareExecutable {
public static void main(String[] args) throws Exception {
new BootstrapNodeMain().execute(args);
}
protected void customizeOptionParsing(OptionParser parser) {
BootstrapNodes bootstrapNodes = new BootstrapNodes();
bootstrapNodes.initWithNetworkId(Node.REG_TEST_P2P_ID); // use regtest as default
parser.accepts(Node.NAME_KEY, description("Name of this node", bootstrapNodes.getLocalhostNode().getName()))
.withRequiredArg()
.ofType(String.class);
parser.accepts(Node.P2P_ID_KEY, description("P2P network ID",
bootstrapNodes.getLocalhostNode().getP2pId()))
.withRequiredArg()
.ofType(int.class);
parser.accepts(Node.PORT_KEY, description("Port to listen on", bootstrapNodes.getLocalhostNode().getPort()))
.withRequiredArg()
.ofType(int.class);
}
protected void doExecute(OptionSet options) {
new BootstrapNode(new BitsquareEnvironment(options)).start();
}
}

View File

@ -23,8 +23,10 @@
</encoder> </encoder>
</appender> </appender>
<root level="INFO"> <root level="TRACE">
<appender-ref ref="CONSOLE_APPENDER"/> <appender-ref ref="CONSOLE_APPENDER"/>
</root> </root>
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="WARN"/>
</configuration> </configuration>

View File

@ -17,18 +17,14 @@
package io.bitsquare.app; package io.bitsquare.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy; import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
import org.slf4j.LoggerFactory;
public class Logging { public class Logging {
private static final Logger log = LoggerFactory.getLogger(Logging.class);
public static void setup(String fileName) { public static void setup(String fileName) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();

View File

@ -15,15 +15,11 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.bitsquare.crypto; package io.bitsquare.common.crypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CryptoException extends Exception { public class CryptoException extends Exception {
private static final Logger log = LoggerFactory.getLogger(CryptoException.class);
public CryptoException() { private CryptoException() {
} }
public CryptoException(String message) { public CryptoException(String message) {
@ -38,7 +34,7 @@ public class CryptoException extends Exception {
super(cause); super(cause);
} }
public CryptoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { private CryptoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace); super(message, cause, enableSuppression, writableStackTrace);
} }
} }

View File

@ -0,0 +1,136 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.crypto;
import io.bitsquare.common.util.Utilities;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class CryptoUtil {
private static final Logger log = LoggerFactory.getLogger(CryptoUtil.class);
public static final String STORAGE_SIGN_KEY_ALGO = "DSA";
public static final String MSG_SIGN_KEY_ALGO = "DSA";
public static final String MSG_ENCR_KEY_ALGO = "RSA";
public static final String SYM_ENCR_KEY_ALGO = "AES";
public static final String SYM_CIPHER = "AES";
public static final String ASYM_CIPHER = "RSA"; //RSA/ECB/PKCS1Padding
public static final String MSG_SIGN_ALGO = "SHA1withDSA";
public static KeyPair generateStorageSignatureKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(STORAGE_SIGN_KEY_ALGO);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.trace("Generate storageSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
public static KeyPair generateMsgSignatureKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(MSG_SIGN_KEY_ALGO);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.trace("Generate msgSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
public static KeyPair generateMsgEncryptionKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(MSG_ENCR_KEY_ALGO);
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.trace("Generate msgEncryptionKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
static {
Security.addProvider(new BouncyCastleProvider());
}
public static String signMessage(PrivateKey privateKey, String message)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature sig = Signature.getInstance(MSG_SIGN_ALGO);
sig.initSign(privateKey);
sig.update(message.getBytes());
return Base64.toBase64String(sig.sign());
}
public static boolean verifyMessage(PublicKey publicKey, String message, String signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature sig = Signature.getInstance(MSG_SIGN_ALGO);
sig.initVerify(publicKey);
sig.update(message.getBytes());
return sig.verify(Base64.decode(signature));
}
public static byte[] signStorageData(PrivateKey privateKey, byte[] data)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature sig = Signature.getInstance(MSG_SIGN_ALGO);
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
public static boolean verifyStorageData(PublicKey publicKey, byte[] data, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature sig = Signature.getInstance(MSG_SIGN_ALGO);
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
public static byte[] getHash(Integer data) {
return Sha256Hash.hash(ByteBuffer.allocate(4).putInt(data).array());
}
public static byte[] getHash(Object data) {
return Sha256Hash.hash(Utilities.objectToByteArray(data));
}
public static byte[] getHash(String message) {
return Sha256Hash.hash(Utils.formatMessageForSigning(message));
}
public static String getHashAsHex(String text) {
return Utils.HEX.encode(Sha256Hash.hash(Utils.formatMessageForSigning(text)));
}
public static String pubKeyToString(PublicKey publicKey) {
final X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
return java.util.Base64.getEncoder().encodeToString(x509EncodedKeySpec.getEncoded());
}
// TODO just temp for arbitrator
public static PublicKey decodeDSAPubKeyHex(String pubKeyHex) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Utils.HEX.decode(pubKeyHex));
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
return keyFactory.generatePublic(pubKeySpec);
}
}

View File

@ -15,21 +15,20 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.bitsquare.crypto; package io.bitsquare.common.crypto;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import javax.inject.Inject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
public class KeyRing { public class KeyRing {
private static final Logger log = LoggerFactory.getLogger(KeyRing.class); private static final Logger log = LoggerFactory.getLogger(KeyRing.class);
// Public key is used as ID in DHT network. Used for data protection mechanism in TomP2P DHT // Used for signing storage data
private KeyPair dhtSignatureKeyPair; private KeyPair storageSignatureKeyPair;
// Used for signing messages sent over the wire // Used for signing messages sent over the wire
private KeyPair msgSignatureKeyPair; private KeyPair msgSignatureKeyPair;
// Used for encrypting messages sent over the wire (hybrid encryption scheme is used, so it is used only to encrypt a symmetric session key) // Used for encrypting messages sent over the wire (hybrid encryption scheme is used, so it is used only to encrypt a symmetric session key)
@ -46,18 +45,17 @@ public class KeyRing {
} }
// consider extra thread for loading (takes about 264 ms at first startup, then load only takes nearly no time) // consider extra thread for loading (takes about 264 ms at first startup, then load only takes nearly no time)
public void init(KeyStorage keyStorage) throws CryptoException { private void init(KeyStorage keyStorage) throws CryptoException {
if (keyStorage.allKeyFilesExist()) { if (keyStorage.allKeyFilesExist()) {
dhtSignatureKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.DHT_SIGNATURE); storageSignatureKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.STORAGE_SIGNATURE);
msgSignatureKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.MSG_SIGNATURE); msgSignatureKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.MSG_SIGNATURE);
msgEncryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.MSG_ENCRYPTION); msgEncryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.Key.MSG_ENCRYPTION);
} } else {
else {
// First time we create key pairs // First time we create key pairs
try { try {
this.dhtSignatureKeyPair = CryptoService.generateDhtSignatureKeyPair(); this.storageSignatureKeyPair = CryptoUtil.generateStorageSignatureKeyPair();
this.msgSignatureKeyPair = CryptoService.generateMsgSignatureKeyPair(); this.msgSignatureKeyPair = CryptoUtil.generateMsgSignatureKeyPair();
this.msgEncryptionKeyPair = CryptoService.generateMsgEncryptionKeyPair(); this.msgEncryptionKeyPair = CryptoUtil.generateMsgEncryptionKeyPair();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
throw new CryptoException("Error at KeyRing constructor ", e); throw new CryptoException("Error at KeyRing constructor ", e);
@ -65,28 +63,28 @@ public class KeyRing {
keyStorage.saveKeyRing(this); keyStorage.saveKeyRing(this);
} }
pubKeyRing = new PubKeyRing(dhtSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic()); pubKeyRing = new PubKeyRing(storageSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic());
} }
// For unit testing // For unit testing
KeyRing() throws NoSuchAlgorithmException { KeyRing() throws NoSuchAlgorithmException {
keyStorage = null; keyStorage = null;
this.dhtSignatureKeyPair = CryptoService.generateDhtSignatureKeyPair(); this.storageSignatureKeyPair = CryptoUtil.generateStorageSignatureKeyPair();
this.msgSignatureKeyPair = CryptoService.generateMsgSignatureKeyPair(); this.msgSignatureKeyPair = CryptoUtil.generateMsgSignatureKeyPair();
this.msgEncryptionKeyPair = CryptoService.generateMsgEncryptionKeyPair(); this.msgEncryptionKeyPair = CryptoUtil.generateMsgEncryptionKeyPair();
pubKeyRing = new PubKeyRing(dhtSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic()); pubKeyRing = new PubKeyRing(storageSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic());
} }
KeyRing(KeyPair dhtSignatureKeyPair, KeyPair msgSignatureKeyPair, KeyPair msgEncryptionKeyPair) { KeyRing(KeyPair storageSignatureKeyPair, KeyPair msgSignatureKeyPair, KeyPair msgEncryptionKeyPair) {
this.keyStorage = null; this.keyStorage = null;
this.dhtSignatureKeyPair = dhtSignatureKeyPair; this.storageSignatureKeyPair = storageSignatureKeyPair;
this.msgSignatureKeyPair = msgSignatureKeyPair; this.msgSignatureKeyPair = msgSignatureKeyPair;
this.msgEncryptionKeyPair = msgEncryptionKeyPair; this.msgEncryptionKeyPair = msgEncryptionKeyPair;
pubKeyRing = new PubKeyRing(dhtSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic()); pubKeyRing = new PubKeyRing(storageSignatureKeyPair.getPublic(), msgSignatureKeyPair.getPublic(), msgEncryptionKeyPair.getPublic());
} }
public KeyPair getDhtSignatureKeyPair() { public KeyPair getStorageSignatureKeyPair() {
return dhtSignatureKeyPair; return storageSignatureKeyPair;
} }
public KeyPair getMsgSignatureKeyPair() { public KeyPair getMsgSignatureKeyPair() {

View File

@ -15,34 +15,25 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.bitsquare.crypto; package io.bitsquare.common.crypto;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.security.*;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class KeyStorage { public class KeyStorage {
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class); private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
@ -52,11 +43,10 @@ public class KeyStorage {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
} }
public enum Key { public enum Key {
DHT_SIGNATURE("dhtSignature", CryptoService.DHT_SIGN_KEY_ALGO), STORAGE_SIGNATURE("storageSignature", CryptoUtil.STORAGE_SIGN_KEY_ALGO),
MSG_SIGNATURE("msgSignature", CryptoService.MSG_SIGN_KEY_ALGO), MSG_SIGNATURE("msgSignature", CryptoUtil.MSG_SIGN_KEY_ALGO),
MSG_ENCRYPTION("msgEncryption", CryptoService.MSG_ENCR_KEY_ALGO); MSG_ENCRYPTION("msgEncryption", CryptoUtil.MSG_ENCR_KEY_ALGO);
private final String fileName; private final String fileName;
private final String algorithm; private final String algorithm;
@ -74,6 +64,7 @@ public class KeyStorage {
return algorithm; return algorithm;
} }
@NotNull
@Override @Override
public String toString() { public String toString() {
return "Key{" + return "Key{" +
@ -91,15 +82,15 @@ public class KeyStorage {
} }
public boolean allKeyFilesExist() throws CryptoException { public boolean allKeyFilesExist() throws CryptoException {
return fileExists(KeyStorage.Key.DHT_SIGNATURE) && fileExists(KeyStorage.Key.MSG_SIGNATURE) && fileExists(KeyStorage.Key.MSG_ENCRYPTION); return fileExists(KeyStorage.Key.STORAGE_SIGNATURE) && fileExists(KeyStorage.Key.MSG_SIGNATURE) && fileExists(KeyStorage.Key.MSG_ENCRYPTION);
} }
private boolean fileExists(Key key) throws CryptoException { private boolean fileExists(Key key) {
return new File(storageDir + "/" + key.getFileName() + "Pub.key").exists(); return new File(storageDir + "/" + key.getFileName() + "Pub.key").exists();
} }
public KeyPair loadKeyPair(Key key) throws CryptoException { public KeyPair loadKeyPair(Key key) throws CryptoException {
long now = System.currentTimeMillis(); // long now = System.currentTimeMillis();
try { try {
KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm());
PublicKey publicKey; PublicKey publicKey;
@ -130,7 +121,7 @@ public class KeyStorage {
log.error(e.getMessage()); log.error(e.getMessage());
throw new CryptoException("Could not load key " + key.toString(), e); throw new CryptoException("Could not load key " + key.toString(), e);
} }
log.info("load completed in {} msec", System.currentTimeMillis() - now); //log.info("load completed in {} msec", System.currentTimeMillis() - now);
return new KeyPair(publicKey, privateKey); return new KeyPair(publicKey, privateKey);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
@ -141,7 +132,7 @@ public class KeyStorage {
} }
public void saveKeyRing(KeyRing keyRing) throws CryptoException { public void saveKeyRing(KeyRing keyRing) throws CryptoException {
saveKeyPair(keyRing.getDhtSignatureKeyPair(), Key.DHT_SIGNATURE.getFileName()); saveKeyPair(keyRing.getStorageSignatureKeyPair(), Key.STORAGE_SIGNATURE.getFileName());
saveKeyPair(keyRing.getMsgSignatureKeyPair(), Key.MSG_SIGNATURE.getFileName()); saveKeyPair(keyRing.getMsgSignatureKeyPair(), Key.MSG_SIGNATURE.getFileName());
saveKeyPair(keyRing.getMsgEncryptionKeyPair(), Key.MSG_ENCRYPTION.getFileName()); saveKeyPair(keyRing.getMsgEncryptionKeyPair(), Key.MSG_ENCRYPTION.getFileName());
} }

View File

@ -15,24 +15,20 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.bitsquare.crypto; package io.bitsquare.common.crypto;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable; import java.io.Serializable;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Same as KeyRing but with public keys only. * Same as KeyRing but with public keys only.
* Used to sent over the wire to other peer. * Used to sent over the wire to other peer.
@ -41,43 +37,43 @@ public class PubKeyRing implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility. // That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
private static final Logger log = LoggerFactory.getLogger(PubKeyRing.class); transient private static final Logger log = LoggerFactory.getLogger(PubKeyRing.class);
private final byte[] dhtSignaturePubKeyBytes; private final byte[] storageSignaturePubKeyBytes;
private final byte[] msgSignaturePubKeyBytes; private final byte[] msgSignaturePubKeyBytes;
private final byte[] msgEncryptionPubKeyBytes; private final byte[] msgEncryptionPubKeyBytes;
transient private PublicKey dhtSignaturePubKey; transient private PublicKey storageSignaturePubKey;
transient private PublicKey msgSignaturePubKey; transient private PublicKey msgSignaturePubKey;
transient private PublicKey msgEncryptionPubKey; transient private PublicKey msgEncryptionPubKey;
public PubKeyRing(PublicKey dhtSignaturePubKey, PublicKey msgSignaturePubKey, PublicKey msgEncryptionPubKey) { public PubKeyRing(PublicKey storageSignaturePubKey, PublicKey msgSignaturePubKey, PublicKey msgEncryptionPubKey) {
this.dhtSignaturePubKey = dhtSignaturePubKey; this.storageSignaturePubKey = storageSignaturePubKey;
this.msgSignaturePubKey = msgSignaturePubKey; this.msgSignaturePubKey = msgSignaturePubKey;
this.msgEncryptionPubKey = msgEncryptionPubKey; this.msgEncryptionPubKey = msgEncryptionPubKey;
this.dhtSignaturePubKeyBytes = new X509EncodedKeySpec(dhtSignaturePubKey.getEncoded()).getEncoded(); this.storageSignaturePubKeyBytes = new X509EncodedKeySpec(storageSignaturePubKey.getEncoded()).getEncoded();
this.msgSignaturePubKeyBytes = new X509EncodedKeySpec(msgSignaturePubKey.getEncoded()).getEncoded(); this.msgSignaturePubKeyBytes = new X509EncodedKeySpec(msgSignaturePubKey.getEncoded()).getEncoded();
this.msgEncryptionPubKeyBytes = new X509EncodedKeySpec(msgEncryptionPubKey.getEncoded()).getEncoded(); this.msgEncryptionPubKeyBytes = new X509EncodedKeySpec(msgEncryptionPubKey.getEncoded()).getEncoded();
} }
public PublicKey getDhtSignaturePubKey() { public PublicKey getStorageSignaturePubKey() {
if (dhtSignaturePubKey == null) { if (storageSignaturePubKey == null) {
try { try {
dhtSignaturePubKey = KeyFactory.getInstance(CryptoService.DHT_SIGN_KEY_ALGO).generatePublic(new X509EncodedKeySpec(dhtSignaturePubKeyBytes)); storageSignaturePubKey = KeyFactory.getInstance(CryptoUtil.STORAGE_SIGN_KEY_ALGO).generatePublic(new X509EncodedKeySpec(storageSignaturePubKeyBytes));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) { } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
} }
} }
return dhtSignaturePubKey; return storageSignaturePubKey;
} }
public PublicKey getMsgSignaturePubKey() { public PublicKey getMsgSignaturePubKey() {
if (msgSignaturePubKey == null) { if (msgSignaturePubKey == null) {
try { try {
msgSignaturePubKey = KeyFactory.getInstance(CryptoService.MSG_SIGN_KEY_ALGO).generatePublic(new X509EncodedKeySpec(msgSignaturePubKeyBytes)); msgSignaturePubKey = KeyFactory.getInstance(CryptoUtil.MSG_SIGN_KEY_ALGO).generatePublic(new X509EncodedKeySpec(msgSignaturePubKeyBytes));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) { } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
@ -89,7 +85,7 @@ public class PubKeyRing implements Serializable {
public PublicKey getMsgEncryptionPubKey() { public PublicKey getMsgEncryptionPubKey() {
if (msgEncryptionPubKey == null) { if (msgEncryptionPubKey == null) {
try { try {
msgEncryptionPubKey = KeyFactory.getInstance(CryptoService.MSG_ENCR_KEY_ALGO).generatePublic(new X509EncodedKeySpec(msgEncryptionPubKeyBytes)); msgEncryptionPubKey = KeyFactory.getInstance(CryptoUtil.MSG_ENCR_KEY_ALGO).generatePublic(new X509EncodedKeySpec(msgEncryptionPubKeyBytes));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) { } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
@ -105,7 +101,7 @@ public class PubKeyRing implements Serializable {
PubKeyRing that = (PubKeyRing) o; PubKeyRing that = (PubKeyRing) o;
if (!Arrays.equals(dhtSignaturePubKeyBytes, that.dhtSignaturePubKeyBytes)) return false; if (!Arrays.equals(storageSignaturePubKeyBytes, that.storageSignaturePubKeyBytes)) return false;
if (!Arrays.equals(msgSignaturePubKeyBytes, that.msgSignaturePubKeyBytes)) return false; if (!Arrays.equals(msgSignaturePubKeyBytes, that.msgSignaturePubKeyBytes)) return false;
return Arrays.equals(msgEncryptionPubKeyBytes, that.msgEncryptionPubKeyBytes); return Arrays.equals(msgEncryptionPubKeyBytes, that.msgEncryptionPubKeyBytes);
@ -113,31 +109,18 @@ public class PubKeyRing implements Serializable {
@Override @Override
public int hashCode() { public int hashCode() {
int result = dhtSignaturePubKeyBytes != null ? Arrays.hashCode(dhtSignaturePubKeyBytes) : 0; int result = storageSignaturePubKeyBytes != null ? Arrays.hashCode(storageSignaturePubKeyBytes) : 0;
result = 31 * result + (msgSignaturePubKeyBytes != null ? Arrays.hashCode(msgSignaturePubKeyBytes) : 0); result = 31 * result + (msgSignaturePubKeyBytes != null ? Arrays.hashCode(msgSignaturePubKeyBytes) : 0);
result = 31 * result + (msgEncryptionPubKeyBytes != null ? Arrays.hashCode(msgEncryptionPubKeyBytes) : 0); result = 31 * result + (msgEncryptionPubKeyBytes != null ? Arrays.hashCode(msgEncryptionPubKeyBytes) : 0);
return result; return result;
} }
public String getHashString() {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(dhtSignaturePubKeyBytes);
messageDigest.update(msgSignaturePubKeyBytes);
messageDigest.update(msgEncryptionPubKeyBytes);
return new String(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Hash Algorithm not found.", e);
}
}
@Override @Override
public String toString() { public String toString() {
return "PubKeyRing{" + return "PubKeyRing{" +
"\ndhtSignaturePubKey=\n" + Util.pubKeyToString(getDhtSignaturePubKey()) + "\nstorageSignaturePubKey=\n" + CryptoUtil.pubKeyToString(getStorageSignaturePubKey()) +
"\n\nmsgSignaturePubKey=\n" + Util.pubKeyToString(getMsgSignaturePubKey()) + "\n\nmsgSignaturePubKey=\n" + CryptoUtil.pubKeyToString(getMsgSignaturePubKey()) +
"\n\nmsgEncryptionPubKey=\n" + Util.pubKeyToString(getMsgEncryptionPubKey()) + "\n\nmsgEncryptionPubKey=\n" + CryptoUtil.pubKeyToString(getMsgEncryptionPubKey()) +
'}'; '}';
} }

View File

@ -1,12 +1,9 @@
package io.bitsquare.util; package io.bitsquare.common.util;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -154,8 +151,7 @@ class DesktopUtil {
if (retval == 0) { if (retval == 0) {
logErr("Process ended immediately."); logErr("Process ended immediately.");
return false; return false;
} } else {
else {
logErr("Process crashed."); logErr("Process crashed.");
return false; return false;
} }
@ -221,7 +217,7 @@ class DesktopUtil {
} }
public static EnumOS getOs() { private static EnumOS getOs() {
String s = System.getProperty("os.name").toLowerCase(); String s = System.getProperty("os.name").toLowerCase();
@ -247,8 +243,7 @@ class DesktopUtil {
if (s.contains("unix")) { if (s.contains("unix")) {
return EnumOS.linux; return EnumOS.linux;
} } else {
else {
return EnumOS.unknown; return EnumOS.unknown;
} }
} }

View File

@ -15,43 +15,24 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.bitsquare.util; package io.bitsquare.common.util;
import io.bitsquare.common.handlers.ResultHandler;
import org.bitcoinj.utils.Threading;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import com.google.gson.*;
import com.google.gson.FieldNamingPolicy; import javafx.scene.input.Clipboard;
import com.google.gson.Gson; import javafx.scene.input.ClipboardContent;
import com.google.gson.GsonBuilder; import javafx.scene.web.WebEngine;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URLConnection;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.awt.*;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.net.URLEncoder;
/** /**
* General utilities * General utilities
@ -60,36 +41,20 @@ public class Utilities {
private static final Logger log = LoggerFactory.getLogger(Utilities.class); private static final Logger log = LoggerFactory.getLogger(Utilities.class);
private static long lastTimeStamp = System.currentTimeMillis(); private static long lastTimeStamp = System.currentTimeMillis();
public static Timer setTimeout(long delay, ResultHandler handler) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
Threading.USER_THREAD.execute(() -> handler.handleResult());
}
};
timer.schedule(task, delay);
return timer;
}
public static Timer setInterval(long delay, ResultHandler handler) {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
Threading.USER_THREAD.execute(() -> handler.handleResult());
}
};
timer.scheduleAtFixedRate(task, delay, delay);
return timer;
}
public static String objectToJson(Object object) { public static String objectToJson(Object object) {
Gson gson = Gson gson = new GsonBuilder()
new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).setPrettyPrinting().create(); .setExclusionStrategies(new AnnotationExclusionStrategy())
/*.excludeFieldsWithModifiers(Modifier.TRANSIENT)*/
/* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)*/
.setPrettyPrinting()
.create();
return gson.toJson(object); return gson.toJson(object);
} }
public static boolean isUnix() {
return isOSX() || isLinux() || getOSName().contains("freebsd");
}
public static boolean isWindows() { public static boolean isWindows() {
return getOSName().contains("win"); return getOSName().contains("win");
} }
@ -98,7 +63,7 @@ public class Utilities {
return getOSName().contains("mac") || getOSName().contains("darwin"); return getOSName().contains("mac") || getOSName().contains("darwin");
} }
public static boolean isLinux() { private static boolean isLinux() {
return getOSName().contains("linux"); return getOSName().contains("linux");
} }
@ -111,8 +76,11 @@ public class Utilities {
&& Desktop.isDesktopSupported() && Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(uri); Desktop.getDesktop().browse(uri);
} } else {
else { // Maybe Application.HostServices works in those cases?
// HostServices hostServices = getHostServices();
// hostServices.showDocument(uri.toString());
// On Linux Desktop is poorly implemented. // On Linux Desktop is poorly implemented.
// See https://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform // See https://stackoverflow.com/questions/18004150/desktop-api-is-not-supported-on-the-current-platform
if (!DesktopUtil.browse(uri)) if (!DesktopUtil.browse(uri))
@ -120,21 +88,76 @@ public class Utilities {
} }
} }
public static void openWebPage(String target) throws Exception { public static void openWebPage(String target) {
openURI(new URI(target)); try {
openURI(new URI(target));
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
public static void printSystemLoad() {
Runtime runtime = Runtime.getRuntime();
long free = runtime.freeMemory() / 1024 / 1024;
long total = runtime.totalMemory() / 1024 / 1024;
long used = total - free;
log.info("System load (nr. threads/used memory (MB)): " + Thread.activeCount() + "/" + used);
}
// Opens links with http and _blank in default web browser instead of webView
// WebView has not feature to open link in default browser, so we use the hack recommended here:
// https://stackoverflow.com/questions/15555510/javafx-stop-opening-url-in-webview-open-in-browser-instead
public static void setupWebViewPopupHandler(WebEngine webEngine) {
webEngine.setCreatePopupHandler(
config -> {
// grab the last hyperlink that has :hover pseudoclass
Object result = webEngine
.executeScript(
"var list = document.querySelectorAll( ':hover' );"
+ "for (i=list.length-1; i>-1; i--) "
+ "{ if ( list.item(i).getAttribute('href') ) "
+ "{ list.item(i).getAttribute('href'); break; } }");
if (result instanceof String && ((String) result).contains("http")) {
openWebPage((String) result);
return null;
} else {
return webEngine;
}
});
}
public static void openMail(String to, String subject, String body) {
try {
subject = URLEncoder.encode(subject, "UTF-8").replace("+", "%20");
body = URLEncoder.encode(body, "UTF-8").replace("+", "%20");
Desktop.getDesktop().mail(new URI("mailto:" + to + "?subject=" + subject + "&body=" + body));
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
}
}
public static void copyToClipboard(String content) {
if (content != null && content.length() > 0) {
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(content);
clipboard.setContent(clipboardContent);
}
} }
public static byte[] concatByteArrays(byte[]... arrays) { public static byte[] concatByteArrays(byte[]... arrays) {
int totalLength = 0; int totalLength = 0;
for (int i = 0; i < arrays.length; i++) { for (byte[] array : arrays) {
totalLength += arrays[i].length; totalLength += array.length;
} }
byte[] result = new byte[totalLength]; byte[] result = new byte[totalLength];
int currentIndex = 0; int currentIndex = 0;
for (int i = 0; i < arrays.length; i++) { for (byte[] array : arrays) {
System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length); System.arraycopy(array, 0, result, currentIndex, array.length);
currentIndex += arrays[i].length; currentIndex += array.length;
} }
return result; return result;
} }
@ -146,7 +169,7 @@ public class Utilities {
} }
public static Object deserializeHexStringToObject(String serializedHexString) { /* public static Object deserializeHexStringToObject(String serializedHexString) {
Object result = null; Object result = null;
try { try {
ByteArrayInputStream byteInputStream = ByteArrayInputStream byteInputStream =
@ -183,7 +206,7 @@ public class Utilities {
e.printStackTrace(); e.printStackTrace();
} }
return result; return result;
} }*/
public static <T> T byteArrayToObject(byte[] data) { public static <T> T byteArrayToObject(byte[] data) {
ByteArrayInputStream bis = new ByteArrayInputStream(data); ByteArrayInputStream bis = new ByteArrayInputStream(data);
@ -211,7 +234,7 @@ public class Utilities {
return (T) result; return (T) result;
} }
public static byte[] objectToBytArray(Object object) { public static byte[] objectToByteArray(Object object) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null; ObjectOutput out = null;
byte[] result = null; byte[] result = null;
@ -286,7 +309,7 @@ public class Utilities {
* *
* @param folder folder to empty * @param folder folder to empty
*/ */
public static void removeDirectory(final File folder) { private static void removeDirectory(final File folder) {
// check if folder file is a real folder // check if folder file is a real folder
if (folder.isDirectory()) { if (folder.isDirectory()) {
File[] list = folder.listFiles(); File[] list = folder.listFiles();
@ -311,11 +334,23 @@ public class Utilities {
connection.setConnectTimeout(10 * 1000); connection.setConnectTimeout(10 * 1000);
connection.addRequestProperty("User-Agent", userAgent); connection.addRequestProperty("User-Agent", userAgent);
connection.connect(); connection.connect();
try (InputStream inputStream = connection.getInputStream();) { try (InputStream inputStream = connection.getInputStream()) {
return CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); return CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
throw e; throw e;
} }
} }
private static class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(JsonExclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
} }

View File

@ -38,50 +38,46 @@
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>io.bitsquare</groupId>
<artifactId>common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>io.bitsquare</groupId>
<artifactId>network</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.msopentech.thali</groupId>
<artifactId>universal</artifactId>
<version>0.0.3-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.msopentech.thali</groupId>
<artifactId>java</artifactId>
<version>0.0.3-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.bitcoinj</groupId> <groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId> <artifactId>bitcoinj-core</artifactId>
<version>0.13.d13665c-SNAPSHOT</version> <version>0.13.2</version>
</dependency> </dependency>
<dependency>
<groupId>net.tomp2p</groupId>
<artifactId>tomp2p-all</artifactId>
<version>5.0-Beta7</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.52</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<dependency> <dependency>
<groupId>net.sf.jopt-simple</groupId> <groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId> <artifactId>jopt-simple</artifactId>
@ -93,27 +89,6 @@
<artifactId>qrgen</artifactId> <artifactId>qrgen</artifactId>
<version>1.3</version> <version>1.3</version>
</dependency> </dependency>
<!-- <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>2.0.1</version>
</dependency>-->
<!-- <dependency>
<groupId>net.jcip</groupId>
<artifactId>jcip-annotations</artifactId>
<version>1.0</version>
</dependency>-->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
</dependency>
<!-- <dependency>
<groupId>org.fxmisc.easybind</groupId>
<artifactId>easybind</artifactId>
<version>1.0.2</version>
</dependency>-->
<dependency> <dependency>
<groupId>com.codahale.metrics</groupId> <groupId>com.codahale.metrics</groupId>
@ -121,17 +96,16 @@
<version>3.0.2</version> <version>3.0.2</version>
</dependency> </dependency>
<dependency> <!-- <dependency>
<groupId>ch.qos.logback</groupId> <groupId>com.google.code.findbugs</groupId>
<artifactId>logback-core</artifactId> <artifactId>jsr305</artifactId>
<version>1.1.2</version> <version>2.0.1</version>
</dependency> </dependency>-->
<dependency> <!-- <dependency>
<groupId>ch.qos.logback</groupId> <groupId>net.jcip</groupId>
<artifactId>logback-classic</artifactId> <artifactId>jcip-annotations</artifactId>
<version>1.1.2</version> <version>1.0</version>
</dependency> </dependency>-->
</dependencies> </dependencies>
</project> </project>

View File

@ -1,71 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class BitsquareModule extends AbstractModule {
private static final Logger log = LoggerFactory.getLogger(BitsquareModule.class);
protected final Environment env;
private final List<BitsquareModule> modules = new ArrayList<>();
protected BitsquareModule(Environment env) {
checkNotNull(env, "Environment must not be null");
this.env = env;
}
protected void install(BitsquareModule module) {
super.install(module);
modules.add(module);
}
/**
* Close any instances this module is responsible for and recursively close any
* sub-modules installed via {@link #install(BitsquareModule)}. This method
* must be called manually, e.g. at the end of a main() method or in the stop() method
* of a JavaFX Application; alternatively it may be registered as a JVM shutdown hook.
*
* @param injector the Injector originally initialized with this module
* @see #doClose(com.google.inject.Injector)
*/
public final void close(Injector injector) {
modules.forEach(module -> module.close(injector));
doClose(injector);
}
/**
* Actually perform closing of any instances this module is responsible for. Called by
* {@link #close(Injector)}.
*
* @param injector the Injector originally initialized with this module
*/
protected void doClose(Injector injector) {
}
}

View File

@ -21,24 +21,13 @@ import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork; 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.common.crypto.KeyStorage;
import io.bitsquare.p2p.tomp2p.TomP2PModule; import io.bitsquare.common.util.Utilities;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.util.Utilities;
import io.bitsquare.util.spring.JOptCommandLinePropertySource; import io.bitsquare.util.spring.JOptCommandLinePropertySource;
import joptsimple.OptionSet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Properties;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import joptsimple.OptionSet;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
@ -48,6 +37,12 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.io.support.ResourcePropertySource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Properties;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class BitsquareEnvironment extends StandardEnvironment { public class BitsquareEnvironment extends StandardEnvironment {
@ -79,11 +74,11 @@ public class BitsquareEnvironment extends StandardEnvironment {
private final String userDataDir; private final String userDataDir;
private final String appDataDir; private final String appDataDir;
private final String btcNetworkDir; private final String btcNetworkDir;
private final String bootstrapNodePort;
private BitcoinNetwork bitcoinNetwork; private BitcoinNetwork bitcoinNetwork;
public BitsquareEnvironment(OptionSet options) { public BitsquareEnvironment(OptionSet options) {
this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(options))); this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(
options)));
} }
public BitcoinNetwork getBitcoinNetwork() { public BitcoinNetwork getBitcoinNetwork() {
@ -99,8 +94,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
Object propertiesObject = appDirProperties().getSource(); Object propertiesObject = appDirProperties().getSource();
if (propertiesObject instanceof Properties) { if (propertiesObject instanceof Properties) {
properties = (Properties) propertiesObject; properties = (Properties) propertiesObject;
} } else {
else {
log.warn("propertiesObject not instance of Properties"); log.warn("propertiesObject not instance of Properties");
} }
} }
@ -129,9 +123,6 @@ 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);
bootstrapNodePort = commandLineProperties.containsProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) ?
(String) commandLineProperties.getProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY) : "-1";
MutablePropertySources propertySources = this.getPropertySources(); MutablePropertySources propertySources = this.getPropertySources();
propertySources.addFirst(commandLineProperties); propertySources.addFirst(commandLineProperties);
try { try {
@ -166,7 +157,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
return new ResourcePropertySource(BITSQUARE_APP_DIR_PROPERTY_SOURCE_NAME, resource); return new ResourcePropertySource(BITSQUARE_APP_DIR_PROPERTY_SOURCE_NAME, resource);
} }
PropertySource<?> homeDirProperties() throws Exception { private PropertySource<?> homeDirProperties() throws Exception {
String location = String.format("file:%s/.bitsquare/bitsquare.properties", getProperty("user.home")); String location = String.format("file:%s/.bitsquare/bitsquare.properties", getProperty("user.home"));
Resource resource = resourceLoader.getResource(location); Resource resource = resourceLoader.getResource(location);
@ -176,12 +167,12 @@ public class BitsquareEnvironment extends StandardEnvironment {
return new ResourcePropertySource(BITSQUARE_HOME_DIR_PROPERTY_SOURCE_NAME, resource); return new ResourcePropertySource(BITSQUARE_HOME_DIR_PROPERTY_SOURCE_NAME, resource);
} }
PropertySource<?> classpathProperties() throws Exception { private PropertySource<?> classpathProperties() throws Exception {
Resource resource = resourceLoader.getResource("classpath:bitsquare.properties"); Resource resource = resourceLoader.getResource("classpath:bitsquare.properties");
return new ResourcePropertySource(BITSQUARE_CLASSPATH_PROPERTY_SOURCE_NAME, resource); return new ResourcePropertySource(BITSQUARE_CLASSPATH_PROPERTY_SOURCE_NAME, resource);
} }
protected PropertySource<?> defaultProperties() { private PropertySource<?> defaultProperties() {
return new PropertiesPropertySource(BITSQUARE_DEFAULT_PROPERTY_SOURCE_NAME, new Properties() { return new PropertiesPropertySource(BITSQUARE_DEFAULT_PROPERTY_SOURCE_NAME, new Properties() {
private static final long serialVersionUID = -8478089705207326165L; private static final long serialVersionUID = -8478089705207326165L;
@ -200,8 +191,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
setProperty(Storage.DIR_KEY, Paths.get(btcNetworkDir, "db").toString()); setProperty(Storage.DIR_KEY, Paths.get(btcNetworkDir, "db").toString());
setProperty(KeyStorage.DIR_KEY, Paths.get(btcNetworkDir, "keys").toString()); setProperty(KeyStorage.DIR_KEY, Paths.get(btcNetworkDir, "keys").toString());
setProperty(ProgramArguments.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
setProperty(TomP2PModule.BOOTSTRAP_NODE_PORT_KEY, bootstrapNodePort);
} }
}); });
} }

View File

@ -22,12 +22,13 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static java.lang.String.*; import static java.lang.String.format;
import static java.lang.String.join;
public abstract class BitsquareExecutable { public abstract class BitsquareExecutable {
public static final int EXIT_SUCCESS = 0; private static final int EXIT_SUCCESS = 0;
public static final int EXIT_FAILURE = 1; public static final int EXIT_FAILURE = 1;
public static final String HELP_KEY = "help"; private static final String HELP_KEY = "help";
public void execute(String[] args) throws Exception { public void execute(String[] args) throws Exception {
OptionParser parser = new OptionParser(); OptionParser parser = new OptionParser();

View File

@ -1,35 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Version {
private static final Logger log = LoggerFactory.getLogger(Version.class);
public static final int MAJOR_VERSION = 0;
public static final int MINOR_VERSION = 3;
public static final int PATCH_VERSION = 1;
public static final String VERSION = MAJOR_VERSION + "." + MINOR_VERSION + "." + PATCH_VERSION;
// If objects are used for both network and database the network version is applied.
public static final long NETWORK_PROTOCOL_VERSION = 1;
public static final long LOCAL_DB_VERSION = 1;
}

View File

@ -1,130 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.arbitration;
import io.bitsquare.app.Version;
import io.bitsquare.crypto.Util;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.storage.Storage;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Utils;
import com.google.inject.Inject;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ArbitrationRepository implements Serializable {
// 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;
transient private static final Logger log = LoggerFactory.getLogger(ArbitrationRepository.class);
transient private final ArbitratorService arbitratorService;
transient private final Arbitrator defaultArbitrator;
transient private final ObservableMap<String, Arbitrator> arbitratorsObservableMap = FXCollections.observableHashMap();
transient private boolean allArbitratorsSynced;
// Persisted fields
private final Map<String, Arbitrator> arbitratorsMap = new HashMap<>();
@Inject
public ArbitrationRepository(Storage<ArbitrationRepository> storage,
Storage<Arbitrator> arbitratorStorage,
ArbitratorService arbitratorService) throws InvalidKeySpecException, NoSuchAlgorithmException {
this.arbitratorService = arbitratorService;
byte[] walletPubKey = Utils.HEX.decode("03a418bf0cb60a35ce217c7f80a2db08a4f5efbe56a0e7602fbc392dea6b63f840");
PublicKey p2pSigPubKey = null;
p2pSigPubKey = Util.decodeDSAPubKeyHex
("308201b83082012c06072a8648ce3804013082011f02818100fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c70215009760508f15230bccb292b982a2eb840bf0581cf502818100f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a0381850002818100db47d4cf76e9bfcc0ba1e98c21c19ba45d1440fa2fec732f664dc8fd63e98877e648aac6db8d1035cd640fe5ff2e0030c2f8694ed124e81bd42c5446a1ce5288d5c8b4073d1cd890fe61ee4527f4e3184279f394cb9c2a4e7924cb2e82320a846cc140304eac6d41d4eaebc4d69b92725715497a82890be9f49d348fda20b095");
this.defaultArbitrator = new Arbitrator(arbitratorStorage,
"default-524f-46c0-b96e-de5a11d3475d",
walletPubKey,
p2pSigPubKey,
"Mr. Default",
new Reputation(),
Arbitrator.ID_TYPE.REAL_LIFE_ID,
Arrays.asList(LanguageUtil.getDefaultLanguageLocaleAsCode()),
Coin.parseCoin("0.1"),
Arrays.asList(Arbitrator.METHOD.TLS_NOTARY),
Arrays.asList(Arbitrator.ID_VERIFICATION.PASSPORT),
"https://bitsquare.io",
"Bla bla...");
ArbitrationRepository persisted = storage.initAndGetPersisted(this);
if (persisted != null) {
arbitratorsMap.putAll(persisted.getArbitratorsMap());
}
arbitratorsMap.put(defaultArbitrator.getId(), defaultArbitrator);
arbitratorsObservableMap.putAll(arbitratorsMap);
arbitratorsObservableMap.addListener((MapChangeListener<String, Arbitrator>) change -> storage.queueUpForSave());
allArbitratorsSynced = false;
}
// Is called when all services are ready
public void loadAllArbitrators() {
log.debug("loadAllArbitrators");
arbitratorService.loadAllArbitrators((Map<String, Arbitrator> arbitratorsMap) -> {
log.debug("Arbitrators successful loaded.");
log.debug("arbitratorsMap.size()=" + arbitratorsMap.size());
ArbitrationRepository.this.arbitratorsMap.clear();
ArbitrationRepository.this.arbitratorsMap.put(defaultArbitrator.getId(), defaultArbitrator);
ArbitrationRepository.this.arbitratorsMap.putAll(arbitratorsMap);
ArbitrationRepository.this.arbitratorsObservableMap.clear();
ArbitrationRepository.this.arbitratorsObservableMap.putAll(ArbitrationRepository.this.arbitratorsMap);
allArbitratorsSynced = true;
},
(log::error));
}
public Map<String, Arbitrator> getArbitratorsMap() {
return arbitratorsMap;
}
public ObservableMap<String, Arbitrator> getArbitratorsObservableMap() {
return arbitratorsObservableMap;
}
public boolean areAllArbitratorsSynced() {
return allArbitratorsSynced;
}
@NotNull
public Arbitrator getDefaultArbitrator() {
return defaultArbitrator;
}
}

View File

@ -18,165 +18,47 @@
package io.bitsquare.arbitration; package io.bitsquare.arbitration;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.storage.Storage; import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.p2p.Address;
import org.bitcoinj.core.Coin; import io.bitsquare.p2p.storage.data.PubKeyProtectedExpirablePayload;
import java.io.Serializable;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
public class Arbitrator implements Serializable { public final class Arbitrator implements PubKeyProtectedExpirablePayload {
// That object is sent over the wire, so we need to take care of version compatibility. // That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public static final long TTL = 10 * 24 * 60 * 60 * 1000; // 10 days
///////////////////////////////////////////////////////////////////////////////////////////
// Enums
///////////////////////////////////////////////////////////////////////////////////////////
public enum ID_TYPE {
REAL_LIFE_ID,
NICKNAME,
COMPANY
}
public enum METHOD {
TLS_NOTARY,
SKYPE_SCREEN_SHARING,
SMART_PHONE_VIDEO_CHAT,
REQUIRE_REAL_ID,
BANK_STATEMENT,
OTHER
}
public enum ID_VERIFICATION {
PASSPORT,
GOV_ID,
UTILITY_BILLS,
FACEBOOK,
GOOGLE_PLUS,
TWITTER,
PGP,
BTC_OTC,
OTHER
}
final transient private Storage<Arbitrator> storage;
// Persisted fields // Persisted fields
private final String id; private final byte[] btcPubKey;
private final byte[] pubKey; private final PubKeyRing pubKeyRing;
private final PublicKey p2pSigPubKey; private final Address arbitratorAddress;
private final String name; private final List<String> languageCodes;
private final Reputation reputation; private final String btcAddress;
private final long registrationDate;
private final String registrationSignature;
private final byte[] registrationPubKey;
// Mutable public Arbitrator(Address arbitratorAddress,
private ID_TYPE idType; byte[] btcPubKey,
private List<String> languageCodes; String btcAddress,
private Coin fee; PubKeyRing pubKeyRing,
private List<METHOD> arbitrationMethods;
private List<ID_VERIFICATION> idVerifications;
private String webUrl;
private String description;
public Arbitrator(Storage<Arbitrator> storage,
String id,
byte[] pubKey,
PublicKey p2pSigPubKey,
String name,
Reputation reputation,
ID_TYPE idType,
List<String> languageCodes, List<String> languageCodes,
Coin fee, Date registrationDate,
List<METHOD> arbitrationMethods, byte[] registrationPubKey,
List<ID_VERIFICATION> idVerifications, String registrationSignature) {
String webUrl, this.arbitratorAddress = arbitratorAddress;
String description) { this.btcPubKey = btcPubKey;
this.storage = storage; this.btcAddress = btcAddress;
this.id = id; this.pubKeyRing = pubKeyRing;
this.pubKey = pubKey;
this.p2pSigPubKey = p2pSigPubKey;
this.name = name;
this.reputation = reputation;
this.idType = idType;
this.languageCodes = languageCodes; this.languageCodes = languageCodes;
this.fee = fee; this.registrationDate = registrationDate.getTime();
this.arbitrationMethods = arbitrationMethods; this.registrationPubKey = registrationPubKey;
this.idVerifications = idVerifications; this.registrationSignature = registrationSignature;
this.webUrl = webUrl;
this.description = description;
}
public void save() {
storage.queueUpForSave();
}
@Override
public int hashCode() {
if (id != null) {
return Objects.hashCode(id);
}
else {
return 0;
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Arbitrator)) {
return false;
}
if (obj == this) {
return true;
}
Arbitrator other = (Arbitrator) obj;
return id != null && id.equals(other.getId());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setDescription(String description) {
this.description = description;
save();
}
public void setIdType(ID_TYPE idType) {
this.idType = idType;
save();
}
public void setLanguageCodes(List<String> languageCodes) {
this.languageCodes = languageCodes;
save();
}
public void setFee(Coin fee) {
this.fee = fee;
save();
}
public void setArbitrationMethods(List<METHOD> arbitrationMethods) {
this.arbitrationMethods = arbitrationMethods;
save();
}
public void setIdVerifications(List<ID_VERIFICATION> idVerifications) {
this.idVerifications = idVerifications;
save();
}
public void setWebUrl(String webUrl) {
this.webUrl = webUrl;
save();
} }
@ -184,51 +66,88 @@ public class Arbitrator implements Serializable {
// Getters // Getters
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public String getId() { @Override
return id; public long getTTL() {
return TTL;
} }
public byte[] getPubKey() { @Override
return pubKey; public PublicKey getPubKey() {
return pubKeyRing.getStorageSignaturePubKey();
} }
public PublicKey getP2pSigPubKey() { public byte[] getBtcPubKey() {
return p2pSigPubKey; return btcPubKey;
} }
public String getName() { public PubKeyRing getPubKeyRing() {
return name; return pubKeyRing;
} }
public ID_TYPE getIdType() { public Address getArbitratorAddress() {
return idType; return arbitratorAddress;
}
public Date getRegistrationDate() {
return new Date(registrationDate);
}
public String getBtcAddress() {
return btcAddress;
} }
public List<String> getLanguageCodes() { public List<String> getLanguageCodes() {
return languageCodes; return languageCodes;
} }
public Reputation getReputation() { public String getRegistrationSignature() {
return reputation; return registrationSignature;
} }
public Coin getFee() { public byte[] getRegistrationPubKey() {
return fee; return registrationPubKey;
} }
public List<METHOD> getArbitrationMethods() { @Override
return arbitrationMethods; public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Arbitrator)) return false;
Arbitrator that = (Arbitrator) o;
if (registrationDate != that.registrationDate) return false;
if (!Arrays.equals(btcPubKey, that.btcPubKey)) return false;
if (pubKeyRing != null ? !pubKeyRing.equals(that.pubKeyRing) : that.pubKeyRing != null) return false;
if (arbitratorAddress != null ? !arbitratorAddress.equals(that.arbitratorAddress) : that.arbitratorAddress != null)
return false;
if (languageCodes != null ? !languageCodes.equals(that.languageCodes) : that.languageCodes != null)
return false;
if (btcAddress != null ? !btcAddress.equals(that.btcAddress) : that.btcAddress != null) return false;
if (registrationSignature != null ? !registrationSignature.equals(that.registrationSignature) : that.registrationSignature != null)
return false;
return Arrays.equals(registrationPubKey, that.registrationPubKey);
} }
public List<ID_VERIFICATION> getIdVerifications() { @Override
return idVerifications; public int hashCode() {
int result = btcPubKey != null ? Arrays.hashCode(btcPubKey) : 0;
result = 31 * result + (pubKeyRing != null ? pubKeyRing.hashCode() : 0);
result = 31 * result + (arbitratorAddress != null ? arbitratorAddress.hashCode() : 0);
result = 31 * result + (languageCodes != null ? languageCodes.hashCode() : 0);
result = 31 * result + (btcAddress != null ? btcAddress.hashCode() : 0);
result = 31 * result + (int) (registrationDate ^ (registrationDate >>> 32));
result = 31 * result + (registrationSignature != null ? registrationSignature.hashCode() : 0);
result = 31 * result + (registrationPubKey != null ? Arrays.hashCode(registrationPubKey) : 0);
return result;
} }
public String getWebUrl() { @Override
return webUrl; public String toString() {
} return "Arbitrator{" +
"arbitratorAddress='" + arbitratorAddress + '\'' +
public String getDescription() { ", languageCodes=" + languageCodes +
return description; ", btcAddress='" + btcAddress + '\'' +
'}';
} }
} }

View File

@ -17,25 +17,28 @@
package io.bitsquare.arbitration; package io.bitsquare.arbitration;
import io.bitsquare.BitsquareModule;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import com.google.inject.name.Names;
import io.bitsquare.app.AppModule;
import io.bitsquare.app.ProgramArguments;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
public abstract class ArbitratorModule extends BitsquareModule { public class ArbitratorModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(ArbitratorModule.class);
protected ArbitratorModule(Environment env) { public ArbitratorModule(Environment env) {
super(env); super(env);
} }
@Override @Override
protected final void configure() { protected final void configure() {
bind(ArbitrationRepository.class).in(Singleton.class); bind(ArbitratorManager.class).in(Singleton.class);
bind(DisputeManager.class).in(Singleton.class);
bind(ArbitratorService.class).in(Singleton.class);
doConfigure(); Boolean devTest = env.getProperty(ProgramArguments.DEV_TEST, boolean.class, false);
} bind(boolean.class).annotatedWith(Names.named(ProgramArguments.DEV_TEST)).toInstance(devTest);
protected void doConfigure() {
} }
} }

View File

@ -17,33 +17,75 @@
package io.bitsquare.arbitration; package io.bitsquare.arbitration;
import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.DHTService; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashSetChangedListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
public interface ArbitratorService extends DHTService { /**
* Used to store arbitrators profile and load map of arbitrators
*/
public class ArbitratorService {
private static final Logger log = LoggerFactory.getLogger(ArbitratorService.class);
void addListener(Listener listener); private P2PService p2PService;
void removeListener(Listener listener);
void addArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
void loadAllArbitrators(ArbitratorMapResultHandler resultHandler, ErrorMessageHandler errorMessageHandler);
interface Listener {
void onArbitratorAdded(Arbitrator arbitrator);
void onAllArbitratorsLoaded(Map<String, Arbitrator> arbitratorsMap);
void onArbitratorRemoved(Arbitrator arbitrator);
}
interface ArbitratorMapResultHandler { interface ArbitratorMapResultHandler {
void handleResult(Map<String, Arbitrator> arbitratorsMap); void handleResult(Map<String, Arbitrator> arbitratorsMap);
} }
}
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public ArbitratorService(P2PService p2PService) {
this.p2PService = p2PService;
}
public void addHashSetChangedListener(HashSetChangedListener hashSetChangedListener) {
p2PService.addHashSetChangedListener(hashSetChangedListener);
}
public void addArbitrator(Arbitrator arbitrator, final ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
log.debug("addArbitrator arbitrator.hashCode() " + arbitrator.hashCode());
boolean result = p2PService.addData(arbitrator);
if (result) {
log.trace("Add arbitrator to network was successful. Arbitrator = " + arbitrator);
resultHandler.handleResult();
} else {
errorMessageHandler.handleErrorMessage("Add arbitrator failed");
}
}
public void removeArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
log.debug("removeArbitrator arbitrator.hashCode() " + arbitrator.hashCode());
if (p2PService.removeData(arbitrator)) {
log.trace("Remove arbitrator from network was successful. Arbitrator = " + arbitrator);
resultHandler.handleResult();
} else {
errorMessageHandler.handleErrorMessage("Remove arbitrator failed");
}
}
P2PService getP2PService() {
return p2PService;
}
public Map<Address, Arbitrator> getArbitrators() {
final Map<Address, Arbitrator> arbitratorsMap = p2PService.getDataMap().values().stream()
.filter(e -> e.expirablePayload instanceof Arbitrator)
.map(e -> (Arbitrator) e.expirablePayload)
.collect(Collectors.toMap(e -> e.getArbitratorAddress(), e -> e));
return arbitratorsMap;
}
}

View File

@ -1,43 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.arbitration;
import io.bitsquare.app.Version;
import java.io.Serializable;
//TODO still open if we use that really...
/**
* Reputation for Arbitrators
*/
public class Reputation implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
//TODO
public Reputation() {
}
@Override
public String toString() {
return "4 positive ratings in 5 cases";
}
}

View File

@ -1,37 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.arbitration.tomp2p;
import io.bitsquare.arbitration.ArbitratorModule;
import io.bitsquare.arbitration.ArbitratorService;
import com.google.inject.Singleton;
import org.springframework.core.env.Environment;
public class TomP2PArbitratorModule extends ArbitratorModule {
public TomP2PArbitratorModule(Environment env) {
super(env);
}
@Override
protected void doConfigure() {
bind(ArbitratorService.class).to(TomP2PArbitratorService.class).in(Singleton.class);
}
}

View File

@ -1,173 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.arbitration.tomp2p;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.KeyRing;
import io.bitsquare.p2p.tomp2p.TomP2PDHTService;
import io.bitsquare.p2p.tomp2p.TomP2PNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.FutureRemove;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PArbitratorService extends TomP2PDHTService implements ArbitratorService {
private static final Logger log = LoggerFactory.getLogger(TomP2PArbitratorService.class);
private static final Number160 LOCATION_KEY = Number160.createHash("ArbitratorService");
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
@Inject
public TomP2PArbitratorService(TomP2PNode tomP2PNode, KeyRing keyRing) {
super(tomP2PNode, keyRing);
}
public void addArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
try {
final Data arbitratorData = new Data(arbitrator);
openRequestsUp();
FuturePut addFuture = addProtectedDataToMap(LOCATION_KEY, arbitratorData);
addFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
openRequestsDown();
if (future.isSuccess()) {
log.trace("Add arbitrator to DHT was successful. Stored data: [key: " + LOCATION_KEY + ", " +
"values: " + arbitratorData + "]");
Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) {
Arbitrator result = (Arbitrator) arbitratorDataObject;
executor.execute(() -> {
resultHandler.handleResult();
listeners.stream().forEach(listener -> listener.onArbitratorAdded(result));
});
}
}
else {
log.error("Add arbitrator to DHT failed with reason:" + addFuture.failedReason());
errorMessageHandler.handleErrorMessage("Add arbitrator to DHT failed with reason:" + addFuture.failedReason());
}
}
});
} catch (IOException e) {
openRequestsDown();
e.printStackTrace();
}
}
public void removeArbitrator(Arbitrator arbitrator) throws IOException {
final Data arbitratorData = new Data(arbitrator);
openRequestsUp();
FutureRemove removeFuture = removeProtectedDataFromMap(LOCATION_KEY, arbitratorData);
removeFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
openRequestsDown();
for (Data arbitratorData : removeFuture.dataMap().values()) {
try {
Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) {
Arbitrator arbitrator = (Arbitrator) arbitratorDataObject;
executor.execute(() -> listeners.stream().forEach(listener -> listener.onArbitratorRemoved(arbitrator)));
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
// We don't test futureRemove.isSuccess() as this API does not fit well to that operation,
// it might change in future to something like foundAndRemoved and notFound
// See discussion at: https://github.com/tomp2p/TomP2P/issues/57#issuecomment-62069840
log.trace("Remove arbitrator from DHT was successful. Stored data: [key: " + LOCATION_KEY + ", " +
"values: " + arbitratorData + "]");
}
});
}
public void loadAllArbitrators(ArbitratorMapResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
FutureGet futureGet = getMap(LOCATION_KEY);
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (future.isSuccess()) {
log.trace("Get arbitrators from DHT was successful. Stored data: [key: " + LOCATION_KEY + ", " +
"values: " + futureGet.dataMap() + "]");
final Map<String, Arbitrator> arbitratorsMap = new HashMap<>();
for (Data arbitratorData : futureGet.dataMap().values()) {
try {
Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) {
Arbitrator arbitrator = (Arbitrator) arbitratorDataObject;
arbitratorsMap.put(arbitrator.getId(), arbitrator);
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
log.error("Get arbitrators from DHT failed with exception:" + e.getMessage());
errorMessageHandler.handleErrorMessage("Get arbitrators from DHT failed with exception:" + e.getMessage());
}
}
executor.execute(() -> {
resultHandler.handleResult(arbitratorsMap);
listeners.stream().forEach(listener -> listener.onAllArbitratorsLoaded(arbitratorsMap));
});
}
else {
log.error("Get arbitrators from DHT failed with reason:" + future.failedReason());
errorMessageHandler.handleErrorMessage("Get arbitrators from DHT failed with reason:" + future.failedReason());
}
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Event Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
}

View File

@ -17,75 +17,59 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.DefaultCoinSelector;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.core.*;
import java.math.BigInteger; import org.bitcoinj.wallet.CoinSelection;
import org.bitcoinj.wallet.CoinSelector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.*;
import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* This class implements a {@link org.bitcoinj.wallet.CoinSelector} which attempts to get the highest priority * We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
* possible. This means that the transaction is the most likely to get confirmed. Note that this means we may end up * We lookup for spendable outputs which matches our address of our addressEntry.
* "spending" more priority than would be required to get the transaction we are creating confirmed.
*/ */
class AddressBasedCoinSelector extends DefaultCoinSelector { class AddressBasedCoinSelector implements CoinSelector {
private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class); private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class);
private final NetworkParameters params; private final NetworkParameters params;
private final AddressEntry addressEntry; private final AddressEntry addressEntry;
private final boolean includePending;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public AddressBasedCoinSelector(NetworkParameters params, AddressEntry addressEntry, @SuppressWarnings("SameParameterValue") boolean includePending) { public AddressBasedCoinSelector(NetworkParameters params, AddressEntry addressEntry) {
this.params = params; this.params = params;
this.addressEntry = addressEntry; this.addressEntry = addressEntry;
this.includePending = includePending;
} }
@SuppressWarnings("WeakerAccess")
@VisibleForTesting @VisibleForTesting
static void sortOutputs(ArrayList<TransactionOutput> outputs) { static void sortOutputs(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> { Collections.sort(outputs, new Comparator<TransactionOutput>() {
int depth1 = 0; @Override
int depth2 = 0; public int compare(TransactionOutput a, TransactionOutput b) {
assert a.getParentTransaction() != null; int depth1 = a.getParentTransactionDepthInBlocks();
assert b.getParentTransaction() != null; int depth2 = b.getParentTransactionDepthInBlocks();
TransactionConfidence conf1 = a.getParentTransaction().getConfidence(); Coin aValue = a.getValue();
TransactionConfidence conf2 = b.getParentTransaction().getConfidence(); Coin bValue = b.getValue();
if (conf1.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1));
depth1 = conf1.getDepthInBlocks(); BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2));
if (conf2.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) int c1 = bCoinDepth.compareTo(aCoinDepth);
depth2 = conf2.getDepthInBlocks(); if (c1 != 0) return c1;
Coin aValue = a.getValue(); // The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
Coin bValue = b.getValue(); int c2 = bValue.compareTo(aValue);
BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1)); if (c2 != 0) return c2;
BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2)); // They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
int c1 = bCoinDepth.compareTo(aCoinDepth); checkNotNull(a.getParentTransactionHash(), "a.getParentTransactionHash() must not be null");
if (c1 != 0) return c1; checkNotNull(b.getParentTransactionHash(), "b.getParentTransactionHash() must not be null");
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size. BigInteger aHash = a.getParentTransactionHash().toBigInteger();
int c2 = bValue.compareTo(aValue); BigInteger bHash = b.getParentTransactionHash().toBigInteger();
if (c2 != 0) return c2; return aHash.compareTo(bHash);
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering. }
BigInteger aHash = a.getParentTransaction().getHash().toBigInteger();
BigInteger bHash = b.getParentTransaction().getHash().toBigInteger();
return aHash.compareTo(bHash);
}); });
} }
@ -94,42 +78,16 @@ class AddressBasedCoinSelector extends DefaultCoinSelector {
TransactionConfidence confidence = tx.getConfidence(); TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType(); TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
// TODO It might be risky to accept 0 confirmation tx from the network with only > 1 numBroadcastPeers
// Need to be tested in testnet and mainnet
// We need to handle cases when malleability happens or tx get lost and have not been successful propagated
/* return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
type.equals(TransactionConfidence.ConfidenceType.PENDING) &&
// we accept network tx without confirmations and numBroadcastPeers > 0
//confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
// In regtest mode we expect to have only one peer, so we won't see transactions propagate.
// TODO: The value 1 below dates from a time when transactions we broadcast *to* were
// counted, set to 0
(confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());*/
log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers());
// TODO at testnet we got confidence.numBroadcastPeers()=0 -> probably because we use chained unconfirmed tx
// investigate further
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
type.equals(TransactionConfidence.ConfidenceType.PENDING); type.equals(TransactionConfidence.ConfidenceType.PENDING);
} }
private static boolean isInBlockChain(Transaction tx) {
// Only pick chain-included transactions.
TransactionConfidence confidence = tx.getConfidence();
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
return type.equals(TransactionConfidence.ConfidenceType.BUILDING);
}
/** /**
* Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. * Sub-classes can override this to just customize whether transactions are usable, but keep age sorting.
*/ */
protected boolean shouldSelect(Transaction tx) { protected boolean shouldSelect(Transaction tx) {
if (includePending) { return isInBlockChainOrPending(tx);
return isInBlockChainOrPending(tx);
}
else {
return isInBlockChain(tx);
}
} }
private boolean matchesRequiredAddress(TransactionOutput transactionOutput) { private boolean matchesRequiredAddress(TransactionOutput transactionOutput) {
@ -151,9 +109,9 @@ class AddressBasedCoinSelector extends DefaultCoinSelector {
@Override @Override
public CoinSelection select(Coin target, List<TransactionOutput> candidates) { public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
log.debug("candidates.size: " + candidates.size()); log.trace("candidates.size: " + candidates.size());
long targetAsLong = target.longValue(); long targetAsLong = target.longValue();
log.debug("value needed: " + targetAsLong); log.trace("value needed: " + targetAsLong);
HashSet<TransactionOutput> selected = new HashSet<>(); HashSet<TransactionOutput> selected = new HashSet<>();
// Sort the inputs by age*value so we get the highest "coindays" spent. // Sort the inputs by age*value so we get the highest "coindays" spent.
// TODO: Consider changing the wallets internal format to track just outputs and keep them ordered. // TODO: Consider changing the wallets internal format to track just outputs and keep them ordered.

View File

@ -18,37 +18,58 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.DeterministicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.Serializable; import java.io.Serializable;
import org.jetbrains.annotations.NotNull;
/** /**
* Is a minimalistic wallet abstraction used to separate transactions between different activities like: * Every trade use a addressEntry with a dedicated address for all transactions related to the trade.
* Registration, trade and arbiter deposit. * That way we have a kind of separated trade wallet, isolated from other transactions and avoiding coin merge.
* If we would not avoid coin merge the user would lose privacy between trades.
*/ */
public class AddressEntry implements Serializable { public class AddressEntry 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;
// that will be restored from the wallet at deserialization private transient static final Logger log = LoggerFactory.getLogger(AddressEntry.class);
public enum Context {
TRADE,
ARBITRATOR
}
// keyPair can be null in case the object is created from deserialization as it is transient.
// It will be restored when the wallet is ready at setDeterministicKey
// So after startup it never must be null
@Nullable
private transient DeterministicKey keyPair; private transient DeterministicKey keyPair;
private final String offerId; // Only set if its a TRADE Context
@Nullable
private String offerId;
private final Context context; private final Context context;
private final byte[] pubKey; private final byte[] pubKey;
private final byte[] pubKeyHash; private final byte[] pubKeyHash;
private final NetworkParameters params; private final NetworkParameters params;
public AddressEntry(DeterministicKey keyPair, NetworkParameters params, @SuppressWarnings("SameParameterValue") Context context) {
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
///////////////////////////////////////////////////////////////////////////////////////////
// If created without offerId (arbitrator)
public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context) {
this(keyPair, params, context, null); this(keyPair, params, context, null);
} }
public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context, String offerId) { // If created with offerId
public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) {
this.keyPair = keyPair; this.keyPair = keyPair;
this.params = params; this.params = params;
this.context = context; this.context = context;
@ -58,29 +79,44 @@ public class AddressEntry implements Serializable {
pubKeyHash = keyPair.getPubKeyHash(); pubKeyHash = keyPair.getPubKeyHash();
} }
// Set after wallet is ready
public void setDeterministicKey(DeterministicKey deterministicKey) {
this.keyPair = deterministicKey;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
@Nullable
public String getOfferId() { public String getOfferId() {
return offerId; return offerId;
} }
// For display we usually only display the first 8 characters.
@Nullable
public String getShortOfferId() {
return offerId != null ? offerId.substring(0, 8) : null;
}
public Context getContext() { public Context getContext() {
return context; return context;
} }
@Nullable
public String getAddressString() { public String getAddressString() {
return getAddress().toString(); return getAddress() != null ? getAddress().toString() : null;
} }
@NotNull @Nullable
public DeterministicKey getKeyPair() { public DeterministicKey getKeyPair() {
return keyPair; return keyPair != null ? keyPair : null;
} }
@Nullable
public Address getAddress() { public Address getAddress() {
return keyPair.toAddress(params); return keyPair != null ? keyPair.toAddress(params) : null;
}
public void setDeterministicKey(DeterministicKey deterministicKey) {
this.keyPair = deterministicKey;
} }
public byte[] getPubKeyHash() { public byte[] getPubKeyHash() {
@ -91,21 +127,12 @@ public class AddressEntry implements Serializable {
return pubKey; return pubKey;
} }
public enum Context {
REGISTRATION_FEE,
TRADE,
ARBITRATOR_DEPOSIT
}
@Override @Override
public String toString() { public String toString() {
return "AddressEntry{" + return "AddressEntry{" +
"offerId='" + offerId + '\'' + "offerId='" + offerId + '\'' +
", context=" + context + ", context=" + context +
", address=" + getAddressString() + ", address=" + getAddressString() +
/* ", pubKey=" + Arrays.toString(pubKey) +
", pubKeyHash=" + Arrays.toString(pubKeyHash) +
", params=" + params +*/
'}'; '}';
} }
} }

View File

@ -17,21 +17,20 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import com.google.inject.Inject;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import org.bitcoinj.core.Wallet; import org.bitcoinj.core.Wallet;
import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.DeterministicKey;
import com.google.inject.Inject;
import java.io.Serializable;
import java.util.ArrayList;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.ArrayList;
/**
* The List supporting our persistence solution.
*/
public class AddressEntryList extends ArrayList<AddressEntry> implements Serializable { public class AddressEntryList extends ArrayList<AddressEntry> 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;
@ -53,36 +52,33 @@ public class AddressEntryList extends ArrayList<AddressEntry> implements Seriali
AddressEntryList persisted = storage.initAndGetPersisted(this); AddressEntryList persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
for (AddressEntry addressEntry : persisted) { for (AddressEntry addressEntry : persisted) {
addressEntry.setDeterministicKey((DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash())); DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash());
this.add(addressEntry); if (keyFromPubHash != null) {
addressEntry.setDeterministicKey(keyFromPubHash);
add(addressEntry);
} else {
log.warn("Key from addressEntry not found in that wallet " + addressEntry.toString());
}
} }
} } else {
else { add(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.ARBITRATOR));
// First time create registrationAddressEntry storage.queueUpForSave();
createRegistrationAddressEntry();
} }
} }
public AddressEntry getNewAddressEntry(AddressEntry.Context context, String offerId) { public AddressEntry getNewAddressEntry(AddressEntry.Context context, String offerId) {
log.trace("getNewAddressEntry called with offerId " + offerId); log.trace("getNewAddressEntry called with offerId " + offerId);
DeterministicKey key = wallet.freshReceiveKey(); AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId);
AddressEntry addressEntry = new AddressEntry(key, wallet.getParams(), context, offerId);
add(addressEntry); add(addressEntry);
storage.queueUpForSave(); storage.queueUpForSave();
return addressEntry; return addressEntry;
} }
private void createRegistrationAddressEntry() {
DeterministicKey registrationKey = wallet.currentReceiveKey();
AddressEntry registrationAddressEntry = new AddressEntry(registrationKey, wallet.getParams(), AddressEntry.Context.REGISTRATION_FEE);
add(registrationAddressEntry);
storage.queueUpForSave();
}
public AddressEntry getRegistrationAddressEntry() { public AddressEntry getArbitratorAddressEntry() {
if (isEmpty()) if (size() > 0)
createRegistrationAddressEntry(); return get(0);
else
return get(0); return null;
} }
} }

View File

@ -17,21 +17,17 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.BitsquareModule;
import com.google.inject.Injector;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.io.File; import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named; import static com.google.inject.name.Names.named;
public class BitcoinModule extends BitsquareModule { public class BitcoinModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(BitcoinModule.class); private static final Logger log = LoggerFactory.getLogger(BitcoinModule.class);
public BitcoinModule(Environment env) { public BitcoinModule(Environment env) {
@ -54,13 +50,6 @@ public class BitcoinModule extends BitsquareModule {
bind(AddressEntryList.class).in(Singleton.class); bind(AddressEntryList.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class); bind(TradeWalletService.class).in(Singleton.class);
bind(WalletService.class).in(Singleton.class); bind(WalletService.class).in(Singleton.class);
bind(BlockChainService.class).in(Singleton.class);
}
@Override
protected void doClose(Injector injector) {
log.trace("doClose " + getClass().getSimpleName());
injector.getInstance(WalletService.class).shutDown();
} }
} }

View File

@ -1,76 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import io.bitsquare.fiat.FiatAccount;
import javax.inject.Inject;
/**
* A service delivers blockchain functionality from the BitcoinJ library.
*/
@SuppressWarnings({"UnusedDeclaration", "UnusedParameters"})
public class BlockChainService {
@Inject
public BlockChainService() {
}
//TODO
@SuppressWarnings("SameReturnValue")
public boolean isAccountBlackListed(String accountId, FiatAccount fiatAccount) {
return false;
}
//TODO
@SuppressWarnings("SameReturnValue")
public boolean verifyAccountRegistration() {
return true;
}
@SuppressWarnings("SameReturnValue")
private boolean findAddressInBlockChain(String address) {
// TODO lookup for address in blockchain
return true;
}
@SuppressWarnings("SameReturnValue")
private byte[] getDataForTxWithAddress(String address) {
// TODO return data after OP_RETURN
return null;
}
@SuppressWarnings("SameReturnValue")
private boolean isFeePayed(String address) {
// TODO check if fee is paid
return true;
}
@SuppressWarnings("SameReturnValue")
private boolean isAccountIDBlacklisted(String accountID) {
// TODO check if accountID is on blacklist
return false;
}
@SuppressWarnings("SameReturnValue")
private boolean isFiatAccountBlacklisted(FiatAccount fiatAccount) {
// TODO check if accountID is on blacklist
return false;
}
}

View File

@ -17,68 +17,39 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.BitsquareException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Wallet;
import javax.inject.Inject;
public class FeePolicy { public class FeePolicy {
public static final Coin TX_FEE = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; // dropped down to 0.00001 BTC // Official min. fee and fee per kiloByte dropped down to 0.00001 BTC / Coin.valueOf(1000) / 1000 satoshis, but as there are reported problems with
// confirmation we use a hgher value.
// The should also help to avoid problems when the tx size is larger as the standard (e.g. if the user does not pay
// in with one transaction but several tx). We don't do a dynamically fee calculation as we need predictable amounts, so that should help to get a larger
// headroom.
// Andreas Schildbach reported problems with confirmation and increased the fee/offered UI side fee setting.
// http://www.cointape.com/
// The fastest and cheapest transaction fee is currently 50 satoshis/byte, shown in green at the top.
// For the average transaction size of 597 bytes, this results in a fee of 298 bits (0.298 mBTC). -> 0.0003 BTC or Coin.valueOf(30000);
// TODO: Change REGISTRATION_FEE to 0.00001 (See https://github.com/bitsquare/bitsquare/issues/228) // trade fee tx: 226 bytes
public static final Coin REGISTRATION_FEE = TX_FEE.add(TX_FEE); // deposit tx: 336 bytes
public static final Coin CREATE_OFFER_FEE = Coin.valueOf(1000000); // 0.01 BTC // payout tx: 371 bytes
// disputed payout tx: 408 bytes -> 20400 satoshis with 50 satoshis/byte
// Other good source is: https://tradeblock.com/blockchain 15-100 satoshis/byte
public static final Coin TX_FEE = Coin.valueOf(30000); // 0.0003 BTC about 0.06 EUR @ 200 EUR/BTC: about 90 satoshi /byte
static {
// we use our fee as default fee
Wallet.SendRequest.DEFAULT_FEE_PER_KB = FeePolicy.TX_FEE;
}
public static final Coin DUST = Coin.valueOf(546);
public static final Coin CREATE_OFFER_FEE = Coin.valueOf(100000); // 0.001 BTC 0.1% of 1 BTC about 0.2 EUR @ 200 EUR/BTC
public static final Coin TAKE_OFFER_FEE = CREATE_OFFER_FEE; public static final Coin TAKE_OFFER_FEE = CREATE_OFFER_FEE;
public static final Coin SECURITY_DEPOSIT = Coin.valueOf(10000000); // 0.1 BTC; about 20 EUR @ 200 EUR/BTC
private final BitcoinNetwork bitcoinNetwork;
private final String createOfferFeeAddress;
private final String takeOfferFeeAddress;
@Inject
public FeePolicy(Preferences preferences) {
this.bitcoinNetwork = preferences.getBitcoinNetwork();
switch (bitcoinNetwork) {
case TESTNET:
createOfferFeeAddress = "mopJDiHncoveyy7S7FZTUNVbrCxazxvGrE";
takeOfferFeeAddress = "mopJDiHncoveyy7S7FZTUNVbrCxazxvGrE";
break;
case MAINNET:
// bitsquare donation address used for the moment...
createOfferFeeAddress = "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7";
takeOfferFeeAddress = "1BVxNn3T12veSK6DgqwU4Hdn7QHcDDRag7";
break;
case REGTEST:
createOfferFeeAddress = "mkNW1omJFA7RD3AZ94mfKqubRff2gx21KE";
takeOfferFeeAddress = "mkNW1omJFA7RD3AZ94mfKqubRff2gx21KE";
break;
default:
throw new BitsquareException("Unknown bitcoin network: %s", bitcoinNetwork);
}
}
//TODO get address form arbitrator list
public Address getAddressForCreateOfferFee() {
try {
return new Address(bitcoinNetwork.getParameters(), createOfferFeeAddress);
} catch (AddressFormatException ex) {
throw new BitsquareException(ex);
}
}
//TODO get address form the intersection of both traders arbitrator lists
public Address getAddressForTakeOfferFee() {
try {
return new Address(bitcoinNetwork.getParameters(), takeOfferFeeAddress);
} catch (AddressFormatException ex) {
throw new BitsquareException(ex);
}
}
} }

View File

@ -20,12 +20,9 @@ package io.bitsquare.btc;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
// Lets see if we get more restriction otherwise move it to other class
public class Restrictions { public class Restrictions {
public static final Coin MIN_TRADE_AMOUNT = Coin.parseCoin("0.0001"); public static final Coin MIN_TRADE_AMOUNT = Coin.parseCoin("0.0001");
public static final Coin MAX_TRADE_AMOUNT = Coin.parseCoin("10"); public static final Coin MAX_TRADE_AMOUNT = Coin.parseCoin("1");
public static final Coin MIN_SECURITY_DEPOSIT = Coin.parseCoin("0.0001");
public static boolean isMinSpendableAmount(Coin amount) { public static boolean isMinSpendableAmount(Coin amount) {
return amount != null && amount.compareTo(FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)) > 0; return amount != null && amount.compareTo(FeePolicy.TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)) > 0;

File diff suppressed because it is too large Load Diff

View File

@ -17,89 +17,56 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.crypto.CryptoService;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.AbstractWalletEventListener;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.DownloadProgressTracker;
import org.bitcoinj.core.FilteredBlock;
import org.bitcoinj.core.GetDataMessage;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Message;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.PeerEventListener;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Wallet;
import org.bitcoinj.core.WalletEventListener;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.utils.Threading;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Service; import com.google.common.util.concurrent.Service;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
import org.bitcoinj.core.*;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DeterministicSeed;
import org.jetbrains.annotations.NotNull;
import org.reactfx.util.FxTimer;
import org.reactfx.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File; import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nullable; /**
* WalletService handles all non trade specific wallet and bitcoin related services.
import javax.inject.Inject; * It startup the wallet app kit and initialized the wallet.
import javax.inject.Named; */
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import rx.subjects.Subject;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
public class WalletService { public class WalletService {
private static final Logger log = LoggerFactory.getLogger(WalletService.class); private static final Logger log = LoggerFactory.getLogger(WalletService.class);
public static final String DIR_KEY = "wallet.dir"; public static final String DIR_KEY = "wallet.dir";
public static final String PREFIX_KEY = "wallet.prefix"; public static final String PREFIX_KEY = "wallet.prefix";
private static final long STARTUP_TIMEOUT = 60; private static final long STARTUP_TIMEOUT = 60 * 1000;
private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>(); private final List<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArrayList<>();
private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>(); private final List<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArrayList<>();
@ -112,16 +79,15 @@ public class WalletService {
private final TradeWalletService tradeWalletService; private final TradeWalletService tradeWalletService;
private final AddressEntryList addressEntryList; private final AddressEntryList addressEntryList;
private final NetworkParameters params; private final NetworkParameters params;
private final CryptoService cryptoService;
private final File walletDir; private final File walletDir;
private final String walletPrefix; private final String walletPrefix;
private final UserAgent userAgent; private final UserAgent userAgent;
private WalletAppKit walletAppKit; private WalletAppKit walletAppKit;
private Wallet wallet; private Wallet wallet;
private AddressEntry registrationAddressEntry; private AddressEntry arbitratorAddressEntry;
private AddressEntry arbitratorDepositAddressEntry;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0); private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -129,14 +95,12 @@ public class WalletService {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public WalletService(RegTestHost regTestHost, CryptoService cryptoService, public WalletService(RegTestHost regTestHost, TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent,
TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent,
@Named(DIR_KEY) File walletDir, @Named(PREFIX_KEY) String walletPrefix, Preferences preferences) { @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 = preferences.getBitcoinNetwork().getParameters(); this.params = preferences.getBitcoinNetwork().getParameters();
this.cryptoService = cryptoService;
this.walletDir = new File(walletDir, "bitcoin"); this.walletDir = new File(walletDir, "bitcoin");
this.walletPrefix = walletPrefix; this.walletPrefix = walletPrefix;
this.userAgent = userAgent; this.userAgent = userAgent;
@ -147,14 +111,18 @@ public class WalletService {
// Public Methods // Public Methods
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Observable<Object> initialize(Executor executor) { public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
Subject<Object, Object> status = BehaviorSubject.create();
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means // Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
// a future version. // a future version.
Threading.USER_THREAD = executor;
Threading.USER_THREAD = UserThread.getExecutor();
Timer timeoutTimer = FxTimer.runLater(
Duration.ofMillis(STARTUP_TIMEOUT),
() -> exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " + STARTUP_TIMEOUT / 1000 + " seconds."))
);
// If seed is non-null it means we are restoring from backup. // If seed is non-null it means we are restoring from backup.
walletAppKit = new WalletAppKit(params, walletDir, walletPrefix) { walletAppKit = new WalletAppKit(params, walletDir, walletPrefix) {
@ -166,30 +134,70 @@ public class WalletService {
if (params != RegTestParams.get()) if (params != RegTestParams.get())
walletAppKit.peerGroup().setMaxConnections(11); walletAppKit.peerGroup().setMaxConnections(11);
walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001); walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001);
initWallet(); wallet = walletAppKit.wallet();
wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
arbitratorAddressEntry = addressEntryList.getArbitratorAddressEntry();
walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
@Override
public void onPeersDiscovered(Set<PeerAddress> peerAddresses) {
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
}
@Override
public void onChainDownloadStarted(Peer peer, int blocksLeft) {
}
@Override
public void onPeerConnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
return null;
}
@Override
public void onTransaction(Peer peer, Transaction t) {
}
@Nullable
@Override
public List<Message> getData(Peer peer, GetDataMessage m) {
return null;
}
});
// set after wallet is ready // set after wallet is ready
tradeWalletService.setWalletAppKit(walletAppKit); tradeWalletService.setWalletAppKit(walletAppKit);
timeoutTimer.stop();
status.onCompleted(); UserThread.execute(resultHandler::handleResult);
} }
}; };
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
// or progress widget to keep the user engaged whilst we initialise, but we don't. // or progress widget to keep the user engaged whilst we initialise, but we don't.
if (params == RegTestParams.get()) { if (params == RegTestParams.get()) {
log.debug("regTestHost " + regTestHost);
if (regTestHost == RegTestHost.REG_TEST_SERVER) { if (regTestHost == RegTestHost.REG_TEST_SERVER) {
try { try {
walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort())); walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} } else if (regTestHost == RegTestHost.LOCALHOST) {
else if (regTestHost == RegTestHost.LOCALHOST) {
walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.} walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.}
} }
} } else if (params == MainNetParams.get()) {
else if (params == MainNetParams.get()) {
// Checkpoints are block headers that ship inside our app: for a new user, we pick the last header // Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
// in the checkpoints file and then download the rest from the network. It makes things much faster. // in the checkpoints file and then download the rest from the network. It makes things much faster.
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the // Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
@ -200,102 +208,46 @@ public class WalletService {
e.printStackTrace(); e.printStackTrace();
log.error(e.toString()); log.error(e.toString());
} }
} } else if (params == TestNet3Params.get()) {
else if (params == TestNet3Params.get()) {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet")); walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
} }
walletAppKit.setDownloadListener(downloadListener) walletAppKit.setDownloadListener(downloadListener)
.setBlockingStartup(false) .setBlockingStartup(false)
.setUserAgent(userAgent.getName(), userAgent.getVersion()); .setUserAgent(userAgent.getName(), userAgent.getVersion())
.restoreWalletFromSeed(seed);
/*
// TODO restore from DeterministicSeed
if (seed != null)
walletAppKit.restoreWalletFromSeed(seed);
*/
walletAppKit.addListener(new Service.Listener() { walletAppKit.addListener(new Service.Listener() {
@Override @Override
public void failed(@NotNull Service.State from, @NotNull Throwable failure) { public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
walletAppKit = null; walletAppKit = null;
log.error("walletAppKit failed"); log.error("walletAppKit failed");
status.onError(failure); timeoutTimer.stop();
UserThread.execute(() -> exceptionHandler.handleException(failure));
} }
}, Threading.USER_THREAD); }, Threading.USER_THREAD);
walletAppKit.startAsync(); walletAppKit.startAsync();
return status.timeout(STARTUP_TIMEOUT, TimeUnit.SECONDS);
}
private void initWallet() {
wallet = walletAppKit.wallet();
wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
registrationAddressEntry = addressEntryList.getRegistrationAddressEntry();
walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
@Override
public void onPeersDiscovered(Set<PeerAddress> peerAddresses) {
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
}
@Override
public void onChainDownloadStarted(Peer peer, int blocksLeft) {
}
@Override
public void onPeerConnected(Peer peer, int peerCount) {
log.trace("onPeerConnected " + peerCount);
Threading.USER_THREAD.execute(() -> numPeers.set(peerCount));
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
log.trace("onPeerDisconnected " + peerCount);
Threading.USER_THREAD.execute(() -> numPeers.set(peerCount));
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
return null;
}
@Override
public void onTransaction(Peer peer, Transaction t) {
}
@Nullable
@Override
public List<Message> getData(Peer peer, GetDataMessage m) {
return null;
}
});
} }
public void shutDown() { public void shutDown() {
if (wallet != null) if (wallet != null)
wallet.removeEventListener(walletEventListener); wallet.removeEventListener(walletEventListener);
if (walletAppKit != null) { if (walletAppKit != null) {
walletAppKit.stopAsync();
try { try {
walletAppKit.stopAsync();
walletAppKit.awaitTerminated(5, TimeUnit.SECONDS); walletAppKit.awaitTerminated(5, TimeUnit.SECONDS);
} catch (TimeoutException e) { } catch (Throwable e) {
e.printStackTrace(); // ignore
log.error("walletAppKit.awaitTerminated not terminated after 5 sec. Error message: " + e.getMessage());
} }
shutDownDone.set(true);
} }
} }
public ReadOnlyDoubleProperty downloadPercentageProperty() { public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
return downloadListener.percentageProperty(); walletAppKit.stopAsync();
} walletAppKit.awaitTerminated();
initialize(seed, resultHandler, exceptionHandler);
public Wallet getWallet() {
return wallet;
} }
@ -332,40 +284,26 @@ public class WalletService {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Get AddressInfo objects // AddressInfo
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public List<AddressEntry> getAddressEntryList() { public List<AddressEntry> getAddressEntryList() {
return ImmutableList.copyOf(addressEntryList); return ImmutableList.copyOf(addressEntryList);
} }
public AddressEntry getRegistrationAddressEntry() { public AddressEntry getArbitratorAddressEntry() {
return registrationAddressEntry; return arbitratorAddressEntry;
} }
public AddressEntry getArbitratorDepositAddressEntry() { public AddressEntry getAddressEntryByOfferId(String offerId) {
if (arbitratorDepositAddressEntry == null)
arbitratorDepositAddressEntry = addressEntryList.getNewAddressEntry(AddressEntry.Context.ARBITRATOR_DEPOSIT, null);
return arbitratorDepositAddressEntry;
}
public AddressEntry getAddressEntry(String offerId) {
log.trace("getAddressEntry called with offerId " + offerId);
Optional<AddressEntry> addressEntry = getAddressEntryList().stream().filter(e -> offerId.equals(e.getOfferId())).findFirst(); Optional<AddressEntry> addressEntry = getAddressEntryList().stream().filter(e -> offerId.equals(e.getOfferId())).findFirst();
if (addressEntry.isPresent()) if (addressEntry.isPresent())
return addressEntry.get(); return addressEntry.get();
else else
return addressEntryList.getNewAddressEntry(AddressEntry.Context.TRADE, offerId); return addressEntryList.getNewAddressEntry(AddressEntry.Context.TRADE, offerId);
} }
private Optional<AddressEntry> getAddressEntryByAddress(String address) {
///////////////////////////////////////////////////////////////////////////////////////////
// Create new AddressInfo objects
///////////////////////////////////////////////////////////////////////////////////////////
private Optional<AddressEntry> getAddressEntryByAddressString(String address) {
return getAddressEntryList().stream().filter(e -> address.equals(e.getAddressString())).findFirst(); return getAddressEntryList().stream().filter(e -> address.equals(e.getAddressString())).findFirst();
} }
@ -445,26 +383,22 @@ public class WalletService {
transactionConfidence = confidence; transactionConfidence = confidence;
} }
} }
} }
return transactionConfidence; return transactionConfidence;
} }
@SuppressWarnings("UnusedDeclaration")
public boolean isRegistrationFeeConfirmed() {
assert getRegistrationAddressEntry() != null;
TransactionConfidence transactionConfidence = getConfidenceForAddress(getRegistrationAddressEntry().getAddress());
return TransactionConfidence.ConfidenceType.BUILDING.equals(transactionConfidence.getConfidenceType());
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Balance // Balance
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// BalanceType.AVAILABLE
public Coin getAvailableBalance() {
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
}
public Coin getBalanceForAddress(Address address) { public Coin getBalanceForAddress(Address address) {
return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(true), address) : Coin.ZERO; return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
} }
private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) { private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
@ -479,97 +413,27 @@ public class WalletService {
return balance; return balance;
} }
Coin getWalletBalance() {
return wallet.getBalance(Wallet.BalanceType.ESTIMATED);
}
Coin getRegistrationBalance() {
return getBalanceForAddress(getRegistrationAddressEntry().getAddress());
}
public Coin getArbitratorDepositBalance() {
return getBalanceForAddress(getArbitratorDepositAddressEntry().getAddress());
}
@SuppressWarnings("UnusedDeclaration")
public boolean isRegistrationFeeBalanceNonZero() {
return getRegistrationBalance().compareTo(Coin.ZERO) > 0;
}
@SuppressWarnings("UnusedDeclaration")
public boolean isRegistrationFeeBalanceSufficient() {
return getRegistrationBalance().compareTo(FeePolicy.REGISTRATION_FEE) >= 0;
}
//TODO
@SuppressWarnings("SameReturnValue")
public int getNumOfPeersSeenTx(String txId) {
// TODO check from blockchain
// will be async
return 3;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Transactions
///////////////////////////////////////////////////////////////////////////////////////////
public void payRegistrationFee(String stringifiedFiatAccounts, FutureCallback<Transaction> callback) throws
InsufficientMoneyException {
log.debug("payRegistrationFee");
log.trace("stringifiedFiatAccounts " + stringifiedFiatAccounts);
Transaction tx = new Transaction(params);
byte[] data = cryptoService.digestMessageWithSignature(getRegistrationAddressEntry().getKeyPair(), stringifiedFiatAccounts);
tx.addOutput(Transaction.MIN_NONDUST_OUTPUT, new ScriptBuilder().op(OP_RETURN).data(data).build());
// We don't take a fee at the moment
// 0.0000454 BTC will get extra to miners as it is lower then durst
/* Coin fee = FeePolicy.REGISTRATION_FEE
.subtract(Transaction.MIN_NONDUST_OUTPUT)
.subtract(FeePolicy.TX_FEE);
log.trace("fee: " + fee.toFriendlyString());
tx.addOutput(fee, feePolicy.getAddressForRegistrationFee());*/
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
sendRequest.shuffleOutputs = false;
// We accept at the moment registration fee payment with 0 confirmations.
// The verification will be done at the end of the trade process again, and then a double spend would be
// detected and lead to arbitration.
// The last param (boolean includePending) is used for indicating that we accept 0 conf tx.
sendRequest.coinSelector = new AddressBasedCoinSelector(params, getRegistrationAddressEntry(), true);
sendRequest.changeAddress = getRegistrationAddressEntry().getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, callback);
log.debug("Registration transaction: " + tx);
printTxWithInputs("payRegistrationFee", tx);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Withdrawal // Withdrawal
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public String sendFunds(String withdrawFromAddress, public String sendFunds(String fromAddress,
String withdrawToAddress, String toAddress,
Coin amount, Coin amount,
FutureCallback<Transaction> callback) throws AddressFormatException, InsufficientMoneyException, IllegalArgumentException { KeyParameter aesKey,
FutureCallback<Transaction> callback) throws AddressFormatException, IllegalArgumentException, InsufficientMoneyException {
Transaction tx = new Transaction(params); Transaction tx = new Transaction(params);
tx.addOutput(amount.subtract(FeePolicy.TX_FEE), new Address(params, withdrawToAddress)); tx.addOutput(amount.subtract(FeePolicy.TX_FEE), new Address(params, toAddress));
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
sendRequest.aesKey = aesKey;
sendRequest.shuffleOutputs = false; sendRequest.shuffleOutputs = false;
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to Optional<AddressEntry> addressEntry = getAddressEntryByAddress(fromAddress);
// wait for 1 confirmation)
Optional<AddressEntry> addressEntry = getAddressEntryByAddressString(withdrawFromAddress);
if (!addressEntry.isPresent()) if (!addressEntry.isPresent())
throw new IllegalArgumentException("WithdrawFromAddress is not found in our wallets."); throw new IllegalArgumentException("WithdrawFromAddress is not found in our wallets.");
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get(), true); sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get());
sendRequest.changeAddress = addressEntry.get().getAddress(); sendRequest.changeAddress = addressEntry.get().getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, callback); Futures.addCallback(sendResult.broadcastComplete, callback);
@ -580,6 +444,45 @@ public class WalletService {
return tx.getHashAsString(); return tx.getHashAsString();
} }
public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
throws InsufficientMoneyException, AddressFormatException {
Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
sendRequest.aesKey = aesKey;
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {
resultHandler.handleResult();
}
@Override
public void onFailure(@NotNull Throwable t) {
errorMessageHandler.handleErrorMessage(t.getMessage());
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
public Wallet getWallet() {
return wallet;
}
public Transaction getTransactionFromSerializedTx(byte[] tx) {
return new Transaction(params, tx);
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private methods // Private methods
@ -595,13 +498,6 @@ public class WalletService {
} }
} }
public int getNumPeers() {
return numPeers.get();
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes // Inner classes
@ -613,13 +509,13 @@ public class WalletService {
@Override @Override
protected void progress(double percentage, int blocksLeft, Date date) { protected void progress(double percentage, int blocksLeft, Date date) {
super.progress(percentage, blocksLeft, date); super.progress(percentage, blocksLeft, date);
Threading.USER_THREAD.execute(() -> this.percentage.set(percentage / 100d)); UserThread.execute(() -> this.percentage.set(percentage / 100d));
} }
@Override @Override
protected void doneDownload() { protected void doneDownload() {
super.doneDownload(); super.doneDownload();
Threading.USER_THREAD.execute(() -> this.percentage.set(1d)); UserThread.execute(() -> this.percentage.set(1d));
} }
public ReadOnlyDoubleProperty percentageProperty() { public ReadOnlyDoubleProperty percentageProperty() {
@ -661,10 +557,69 @@ public class WalletService {
if (balanceListener.getAddress() != null) if (balanceListener.getAddress() != null)
balance = getBalanceForAddress(balanceListener.getAddress()); balance = getBalanceForAddress(balanceListener.getAddress());
else else
balance = getWalletBalance(); balance = getAvailableBalance();
balanceListener.onBalanceChanged(balance); balanceListener.onBalanceChanged(balance);
} }
} }
} }
/* // TODO
private class BloomFilterForForeignTx extends AbstractPeerEventListener implements PeerFilterProvider {
private final String txId;
public BloomFilterForForeignTx(String txId) {
this.txId = txId;
}
@Override
public long getEarliestKeyCreationTime() {
return Utils.currentTimeSeconds();
}
@Override
public void beginBloomFilterCalculation() {
}
@Override
public int getBloomFilterElementCount() {
return 1;
}
@Override
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
*//* for (TransactionOutPoint pledge : allPledges.keySet()) {
filter.insert(pledge.bitcoinSerialize());
}*//*
// how to add txid ???
return filter;
}
@Override
public boolean isRequiringUpdateAllBloomFilter() {
return false;
}
@Override
public void endBloomFilterCalculation() {
}
@Override
public void onTransaction(Peer peer, Transaction t) {
// executor.checkOnThread();
// TODO: Gate this logic on t being announced by multiple peers.
// checkForRevocation(t);
// TODO: Watch out for the confirmation. If no confirmation of the revocation occurs within N hours, alert the user.
}
}
// TODO
public void findTxInBlockChain(String txId) {
// https://groups.google.com/forum/?hl=de#!topic/bitcoinj/kinFP7lLsRE
// https://groups.google.com/forum/?hl=de#!topic/bitcoinj/f7m87kCWdb8
// https://groups.google.com/forum/?hl=de#!topic/bitcoinj/jNE5ohLExVM
walletAppKit.peerGroup().addPeerFilterProvider(new BloomFilterForForeignTx(txId));
}*/
} }

View File

@ -1,25 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.handlers;
/**
* For reporting error message only (UI)
*/
public interface ErrorMessageHandler {
void handleErrorMessage(String errorMessage);
}

View File

@ -1,25 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.handlers;
/**
* For reporting throwable objects only
*/
public interface ExceptionHandler {
void handleException(Throwable throwable);
}

View File

@ -1,25 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.handlers;
/**
* For reporting a description message and throwable
*/
public interface FaultHandler {
void handleFault(String errorMessage, Throwable throwable);
}

View File

@ -1,22 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.handlers;
public interface ResultHandler {
void handleResult();
}

View File

@ -1,30 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.taskrunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InterceptTaskException extends RuntimeException {
private static final Logger log = LoggerFactory.getLogger(InterceptTaskException.class);
private static final long serialVersionUID = 5216202440370333534L;
public InterceptTaskException(String message) {
super(message);
}
}

View File

@ -1,24 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.taskrunner;
public interface Model {
void persist();
void onComplete();
}

View File

@ -1,74 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.taskrunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class Task<T extends Model> {
private static final Logger log = LoggerFactory.getLogger(Task.class);
public static Class<? extends Task> taskToIntercept;
private final TaskRunner taskHandler;
protected final T model;
protected String errorMessage = "An error occurred at: " + getClass().getSimpleName();
public Task(TaskRunner taskHandler, T model) {
this.taskHandler = taskHandler;
this.model = model;
}
abstract protected void run();
protected void runInterceptHook() {
if (getClass() == taskToIntercept)
throw new InterceptTaskException("Task intercepted for testing purpose. Task = " + getClass().getSimpleName());
}
protected void appendToErrorMessage(String message) {
errorMessage += "\n" + message;
}
protected void appendExceptionToErrorMessage(Throwable t) {
if (t.getMessage() != null)
errorMessage += "\nException message: " + t.getMessage();
else
errorMessage += "\nException: " + t.toString();
}
protected void complete() {
taskHandler.handleComplete();
}
protected void failed(String message) {
appendToErrorMessage(message);
failed();
}
protected void failed(Throwable t) {
t.printStackTrace();
appendExceptionToErrorMessage(t);
failed();
}
protected void failed() {
taskHandler.handleErrorMessage(errorMessage);
}
}

View File

@ -1,97 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.common.taskrunner;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TaskRunner<T extends Model> {
private static final Logger log = LoggerFactory.getLogger(TaskRunner.class);
private final Queue<Class<? extends Task>> tasks = new LinkedBlockingQueue<>();
protected final T sharedModel;
private final Class<T> sharedModelClass;
private final ResultHandler resultHandler;
private final ErrorMessageHandler errorMessageHandler;
private boolean failed = false;
private boolean isCanceled;
private Class<? extends Task> currentTask;
public TaskRunner(T sharedModel, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
this(sharedModel, (Class<T>) sharedModel.getClass(), resultHandler, errorMessageHandler);
}
public TaskRunner(T sharedModel, Class<T> sharedModelClass, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
this.sharedModel = sharedModel;
this.resultHandler = resultHandler;
this.errorMessageHandler = errorMessageHandler;
this.sharedModelClass = sharedModelClass;
}
public final void addTasks(Class<? extends Task<T>>... items) {
tasks.addAll(Arrays.asList(items));
}
public void run() {
next();
}
protected void next() {
if (!failed && !isCanceled) {
if (tasks.size() > 0) {
try {
currentTask = tasks.poll();
log.trace("Run task: " + currentTask.getSimpleName());
currentTask.getDeclaredConstructor(TaskRunner.class, sharedModelClass).newInstance(this, sharedModel).run();
} catch (Throwable throwable) {
throwable.printStackTrace();
handleErrorMessage("Error at taskRunner: " + throwable.getMessage());
}
}
else {
resultHandler.handleResult();
}
}
}
public void cancel() {
isCanceled = true;
}
void handleComplete() {
log.trace("Task completed: " + currentTask.getSimpleName());
sharedModel.persist();
next();
}
void handleErrorMessage(String errorMessage) {
log.error("Task failed: " + currentTask.getSimpleName());
log.error("errorMessage: " + errorMessage);
failed = true;
errorMessageHandler.handleErrorMessage(errorMessage);
}
}

View File

@ -1,228 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import io.bitsquare.p2p.Message;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import com.google.common.base.Charsets;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.SignedObject;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class CryptoService<T> {
private static final Logger log = LoggerFactory.getLogger(CryptoService.class);
public static final String DHT_SIGN_KEY_ALGO = "DSA";
public static final String MSG_SIGN_KEY_ALGO = "DSA";
public static final String MSG_ENCR_KEY_ALGO = "RSA";
private static final String SYM_ENCR_KEY_ALGO = "AES";
private static final String SYM_CIPHER = "AES";
private static final String ASYM_CIPHER = "RSA"; //RSA/ECB/PKCS1Padding
private static final String MSG_SIGN_ALGO = "SHA1withDSA";
public static KeyPair generateDhtSignatureKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(DHT_SIGN_KEY_ALGO);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.debug("Generate dhtSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
public static KeyPair generateMsgSignatureKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(MSG_SIGN_KEY_ALGO);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.debug("Generate dhtSignatureKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
public static KeyPair generateMsgEncryptionKeyPair() throws NoSuchAlgorithmException {
long ts = System.currentTimeMillis();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(MSG_ENCR_KEY_ALGO);
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.debug("Generate msgEncryptionKeyPair needed {} ms", System.currentTimeMillis() - ts);
return keyPair;
}
static {
Security.addProvider(new BouncyCastleProvider());
}
private KeyRing keyRing;
@Inject
public CryptoService(KeyRing keyRing) {
this.keyRing = keyRing;
}
public SealedAndSignedMessage encryptAndSignMessage(PubKeyRing pubKeyRing, Message message) throws CryptoException {
long ts = System.currentTimeMillis();
try {
// Create symmetric key
KeyGenerator keyGenerator = KeyGenerator.getInstance(SYM_ENCR_KEY_ALGO);
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
// Encrypt secretKey with peers pubKey using SealedObject
Cipher cipherAsym = Cipher.getInstance(ASYM_CIPHER);
cipherAsym.init(Cipher.ENCRYPT_MODE, pubKeyRing.getMsgEncryptionPubKey());
SealedObject sealedSecretKey = new SealedObject(secretKey, cipherAsym);
// Sign (hash of) message and pack it into SignedObject
SignedObject signedMessage = new SignedObject(message, keyRing.getMsgSignatureKeyPair().getPrivate(), Signature.getInstance(MSG_SIGN_ALGO));
// // Encrypt signedMessage with secretKey using SealedObject
Cipher cipherSym = Cipher.getInstance(SYM_CIPHER);
cipherSym.init(Cipher.ENCRYPT_MODE, secretKey);
SealedObject sealedMessage = new SealedObject(signedMessage, cipherSym);
SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(sealedSecretKey,
sealedMessage,
keyRing.getMsgSignatureKeyPair().getPublic()
);
log.debug("Encryption needed {} ms", System.currentTimeMillis() - ts);
return sealedAndSignedMessage;
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
| IllegalBlockSizeException | IOException | SignatureException e) {
throw new CryptoException(e);
}
}
public MessageWithPubKey decryptAndVerifyMessage(SealedAndSignedMessage sealedAndSignedMessage) throws CryptoException {
long ts = System.currentTimeMillis();
try {
SealedObject sealedSecretKey = sealedAndSignedMessage.getSealedSecretKey();
SealedObject sealedMessage = sealedAndSignedMessage.getSealedMessage();
PublicKey signaturePubKey = sealedAndSignedMessage.getSignaturePubKey();
// Decrypt secretKey with my privKey
Cipher cipherAsym = Cipher.getInstance(ASYM_CIPHER);
cipherAsym.init(Cipher.DECRYPT_MODE, keyRing.getMsgEncryptionKeyPair().getPrivate());
Object secretKeyObject = sealedSecretKey.getObject(cipherAsym);
if (secretKeyObject instanceof SecretKey) {
SecretKey secretKey = (SecretKey) secretKeyObject;
// Decrypt signedMessage with secretKey
Cipher cipherSym = Cipher.getInstance(SYM_CIPHER);
cipherSym.init(Cipher.DECRYPT_MODE, secretKey);
Object signedMessageObject = sealedMessage.getObject(cipherSym);
if (signedMessageObject instanceof SignedObject) {
SignedObject signedMessage = (SignedObject) signedMessageObject;
// Verify message with peers pubKey
if (signedMessage.verify(signaturePubKey, Signature.getInstance(MSG_SIGN_ALGO))) {
// Get message
Object messageObject = signedMessage.getObject();
if (messageObject instanceof Message) {
log.debug("Decryption needed {} ms", System.currentTimeMillis() - ts);
return new MessageWithPubKey((Message) messageObject, signaturePubKey);
}
else {
throw new CryptoException("messageObject is not instance of Message");
}
}
else {
throw new CryptoException("Signature is not valid");
}
}
else {
throw new CryptoException("signedMessageObject is not instance of SignedObject");
}
}
else {
throw new CryptoException("secretKeyObject is not instance of SecretKey");
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException |
ClassNotFoundException | IllegalBlockSizeException | IOException | SignatureException e) {
throw new CryptoException(e);
}
}
public String signMessage(ECKey key, Sha256Hash hash) {
ECKey.ECDSASignature sig = key.sign(hash, null);
// Now we have to work backwards to figure out the recId needed to recover the signature.
int recId = -1;
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, hash, key.isCompressed());
if (k != null && k.getPubKeyPoint().equals(key.getPubKeyPoint())) {
recId = i;
break;
}
}
if (recId == -1)
throw new RuntimeException("Could not construct a recoverable key. This should never happen.");
int headerByte = recId + 27 + (key.isCompressed() ? 4 : 0);
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
sigData[0] = (byte) headerByte;
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
return new String(Base64.encode(sigData), Charsets.UTF_8);
}
public byte[] digestMessageWithSignature(ECKey key, String message) {
String signedMessage = signMessage(key, message);
return Utils.sha256hash160(message.concat(signedMessage).getBytes(Charsets.UTF_8));
}
public String signMessage(ECKey key, String message) {
byte[] data = Utils.formatMessageForSigning(message);
Sha256Hash hash = Sha256Hash.hashTwice(data);
return signMessage(key, hash);
}
public Sha256Hash hash(String message) {
byte[] data = Utils.formatMessageForSigning(message);
return Sha256Hash.hashTwice(data);
}
}

View File

@ -1,46 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import io.bitsquare.p2p.Message;
import java.security.PublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MessageWithPubKey implements Message {
private static final Logger log = LoggerFactory.getLogger(MessageWithPubKey.class);
public Message getMessage() {
return message;
}
public PublicKey getSignaturePubKey() {
return signaturePubKey;
}
private final Message message;
private final PublicKey signaturePubKey;
public MessageWithPubKey(Message message, PublicKey signaturePubKey) {
this.message = message;
this.signaturePubKey = signaturePubKey;
}
}

View File

@ -0,0 +1,40 @@
package io.bitsquare.crypto;
import com.google.protobuf.ByteString;
import io.bitsquare.common.UserThread;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.wallet.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
//TODO
public class ScryptUtil {
private static final Logger log = LoggerFactory.getLogger(ScryptUtil.class);
public ScryptUtil() {
}
public static final Protos.ScryptParameters SCRYPT_PARAMETERS = Protos.ScryptParameters.newBuilder()
.setP(6)
.setR(8)
.setN(32768)
.setSalt(ByteString.copyFrom(KeyCrypterScrypt.randomSalt()))
.build();
public interface ScryptKeyDerivationResultHandler {
void handleResult(KeyParameter aesKey);
}
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, ScryptKeyDerivationResultHandler resultHandler) {
new Thread(() -> {
log.info("Doing key derivation");
long start = System.currentTimeMillis();
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
long duration = System.currentTimeMillis() - start;
log.info("Key derivation took {} msec", duration);
UserThread.execute(() -> resultHandler.handleResult(aesKey));
}).start();
}
}

View File

@ -1,64 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Message;
import java.io.Serializable;
import java.security.PublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.SealedObject;
/**
* Packs the encrypted symmetric secretKey and the encrypted signed message into one object.
* SecretKey is encrypted with asymmetric pubKey of peer. Signed message is encrypted with secretKey.
* Using that hybrid encryption model we are not restricted by data size and performance as symmetric encryption is very fast.
*/
public class SealedAndSignedMessage implements Serializable, Message {
// 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 Logger log = LoggerFactory.getLogger(SealedAndSignedMessage.class);
private final SealedObject sealedSecretKey;
private final SealedObject sealedMessage;
private final PublicKey signaturePubKey;
public SealedAndSignedMessage(SealedObject sealedSecretKey, SealedObject sealedMessage, PublicKey signaturePubKey) {
this.sealedSecretKey = sealedSecretKey;
this.sealedMessage = sealedMessage;
this.signaturePubKey = signaturePubKey;
}
public SealedObject getSealedSecretKey() {
return sealedSecretKey;
}
public SealedObject getSealedMessage() {
return sealedMessage;
}
public PublicKey getSignaturePubKey() {
return signaturePubKey;
}
}

View File

@ -1,47 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import org.bitcoinj.core.Utils;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Util {
private static final Logger log = LoggerFactory.getLogger(Util.class);
public static String pubKeyToString(PublicKey publicKey) {
final X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
return Base64.getEncoder().encodeToString(x509EncodedKeySpec.getEncoded());
}
// TODO just temp for arbitrator
public static PublicKey decodeDSAPubKeyHex(String pubKeyHex) throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Utils.HEX.decode(pubKeyHex));
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
return keyFactory.generatePublic(pubKeySpec);
}
}

View File

@ -1,128 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.fiat;
import io.bitsquare.app.Version;
import io.bitsquare.locale.Country;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.concurrent.Immutable;
@Immutable
public class FiatAccount implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public static final long HOUR_IN_BLOCKS = 6;
public static final long DAY_IN_BLOCKS = HOUR_IN_BLOCKS * 24;
public static final long WEEK_IN_BLOCKS = DAY_IN_BLOCKS * 7;
public enum Type {
IRC("", "", 0),
SEPA("IBAN", "BIC", WEEK_IN_BLOCKS),
WIRE("primary ID", "secondary ID", WEEK_IN_BLOCKS),
INTERNATIONAL("primary ID", "secondary ID", 2 * WEEK_IN_BLOCKS),
OK_PAY("primary ID", "secondary ID", HOUR_IN_BLOCKS),
NET_TELLER("primary ID", "secondary ID", HOUR_IN_BLOCKS),
PERFECT_MONEY("primary ID", "secondary ID", HOUR_IN_BLOCKS);
public final String primaryId;
public final String secondaryId;
public final long lockTimeDelta;
Type(String primaryId, String secondaryId, long lockTimeDelta) {
this.primaryId = primaryId;
this.secondaryId = secondaryId;
this.lockTimeDelta = lockTimeDelta;
}
public static ArrayList<Type> getAllBankAccountTypes() {
return new ArrayList<>(Arrays.asList(Type.values()));
}
}
public final String id;
public final String nameOfBank;
public final Type type;
public final Country country; // where bank is registered
public final String accountPrimaryID; // like IBAN
public final String accountSecondaryID; // like BIC
public final String accountHolderName;
// The main currency if account support multiple currencies.
// The user can create multiple bank accounts with same bank account but other currency if his bank account
// support that.
public final String currencyCode;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public FiatAccount(Type type, String currencyCode, Country country, String nameOfBank,
String accountHolderName, String accountPrimaryID, String accountSecondaryID) {
this.type = type;
this.currencyCode = currencyCode;
this.country = country;
this.nameOfBank = nameOfBank;
this.accountHolderName = accountHolderName;
this.accountPrimaryID = accountPrimaryID;
this.accountSecondaryID = accountSecondaryID;
id = nameOfBank;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public int hashCode() {
return Objects.hashCode(id);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof FiatAccount)) return false;
if (obj == this) return true;
final FiatAccount other = (FiatAccount) obj;
return id.equals(other.id);
}
@Override
public String toString() {
return "FiatAccount{" +
"id='" + id + '\'' +
", nameOfBank='" + nameOfBank + '\'' +
", type=" + type +
", country=" + country +
", accountPrimaryID='" + accountPrimaryID + '\'' +
", accountSecondaryID='" + accountSecondaryID + '\'' +
", accountHolderName='" + accountHolderName + '\'' +
", currencyCode='" + currencyCode + '\'' +
'}';
}
}

View File

@ -17,23 +17,21 @@
package io.bitsquare.locale; package io.bitsquare.locale;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Locale; import java.util.Locale;
import java.util.MissingResourceException; import java.util.MissingResourceException;
import java.util.PropertyResourceBundle; import java.util.PropertyResourceBundle;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BSResources { public class BSResources {
private static final Logger log = LoggerFactory.getLogger(BSResources.class); private static final Logger log = LoggerFactory.getLogger(BSResources.class);
@ -61,7 +59,7 @@ public class BSResources {
// Adds UTF8 support for property files // Adds UTF8 support for property files
class UTF8Control extends ResourceBundle.Control { class UTF8Control extends ResourceBundle.Control {
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) public ResourceBundle newBundle(String baseName, @NotNull Locale locale, @NotNull String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException { throws IllegalAccessException, InstantiationException, IOException {
// The below is a copy of the default implementation. // The below is a copy of the default implementation.
final String bundleName = toBundleName(baseName, locale); final String bundleName = toBundleName(baseName, locale);
@ -77,8 +75,7 @@ class UTF8Control extends ResourceBundle.Control {
stream = connection.getInputStream(); stream = connection.getInputStream();
} }
} }
} } else {
else {
stream = loader.getResourceAsStream(resourceName); stream = loader.getResourceAsStream(resourceName);
} }
if (stream != null) { if (stream != null) {

View File

@ -19,14 +19,13 @@ package io.bitsquare.locale;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import java.io.Serializable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import java.io.Serializable;
@Immutable @Immutable
public class Country implements Serializable { public class Country implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility. // That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
public final String code; public final String code;
public final String name; public final String name;

View File

@ -19,16 +19,12 @@ package io.bitsquare.locale;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import io.bitsquare.user.Preferences;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class CountryUtil { public class CountryUtil {
public static List<Region> getAllRegions() { public static List<Region> getAllRegions() {
final List<Region> allRegions = new ArrayList<>(); final List<Region> allRegions = new ArrayList<>();
@ -59,19 +55,62 @@ public class CountryUtil {
return allRegions; return allRegions;
} }
public static List<Country> getAllEuroCountries() { public static List<Country> getAllSepaEuroCountries() {
List<Country> allEuroCountries = new ArrayList<>(); List<Country> list = new ArrayList<>();
String[] code = {"BE", "DE", "EE", "FI", "FR", "GR", "IE", "IT", "LV", "LU", "MT", "NL", "PT", "SK", "SI", String[] codes = {"AT", "BE", "CY", "DE", "EE", "FI", "FR", "GR", "IE",
"ES", "AT", "CY"}; "IT", "LV", "LT", "LU", "MC", "MT", "NL", "PT", "SK", "SI", "ES"};
for (String aCode : code) { for (String code : codes) {
Locale locale = new Locale("", aCode, ""); Locale locale = new Locale(LanguageUtil.getDefaultLanguage(), code, "");
String regionCode = getRegionCode(locale.getCountry()); String regionCode = getRegionCode(locale.getCountry());
final Region region = new Region(regionCode, getRegionName(regionCode)); final Region region = new Region(regionCode, getRegionName(regionCode));
final Country country = new Country(locale.getCountry(), locale.getDisplayCountry(), region); final Country country = new Country(locale.getCountry(), locale.getDisplayCountry(), region);
allEuroCountries.add(country); list.add(country);
} }
list.sort((a, b) -> a.code.compareTo(b.code));
return allEuroCountries; return list;
}
public static boolean containsAllSepaEuroCountries(List<String> countryCodesToCompare) {
countryCodesToCompare.sort((a, b) -> a.compareTo(b));
List<String> countryCodesBase = getAllSepaEuroCountries().stream().map(c -> c.code).collect(Collectors.toList());
return countryCodesToCompare.toString().equals(countryCodesBase.toString());
/*
List<Country> countriesBase = getAllSepaEuroCountries();
List<Country> remainingBase = new ArrayList<>(countriesBase);
List<String> remainingToCompare = new ArrayList<>(countryCodesToCompare);
for (int i = 0; i < countriesBase.size(); i++) {
String countryCodeBase = countriesBase.get(i).code;
for (int n = 0; n < countryCodesToCompare.size(); n++) {
if (countryCodeBase.equals(countryCodesToCompare.get(n))) {
if (remainingBase.size() > 0) remainingBase.remove(i);
if (remainingToCompare.size() > 0) remainingToCompare.remove(n);
}
}
}
return remainingBase.size() == 0 && remainingBase.size() == remainingToCompare.size();*/
}
public static List<Country> getAllSepaNonEuroCountries() {
List<Country> list = new ArrayList<>();
String[] codes = {"BG", "HR", "CZ", "DK", "GB", "HU", "PL", "RO",
"SE", "IS", "NO", "LI", "CH"};
for (String code : codes) {
Locale locale = new Locale(LanguageUtil.getDefaultLanguage(), code, "");
String regionCode = getRegionCode(locale.getCountry());
final Region region = new Region(regionCode, getRegionName(regionCode));
final Country country = new Country(locale.getCountry(), locale.getDisplayCountry(), region);
list.add(country);
}
list.sort((a, b) -> a.code.compareTo(b.code));
return list;
}
public static List<Country> getAllSepaCountries() {
List<Country> list = new ArrayList<>();
list.addAll(getAllSepaEuroCountries());
list.addAll(getAllSepaNonEuroCountries());
return list;
} }
public static List<Country> getAllCountriesFor(Region selectedRegion) { public static List<Country> getAllCountriesFor(Region selectedRegion) {
@ -80,7 +119,7 @@ public class CountryUtil {
} }
public static Country getDefaultCountry() { public static Country getDefaultCountry() {
final Locale locale = new Locale("", Locale.getDefault().getCountry()); final Locale locale = new Locale(LanguageUtil.getDefaultLanguage(), getDefaultCountryCode());
String regionCode = getRegionCode(locale.getCountry()); String regionCode = getRegionCode(locale.getCountry());
final Region region = new Region(regionCode, getRegionName(regionCode)); final Region region = new Region(regionCode, getRegionName(regionCode));
return new Country(locale.getCountry(), locale.getDisplayCountry(), region); return new Country(locale.getCountry(), locale.getDisplayCountry(), region);
@ -98,6 +137,22 @@ public class CountryUtil {
return allCountries; return allCountries;
} }
public static String getNameByCode(String countryCode) {
return new Locale(LanguageUtil.getDefaultLanguage(), countryCode).getDisplayCountry();
}
public static List<String> getNamesByCodes(List<String> countryCodes) {
return countryCodes.stream().map(c -> getNameByCode(c)).collect(Collectors.toList());
}
public static String getCodesString(List<String> countryCodes) {
return countryCodes.stream().collect(Collectors.joining(", "));
}
public static String getNamesByCodesString(List<String> countryCodes) {
return getNamesByCodes(countryCodes).stream().collect(Collectors.joining(", "));
}
private static final String[] countryCodes = new String[]{"AE", "AL", "AR", "AT", "AU", "BA", "BE", "BG", "BH", private static final String[] countryCodes = new String[]{"AE", "AL", "AR", "AT", "AU", "BA", "BE", "BG", "BH",
"BO", "BR", "BY", "CA", "CH", "CL", "CN", "CO", "CR", "CS", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ", "BO", "BR", "BY", "CA", "CH", "CL", "CN", "CO", "CR", "CS", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ",
"EC", "EE", "EG", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HR", "HU", "ID", "IE", "IL", "IN", "EC", "EE", "EG", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HR", "HU", "ID", "IE", "IL", "IN",
@ -133,7 +188,7 @@ public class CountryUtil {
private static List<Locale> getAllCountryLocales() { private static List<Locale> getAllCountryLocales() {
List<Locale> allLocales = Arrays.asList(Locale.getAvailableLocales()); List<Locale> allLocales = Arrays.asList(Locale.getAvailableLocales());
Set<Locale> allLocalesAsSet = allLocales.stream().filter(locale -> !"".equals(locale.getCountry())) Set<Locale> allLocalesAsSet = allLocales.stream().filter(locale -> !"".equals(locale.getCountry()))
.map(locale -> new Locale("", locale.getCountry(), "")) .map(locale -> new Locale(LanguageUtil.getDefaultLanguage(), locale.getCountry(), ""))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
allLocales = new ArrayList<>(); allLocales = new ArrayList<>();
@ -145,10 +200,14 @@ public class CountryUtil {
private static String getRegionCode(String countryCode) { private static String getRegionCode(String countryCode) {
if (!countryCode.isEmpty() && countryCodeList.contains(countryCode)) { if (!countryCode.isEmpty() && countryCodeList.contains(countryCode)) {
return regionCodeList.get(countryCodeList.indexOf(countryCode)); return regionCodeList.get(countryCodeList.indexOf(countryCode));
} } else {
else {
return "Undefined"; return "Undefined";
} }
} }
public static String getDefaultCountryCode() {
// might be set later in pref or config, so not use Preferences.getDefaultLocale() anywhere in the code
return Preferences.getDefaultLocale().getCountry();
}
} }

View File

@ -17,59 +17,196 @@
package io.bitsquare.locale; package io.bitsquare.locale;
import java.util.ArrayList; import io.bitsquare.user.Preferences;
import java.util.Currency; import org.slf4j.Logger;
import java.util.List; import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class CurrencyUtil { public class CurrencyUtil {
transient private static final Logger log = LoggerFactory.getLogger(CurrencyUtil.class);
public static List<String> getAllCurrencyCodes() { private static List<TradeCurrency> allSortedCurrencies = createAllSortedCurrenciesList();
return getAllCurrencies().stream().map(Currency::getCurrencyCode).collect(Collectors.toList());
public static List<TradeCurrency> getAllSortedCurrencies() {
return allSortedCurrencies;
} }
public static String getDefaultCurrencyAsCode() { // We add all currencies supported by payment methods
return getDefaultCurrency().getCurrencyCode(); private static List<TradeCurrency> createAllSortedCurrenciesList() {
Set<TradeCurrency> set = new HashSet<>();
// Sepa: EUR at first place
set.addAll(getSortedSepaCurrencyCodes());
// PerfectMoney:
set.add(new TradeCurrency("USD"));
// Alipay:
set.add(new TradeCurrency("CNY"));
// OKPay: We want to maintain the order so we don't use a Set but add items if nto already in list
getAllOKPayCurrencies().stream().forEach(e -> set.add(e));
// Swish: it is already added by Sepa
// for printing out all codes
/* String res;
result.stream().forEach(e -> {
res += "list.add(new FiatCurrency(\""+e.code+"\"));\n";
});
log.debug(res);*/
List<TradeCurrency> list = getAllManuallySortedFiatCurrencies();
// check if the list derived form the payment methods is containing exactly the same as our manually sorted one
List<String> list1 = set.stream().map(e -> e.code).collect(Collectors.toList());
list1.sort((a, b) -> a.compareTo(b));
List<String> list2 = list.stream().map(e -> e.code).collect(Collectors.toList());
list2.sort((a, b) -> a.compareTo(b));
if (list1.size() != list2.size()) {
log.error("manually defined currencies are not matching currencies derived form our payment methods");
log.error("list1 " + list1.toString());
log.error("list2 " + list2.toString());
}
if (!list1.toString().equals(list2.toString())) {
log.error("List derived form the payment methods is not matching exactly the same as our manually sorted one");
log.error("list1 " + list1.toString());
log.error("list2 " + list2.toString());
}
// Blockchain
getSortedCryptoCurrencies().stream().forEach(e -> list.add(e));
return list;
} }
public static String getDisplayName(String currencyCode) { private static List<TradeCurrency> getAllManuallySortedFiatCurrencies() {
return Currency.getInstance(currencyCode).getDisplayName(); List<TradeCurrency> list = new ArrayList<>();
list.add(new FiatCurrency("EUR"));
list.add(new FiatCurrency("USD"));
list.add(new FiatCurrency("GBP"));
list.add(new FiatCurrency("CNY"));
list.add(new FiatCurrency("HKD"));
list.add(new FiatCurrency("CHF"));
list.add(new FiatCurrency("JPY"));
list.add(new FiatCurrency("CAD"));
list.add(new FiatCurrency("AUD"));
list.add(new FiatCurrency("NZD"));
list.add(new FiatCurrency("ZAR"));
list.add(new FiatCurrency("RUB"));
list.add(new FiatCurrency("SEK"));
list.add(new FiatCurrency("NOK"));
list.add(new FiatCurrency("DKK"));
list.add(new FiatCurrency("ISK"));
list.add(new FiatCurrency("PLN"));
list.add(new FiatCurrency("CZK"));
list.add(new FiatCurrency("TRY"));
list.add(new FiatCurrency("BGN"));
list.add(new FiatCurrency("HRK"));
list.add(new FiatCurrency("HUF"));
list.add(new FiatCurrency("RON"));
return list;
} }
private static List<Currency> getAllCurrencies() { /**
final ArrayList<Currency> mainCurrencies = new ArrayList<>(); * @return Sorted list of sepa currencies with EUR as first item
mainCurrencies.add(Currency.getInstance("EUR")); */
mainCurrencies.add(Currency.getInstance("USD")); public static Set<TradeCurrency> getSortedSepaCurrencyCodes() {
mainCurrencies.add(Currency.getInstance("CNY")); return CountryUtil.getAllSepaCountries().stream()
mainCurrencies.add(Currency.getInstance("RUB")); .map(country -> getCurrencyByCountryCode(country.code))
mainCurrencies.add(Currency.getInstance("JPY")); .collect(Collectors.toSet());
mainCurrencies.add(Currency.getInstance("GBP"));
mainCurrencies.add(Currency.getInstance("CAD"));
mainCurrencies.add(Currency.getInstance("AUD"));
mainCurrencies.add(Currency.getInstance("CHF"));
mainCurrencies.add(Currency.getInstance("CNY"));
Set<Currency> allCurrenciesSet = Currency.getAvailableCurrencies();
allCurrenciesSet.removeAll(mainCurrencies);
final List<Currency> allCurrenciesList = new ArrayList<>(allCurrenciesSet);
allCurrenciesList.sort((a, b) -> a.getCurrencyCode().compareTo(b.getCurrencyCode()));
final List<Currency> resultList = new ArrayList<>(mainCurrencies);
resultList.addAll(allCurrenciesList);
resultList.remove(getDefaultCurrency());
resultList.add(0, getDefaultCurrency());
return resultList;
} }
private static Currency getDefaultCurrency() { // At OKPay you can exchange internally those currencies
// TODO Only display EUR for the moment public static List<TradeCurrency> getAllOKPayCurrencies() {
return Currency.getInstance("EUR"); return new ArrayList<>(Arrays.asList(
// return Currency.getInstance(Locale.getDefault()); new FiatCurrency("EUR"),
new FiatCurrency("USD"),
new FiatCurrency("GBP"),
new FiatCurrency("CHF"),
new FiatCurrency("RUB"),
new FiatCurrency("PLN"),
new FiatCurrency("JPY"),
new FiatCurrency("CAD"),
new FiatCurrency("AUD"),
new FiatCurrency("CZK"),
new FiatCurrency("NOK"),
new FiatCurrency("SEK"),
new FiatCurrency("DKK"),
new FiatCurrency("HRK"),
new FiatCurrency("HUF"),
new FiatCurrency("NZD"),
new FiatCurrency("RON"),
new FiatCurrency("TRY"),
new FiatCurrency("ZAR"),
new FiatCurrency("HKD"),
new FiatCurrency("CNY")
));
} }
public static List<TradeCurrency> getSortedCryptoCurrencies() {
final List<TradeCurrency> result = new ArrayList<>();
result.add(new CryptoCurrency("ETH", "Ethereum"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("NMC", "Namecoin"));
// Unfortunately we cannot support CryptoNote coins yet as there is no way to proof the transaction. Payment ID helps only locate the tx but the
// arbitrator cannot see if the receiving key matches the receivers address. They might add support for exposing the tx key, but that is not
// implemented yet. To use the view key (also not available in GUI wallets) would reveal the complete wallet history for incoming payments, which is
// not acceptable from pricavy point of view.
// result.add(new CryptoCurrency("XMR", "Monero"));
// result.add(new CryptoCurrency("BCN", "Bytecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("ANC", "Anoncoin"));
result.add(new CryptoCurrency("NBT", "NuBits"));
result.add(new CryptoCurrency("NSR", "NuShares"));
result.add(new CryptoCurrency("FAIR", "FairCoin"));
result.add(new CryptoCurrency("PPC", "Peercoin"));
result.add(new CryptoCurrency("XPM", "Primecoin"));
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
result.add(new CryptoCurrency("NXT", "Nxt"));
result.add(new CryptoCurrency("BTS", "BitShares"));
result.add(new CryptoCurrency("XCP", "Counterparty"));
result.add(new CryptoCurrency("XRP", "Ripple"));
result.add(new CryptoCurrency("STR", "Stellar"));
return result;
}
public static boolean isCryptoNoteCoin(String currencyCode) {
return currencyCode.equals("XMR") || currencyCode.equals("BCN");
}
public static FiatCurrency getCurrencyByCountryCode(String countryCode) {
// java 1.8.8_0_20 reports wrong currency (Lita instead of EUR)
if (!countryCode.equals("LT"))
return new FiatCurrency(Currency.getInstance(new Locale(LanguageUtil.getDefaultLanguage(), countryCode)).getCurrencyCode());
else {
return new FiatCurrency("EUR");
}
}
public static String getNameByCode(String currencyCode) {
try {
return Currency.getInstance(currencyCode).getDisplayName(Preferences.getDefaultLocale());
} catch (Throwable t) {
// Seems that it is a crypto currency
return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findFirst().get().getCodeAndName();
}
}
public static FiatCurrency getDefaultFiatCurrency() {
return new FiatCurrency(getCurrencyByCountryCode(CountryUtil.getDefaultCountryCode()).getCurrency());
}
} }

View File

@ -17,38 +17,42 @@
package io.bitsquare.locale; package io.bitsquare.locale;
import java.util.ArrayList; import io.bitsquare.user.Preferences;
import java.util.Arrays; import org.slf4j.Logger;
import java.util.List; import org.slf4j.LoggerFactory;
import java.util.Locale;
import java.util.Set; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class LanguageUtil { public class LanguageUtil {
private static final Logger log = LoggerFactory.getLogger(LanguageUtil.class);
public static List<String> getAllLanguageLocaleCodes() { public static List<String> getAllLanguageCodes() {
List<Locale> allLocales = Arrays.asList(Locale.getAvailableLocales()); List<Locale> allLocales = Arrays.asList(Locale.getAvailableLocales());
final Set<String> allLocaleCodesAsSet = final Set<String> allLocaleCodesAsSet = allLocales.stream()
allLocales.stream().filter(locale -> !"".equals(locale.getLanguage())).map(locale -> .filter(locale -> !"".equals(locale.getLanguage()) && !"".equals(locale.getDisplayLanguage()))
new Locale(locale.getLanguage(), "").getISO3Language()).collect(Collectors.toSet()); .map(locale -> new Locale(locale.getLanguage(), "").getLanguage())
.collect(Collectors.toSet());
List<String> allLocaleCodes = new ArrayList<>(); List<String> allLocaleCodes = new ArrayList<>();
allLocaleCodes.addAll(allLocaleCodesAsSet); allLocaleCodes.addAll(allLocaleCodesAsSet);
allLocaleCodes.sort(String::compareTo); allLocaleCodes.sort((o1, o2) -> getDisplayName(o1).compareTo(getDisplayName(o2)));
return allLocaleCodes; return allLocaleCodes;
} }
public static String getDefaultLanguage() {
// might be set later in pref or config, so not use Preferences.getDefaultLocale() anywhere in the code
return Preferences.getDefaultLocale().getLanguage();
}
public static String getDefaultLanguageLocaleAsCode() { public static String getDefaultLanguageLocaleAsCode() {
if (Locale.getDefault() != null) return new Locale(LanguageUtil.getDefaultLanguage(), "").getLanguage();
return new Locale(Locale.getDefault().getLanguage(), "").getISO3Language();
else
return getEnglishLanguageLocaleCode();
} }
public static String getEnglishLanguageLocaleCode() { public static String getEnglishLanguageLocaleCode() {
return new Locale(Locale.ENGLISH.getLanguage(), "").getISO3Language(); return new Locale(Locale.ENGLISH.getLanguage(), "").getLanguage();
} }
public static String getDisplayName(String code) { public static String getDisplayName(String code) {
return new Locale(code).getDisplayName(); return new Locale(code.toUpperCase()).getDisplayName(Preferences.getDefaultLocale());
} }
} }

View File

@ -19,17 +19,16 @@ package io.bitsquare.locale;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import java.io.Serializable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import java.io.Serializable;
@Immutable @Immutable
public class Region implements Serializable { public class Region implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility. // That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
public final String code; private final String code;
public final String name; private final String name;
public Region(String code, String name) { public Region(String code, String name) {
this.code = code; this.code = code;

View File

@ -1,26 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.p2p.listener.GetPeerAddressListener;
public interface AddressService extends DHTService {
void findPeerAddress(PubKeyRing pubKeyRing, GetPeerAddressListener getPeerAddressListener);
}

View File

@ -1,71 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.util.concurrent.Executor;
import net.tomp2p.dht.PeerDHT;
public class BaseP2PService implements P2PService {
private static Executor userThread;
public static void setUserThread(Executor userThread) {
BaseP2PService.userThread = userThread;
}
public static Executor getUserThread() {
return userThread;
}
protected Executor executor;
protected PeerDHT peerDHT;
private int openRequests = 0;
@Override
public void bootstrapCompleted() {
this.executor = BaseP2PService.userThread;
}
@Override
public void setExecutor(Executor executor) {
this.executor = executor;
}
protected void openRequestsUp() {
executor.execute(() -> openRequests++);
}
protected void openRequestsDown() {
executor.execute(() -> openRequests--);
}
@Override
public void shutDown() {
long ts = System.currentTimeMillis();
// wait max. 10 sec. for open calls to complete
while (openRequests > 0 && (System.currentTimeMillis() - ts) < 10000) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@ -1,139 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.BitsquareException;
import com.google.inject.name.Named;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import javax.inject.Inject;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BootstrapNodes {
private static final Logger log = LoggerFactory.getLogger(BootstrapNodes.class);
public static final String BOOTSTRAP_NODE_KEY = "bootstrapNode";
private final List<Node> rawBootstrapNodes = Arrays.asList(
Node.rawNodeAt("digitalocean1.bitsquare.io", "188.226.179.109"),
Node.rawNodeAt("aws1.bitsquare.io", "52.24.144.42"),
Node.rawNodeAt("aws2.bitsquare.io", "52.11.125.194")
);
private Node rawLocalhostNode = Node.rawNodeAt("localhost", "127.0.0.1");
private Node preferredBootstrapNode;
private List<Node> bootstrapNodes;
private Node localhostNode;
private int p2pId;
private boolean inited;
@Inject
public BootstrapNodes(@Named(BOOTSTRAP_NODE_KEY) Node preferredBootstrapNode) {
// preferredBootstrapNode need to be fully defined to get accepted (name, IP, p2pId, port)
if (preferredBootstrapNode.getName() != null
&& preferredBootstrapNode.getIp() != null
&& preferredBootstrapNode.getP2pId() != -1
&& preferredBootstrapNode.getPort() != -1) {
this.preferredBootstrapNode = preferredBootstrapNode;
}
else if (preferredBootstrapNode.getName() != null
|| preferredBootstrapNode.getIp() != null
|| preferredBootstrapNode.getP2pId() != -1
|| preferredBootstrapNode.getPort() != -1) {
log.debug("preferredBootstrapNode not fully defined (name, IP, p2pId, port). preferredBootstrapNode=" + preferredBootstrapNode);
}
}
public BootstrapNodes() {
}
public void initWithNetworkId(int p2pId) {
if (!inited) {
inited = true;
this.p2pId = p2pId;
if (preferredBootstrapNode != null) {
bootstrapNodes = Arrays.asList(preferredBootstrapNode);
}
else {
switch (p2pId) {
case Node.MAIN_NET_P2P_ID:
bootstrapNodes = rawBootstrapNodes.stream()
.map(e -> e.withP2pIdAndPort(Node.MAIN_NET_P2P_ID, Node.MAIN_NET_PORT)).collect(Collectors.toList());
localhostNode = rawLocalhostNode.withP2pIdAndPort(Node.MAIN_NET_P2P_ID, Node.MAIN_NET_PORT);
break;
case Node.TEST_NET_P2P_ID:
bootstrapNodes = rawBootstrapNodes.stream()
.map(e -> e.withP2pIdAndPort(Node.TEST_NET_P2P_ID, Node.TEST_NET_PORT)).collect(Collectors.toList());
localhostNode = rawLocalhostNode.withP2pIdAndPort(Node.TEST_NET_P2P_ID, Node.TEST_NET_PORT);
break;
case Node.REG_TEST_P2P_ID:
bootstrapNodes = rawBootstrapNodes.stream()
.map(e -> e.withP2pIdAndPort(Node.REG_TEST_P2P_ID, Node.REG_TEST_PORT)).collect(Collectors.toList());
localhostNode = rawLocalhostNode.withP2pIdAndPort(Node.REG_TEST_P2P_ID, Node.REG_TEST_PORT);
break;
default:
throw new BitsquareException("Unsupported P2pId. p2pId=" + p2pId);
}
}
}
else {
throw new BitsquareException("initWithNetworkId called twice");
}
}
public Node getRandomDiscoverNode() {
return bootstrapNodes.get(new Random().nextInt(bootstrapNodes.size()));
}
public List<Node> getBootstrapNodes() {
return bootstrapNodes;
}
public List<PeerAddress> getBootstrapPeerAddresses() {
return bootstrapNodes.stream().map(e -> {
try {
return new PeerAddress(Number160.createHash(e.getName()), InetAddress.getByName(e.getIp()), e.getPort(), e.getPort());
} catch (UnknownHostException e1) {
e1.printStackTrace();
log.error(e1.getMessage());
return null;
}
}).collect(Collectors.toList());
}
public Node getLocalhostNode() {
return localhostNode;
}
public int getP2pId() {
return p2pId;
}
}

View File

@ -1,40 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder;
import java.security.KeyPair;
import java.util.concurrent.Executor;
import javafx.beans.property.ReadOnlyIntegerProperty;
import rx.Observable;
public interface ClientNode {
BootstrappedPeerBuilder.ConnectionType getConnectionType();
String getClientNodeInfo();
Observable<BootstrappedPeerBuilder.State> bootstrap(KeyPair keyPair);
ReadOnlyIntegerProperty numPeersProperty();
void setExecutor(Executor executor);
}

View File

@ -1,45 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.security.PublicKey;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.FutureRemove;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
public interface DHTService extends P2PService {
FuturePut putData(Number160 locationKey, Data data);
FutureGet getData(Number160 locationKey);
FuturePut putDataToMyProtectedDomain(Number160 locationKey, Data data);
FutureRemove removeDataFromMyProtectedDomain(Number160 locationKey);
FutureGet getDataOfProtectedDomain(Number160 locationKey, PublicKey publicKey);
FuturePut addProtectedDataToMap(Number160 locationKey, Data data);
FutureRemove removeProtectedDataFromMap(Number160 locationKey, Data data);
FutureGet getMap(Number160 locationKey);
}

View File

@ -1,24 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.crypto.MessageWithPubKey;
public interface DecryptedMessageHandler {
void handleMessage(MessageWithPubKey message, Peer sender);
}

View File

@ -1,23 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.io.Serializable;
public interface MailboxMessage extends Message, Serializable {
}

View File

@ -1,26 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.crypto.SealedAndSignedMessage;
import java.util.List;
public interface MailboxMessagesResultHandler {
void handleResult(List<SealedAndSignedMessage> encryptedMessages);
}

View File

@ -1,33 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.crypto.SealedAndSignedMessage;
public interface MailboxService {
void addMessage(PubKeyRing pubKeyRing, SealedAndSignedMessage message, ResultHandler resultHandler, FaultHandler faultHandler);
void getAllMessages(MailboxMessagesResultHandler resultHandler);
void removeAllMessages(ResultHandler resultHandler, FaultHandler faultHandler);
void shutDown();
}

View File

@ -1,23 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.io.Serializable;
public interface Message extends Serializable {
}

View File

@ -1,22 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
public interface MessageHandler {
void handleMessage(Message message, Peer sender);
}

View File

@ -1,35 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.p2p.listener.SendMessageListener;
public interface MessageService extends P2PService {
void sendEncryptedMessage(Peer peer, PubKeyRing pubKeyRing, Message message, boolean useMailboxIfUnreachable, SendMessageListener listener);
void addMessageHandler(MessageHandler listener);
void removeMessageHandler(MessageHandler listener);
void addDecryptedMessageHandler(DecryptedMessageHandler listener);
void removeDecryptedMessageHandler(DecryptedMessageHandler listener);
}

View File

@ -1,40 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.io.IOException;
@SuppressWarnings("serializable")
public class NetworkException extends IOException {
private static final long serialVersionUID = 3635593267998809977L;
public NetworkException(Throwable cause) {
super(cause);
}
public NetworkException(String message) {
super(message);
}
public NetworkException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,163 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import com.google.common.base.Objects;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Node {
private static final Logger log = LoggerFactory.getLogger(Node.class);
public static final String NAME_KEY = "node.name";
public static final String PORT_KEY = "node.port";
public static final String P2P_ID_KEY = "node.p2pId";
//public static int DEFAULT_PORT = findFreeSystemPort();
// P2P network ids
public static final int MAIN_NET_P2P_ID = 10;
public static final int TEST_NET_P2P_ID = 11;
public static final int REG_TEST_P2P_ID = 12;
// ports
public static final int MAIN_NET_PORT = 7370;
public static final int TEST_NET_PORT = 7371;
public static final int REG_TEST_PORT = 7372;
private final String name;
private final String ip;
private final int port;
private final int p2pId;
private Node(String name, String ip, int p2pId, int port) {
this.name = name;
this.ip = ip;
this.p2pId = p2pId;
this.port = port;
}
// Not fully defined node
public static Node rawNodeAt(String name, String ip) {
return Node.at(name, ip, -1, -1);
}
public static Node at(String name, String ip, int p2pId, int port) {
return new Node(name, ip, p2pId, port);
}
public Node withP2pIdAndPort(int p2pId, int port) {
return Node.at(this.name, this.ip, p2pId, port);
}
public static final int CLIENT_PORT = findFreeSystemPort();
public static int findFreeSystemPort() {
int port = 7369;
try {
ServerSocket server = new ServerSocket(0);
port = server.getLocalPort();
log.debug("Random system port used for client: {}", port);
server.close();
} catch (IOException e) {
e.printStackTrace();
}
return port;
}
public PeerAddress toPeerAddressWithPort(int port) {
try {
return new PeerAddress(Number160.createHash(name),
InetAddress.getByName(ip),
port,
port);
} catch (UnknownHostException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
public PeerAddress toPeerAddress() {
try {
return new PeerAddress(Number160.createHash(name),
InetAddress.getByName(ip),
port,
port);
} catch (UnknownHostException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
public String getName() {
return name;
}
public String getIp() {
return ip;
}
public int getP2pId() {
return p2pId;
}
public int getPort() {
return port;
}
@Override
public boolean equals(Object object) {
if (this == object)
return true;
if (object == null || getClass() != object.getClass())
return false;
Node that = (Node) object;
return Objects.equal(this.name, that.name) &&
Objects.equal(this.ip, that.ip) &&
Objects.equal(this.port, that.port);
}
@Override
public int hashCode() {
return Objects.hashCode(name, ip, port);
}
@Override
public String toString() {
return "Name='" + name + '\'' +
"; IP='" + ip + '\'' +
"; port=" + port +
"; P2P network ID=" + p2pId;
}
}

View File

@ -1,37 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import io.bitsquare.BitsquareModule;
import org.springframework.core.env.Environment;
public abstract class P2PModule extends BitsquareModule {
protected P2PModule(Environment env) {
super(env);
}
@Override
protected final void configure() {
doConfigure();
}
protected void doConfigure() {
}
}

View File

@ -1,28 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import java.util.concurrent.Executor;
public interface P2PService {
void bootstrapCompleted();
void setExecutor(Executor executor);
void shutDown();
}

View File

@ -1,26 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
/**
* A peer on the Bitsquare network.
*
* @author Chris Beams
*/
public interface Peer {
}

View File

@ -1,26 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.listener;
import io.bitsquare.p2p.Peer;
public interface GetPeerAddressListener {
void onResult(Peer peer);
void onFailed();
}

View File

@ -1,24 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.listener;
public interface SendMessageListener {
void handleFault();
void handleResult();
}

View File

@ -1,412 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import io.bitsquare.user.Preferences;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.name.Named;
import java.io.IOException;
import java.security.KeyPair;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import net.tomp2p.connection.Bindings;
import net.tomp2p.connection.ChannelClientConfiguration;
import net.tomp2p.connection.ChannelServerConfiguration;
import net.tomp2p.dht.PeerBuilderDHT;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureListener;
import net.tomp2p.futures.FutureBootstrap;
import net.tomp2p.futures.FutureDiscover;
import net.tomp2p.nat.FutureNAT;
import net.tomp2p.nat.FutureRelayNAT;
import net.tomp2p.nat.PeerBuilderNAT;
import net.tomp2p.nat.PeerNAT;
import net.tomp2p.p2p.Peer;
import net.tomp2p.p2p.PeerBuilder;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMapChangeListener;
import net.tomp2p.peers.PeerStatistic;
import net.tomp2p.relay.tcp.TCPRelayClientConfig;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
/**
* Creates a DHT peer and bootstraps to the network via a bootstrap node
*/
public class BootstrappedPeerBuilder {
private static final Logger log = LoggerFactory.getLogger(BootstrappedPeerBuilder.class);
static final String NETWORK_INTERFACE_KEY = "interface";
static final String NETWORK_INTERFACE_UNSPECIFIED = "<unspecified>";
static final String USE_MANUAL_PORT_FORWARDING_KEY = "node.useManualPortForwarding";
public enum ConnectionType {
UNDEFINED, DIRECT, MANUAL_PORT_FORWARDING, AUTO_PORT_FORWARDING, RELAY
}
public enum State {
UNDEFINED,
PEER_CREATION_FAILED,
DISCOVERY_STARTED,
DISCOVERY_DIRECT_SUCCEEDED,
DISCOVERY_MANUAL_PORT_FORWARDING_SUCCEEDED,
DISCOVERY_FAILED,
DISCOVERY_AUTO_PORT_FORWARDING_STARTED,
DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED,
DISCOVERY_AUTO_PORT_FORWARDING_FAILED,
RELAY_STARTED,
RELAY_SUCCEEDED,
RELAY_FAILED,
BOOT_STRAP_SUCCEEDED,
BOOT_STRAP_FAILED;
private String message;
State() {
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
private final int port;
private final boolean useManualPortForwarding;
private final String networkInterface;
private BootstrapNodes bootstrapNodes;
private final Preferences preferences;
private final SettableFuture<PeerDHT> settableFuture = SettableFuture.create();
private final ObjectProperty<State> state = new SimpleObjectProperty<>(State.UNDEFINED);
private final ObjectProperty<ConnectionType> connectionType = new SimpleObjectProperty<>(ConnectionType.UNDEFINED);
private KeyPair keyPair;
private Peer peer;
private PeerDHT peerDHT;
private Executor executor;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BootstrappedPeerBuilder(@Named(Node.PORT_KEY) int port,
@Named(USE_MANUAL_PORT_FORWARDING_KEY) boolean useManualPortForwarding,
@Named(NETWORK_INTERFACE_KEY) String networkInterface,
BootstrapNodes bootstrapNodes,
Preferences preferences) {
this.port = port;
this.useManualPortForwarding = useManualPortForwarding;
this.networkInterface = networkInterface;
this.bootstrapNodes = bootstrapNodes;
this.preferences = preferences;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setKeyPair(@NotNull KeyPair keyPair) {
this.keyPair = keyPair;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void setExecutor(Executor executor) {
this.executor = executor;
}
public SettableFuture<PeerDHT> start() {
try {
DefaultEventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(20);
ChannelClientConfiguration clientConf = PeerBuilder.createDefaultChannelClientConfiguration();
clientConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup));
ChannelServerConfiguration serverConf = PeerBuilder.createDefaultChannelServerConfiguration();
serverConf.pipelineFilter(new PeerBuilder.EventExecutorGroupFilter(eventExecutorGroup));
serverConf.connectionTimeoutTCPMillis(5000);
Bindings bindings = new Bindings();
if (!NETWORK_INTERFACE_UNSPECIFIED.equals(networkInterface))
bindings.addInterface(networkInterface);
if (useManualPortForwarding) {
peer = new PeerBuilder(keyPair)
.p2pId(bootstrapNodes.getP2pId())
.channelClientConfiguration(clientConf)
.channelServerConfiguration(serverConf)
.ports(port)
.bindings(bindings)
.tcpPortForwarding(port)
.udpPortForwarding(port)
.start();
}
else {
peer = new PeerBuilder(keyPair)
.p2pId(bootstrapNodes.getP2pId())
.channelClientConfiguration(clientConf)
.channelServerConfiguration(serverConf)
.ports(port)
.bindings(bindings)
.start();
}
peerDHT = new PeerBuilderDHT(peer).start();
peer.peerBean().peerMap().addPeerMapChangeListener(new PeerMapChangeListener() {
@Override
public void peerInserted(PeerAddress peerAddress, boolean verified) {
if (verified)
log.debug("Peer inserted: peerAddress=" + peerAddress + ", verified=" + verified);
else
log.trace("Peer inserted: peerAddress=" + peerAddress + ", verified=" + verified);
}
@Override
public void peerRemoved(PeerAddress peerAddress, PeerStatistic peerStatistics) {
log.debug("Peer removed: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
}
@Override
public void peerUpdated(PeerAddress peerAddress, PeerStatistic peerStatistics) {
// log.debug("Peer updated: peerAddress=" + peerAddress + ", peerStatistics=" + peerStatistics);
}
});
if (preferences.getUseUPnP())
discoverExternalAddressUsingUPnP();
else
discoverExternalAddress();
} catch (IOException e) {
handleError(State.PEER_CREATION_FAILED, "Cannot create a peer with port: " +
port + ". Exception: " + e);
}
return settableFuture;
}
public void shutDown() {
if (peerDHT != null)
peerDHT.shutdown();
}
// We need to discover our external address and test if we are reachable for other nodes
// We know our internal address from a discovery of our local network interfaces
// We start a discover process with our bootstrap node.
// There are 4 cases:
// 1. If we are not behind a NAT we get reported back the same address as our internal.
// 2. If we are behind a NAT and manual port forwarding is setup we get reported our external address from the
// bootstrap node and the bootstrap node could ping us so we know we are reachable.
// 3. If we are behind a NAT and the ping probes fails we need to setup port forwarding with UPnP or NAT-PMP.
// If that is successfully setup we need to try again a discover so we find out our external address and have
// tested successfully our reachability (the additional discover is done internally from startSetupPortforwarding)
// 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
private void discoverExternalAddressUsingUPnP() {
Node randomNode = bootstrapNodes.getRandomDiscoverNode();
log.info("Random Node for discovering own address visible form outside: " + randomNode);
FutureDiscover futureDiscover = peer.discover().peerAddress(randomNode.toPeerAddress()).start();
setState(State.DISCOVERY_STARTED);
PeerNAT peerNAT = new PeerBuilderNAT(peer).start();
FutureNAT futureNAT = peerNAT.startSetupPortforwarding(futureDiscover);
FutureRelayNAT futureRelayNAT = peerNAT.startRelay(new TCPRelayClientConfig(), futureDiscover, futureNAT);
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 {
setState(State.DISCOVERY_AUTO_PORT_FORWARDING_STARTED);
if (futureNAT.isSuccess()) {
setState(State.DISCOVERY_AUTO_PORT_FORWARDING_SUCCEEDED,
"NAT traversal successful with automatic port forwarding.");
setConnectionType(ConnectionType.AUTO_PORT_FORWARDING);
bootstrap();
}
else {
if (futureRelayNAT.isSuccess()) {
// relay mode succeeded
setState(State.RELAY_SUCCEEDED, "NAT traversal not successful. Using relay mode.");
setConnectionType(ConnectionType.RELAY);
bootstrap();
}
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 discoverExternalAddress() {
Node randomNode = bootstrapNodes.getRandomDiscoverNode();
log.info("Random Node for discovering own address visible form outside: " + randomNode);
FutureDiscover futureDiscover = peer.discover().peerAddress(randomNode.toPeerAddress()).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 {
// 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() {
log.trace("start bootstrap");
// We don't wait until bootstrap is done for speeding up startup process
FutureBootstrap futureBootstrap = peer.bootstrap().bootstrapTo(bootstrapNodes.getBootstrapPeerAddresses()).start();
futureBootstrap.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (futureBootstrap.isSuccess()) {
log.trace("bootstrap complete");
setState(State.BOOT_STRAP_SUCCEEDED, "Bootstrap was successful.");
settableFuture.set(peerDHT);
}
else {
handleError(State.BOOT_STRAP_FAILED, "Bootstrap failed. " +
futureBootstrap.failedReason());
}
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
handleError(State.BOOT_STRAP_FAILED, "Exception at bootstrap: " + t.getMessage());
}
});
}
public ConnectionType getConnectionType() {
return connectionType.get();
}
public ReadOnlyObjectProperty<ConnectionType> connectionTypeProperty() {
return connectionType;
}
private void setConnectionType(ConnectionType discoveryState) {
this.connectionType.set(discoveryState);
}
public ObjectProperty<State> getState() {
return state;
}
private void setState(State state) {
setState(state, "", true);
}
private void setState(State state, String message) {
setState(state, message, true);
}
private void setState(State state, String message, boolean isSuccess) {
if (isSuccess)
log.info(message);
else
log.error(message);
state.setMessage(message);
this.state.set(state);
}
private void handleError(State state, String errorMessage) {
setState(state, errorMessage, false);
peerDHT.shutdown();
settableFuture.setException(new Exception(errorMessage));
}
}

View File

@ -1,172 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.crypto.KeyRing;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.p2p.AddressService;
import io.bitsquare.p2p.NetworkException;
import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.listener.GetPeerAddressListener;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.inject.Inject;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.BaseFutureListener;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PAddressService extends TomP2PDHTService implements AddressService {
private static final Logger log = LoggerFactory.getLogger(TomP2PAddressService.class);
private static final int IP_CHECK_PERIOD = 2 * 60 * 1000; // Cheap call if nothing changes, so set it short to 2 min.
private static final int STORE_ADDRESS_PERIOD = 5 * 60 * 1000; // Save every 5 min.
private static final int ADDRESS_TTL = STORE_ADDRESS_PERIOD * 2; // TTL 10 min.
private final Number160 locationKey;
private PeerAddress storedPeerAddress;
private Timer timerForStoreAddress;
private Timer timerForIPCheck;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TomP2PAddressService(TomP2PNode tomP2PNode, KeyRing keyRing) {
super(tomP2PNode, keyRing);
locationKey = Utils.makeSHAHash(keyRing.getPubKeyRing().getDhtSignaturePubKey().getEncoded());
}
@Override
public void bootstrapCompleted() {
super.bootstrapCompleted();
setupTimerForIPCheck();
setupTimerForStoreAddress();
storeAddress();
}
@Override
public void shutDown() {
if (timerForIPCheck != null)
timerForIPCheck.cancel();
if (timerForStoreAddress != null)
timerForStoreAddress.cancel();
super.shutDown();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Find peer address by publicKey
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void findPeerAddress(PubKeyRing pubKeyRing, GetPeerAddressListener listener) {
final Number160 locationKey = Utils.makeSHAHash(pubKeyRing.getDhtSignaturePubKey().getEncoded());
FutureGet futureGet = getDataOfProtectedDomain(locationKey, pubKeyRing.getDhtSignaturePubKey());
log.trace("findPeerAddress called");
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture baseFuture) throws Exception {
if (baseFuture.isSuccess() && futureGet.data() != null) {
final Peer peer = (Peer) futureGet.data().object();
log.trace("Peer found in DHT. Peer = " + peer);
executor.execute(() -> listener.onResult(peer));
}
else {
log.error("getPeerAddress failed. failedReason = " + baseFuture.failedReason());
executor.execute(listener::onFailed);
}
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void setupTimerForStoreAddress() {
timerForStoreAddress = new Timer();
timerForStoreAddress.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (storedPeerAddress != null && peerDHT != null && !storedPeerAddress.equals(peerDHT.peerAddress()))
storeAddress();
}
}, STORE_ADDRESS_PERIOD, STORE_ADDRESS_PERIOD);
}
private void setupTimerForIPCheck() {
timerForIPCheck = new Timer();
timerForIPCheck.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (storedPeerAddress != null && peerDHT != null && !storedPeerAddress.equals(peerDHT.peerAddress()))
storeAddress();
}
}, IP_CHECK_PERIOD, IP_CHECK_PERIOD);
}
private void storeAddress() {
try {
Data data = new Data(new TomP2PPeer(peerDHT.peerAddress()));
// We set a short time-to-live to make getAddress checks fail fast in case if the offerer is offline and to support cheap offerbook state updates
data.ttlSeconds(ADDRESS_TTL);
log.debug("storePeerAddress " + peerDHT.peerAddress().toString());
FuturePut futurePut = putDataToMyProtectedDomain(locationKey, data);
futurePut.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (future.isSuccess()) {
storedPeerAddress = peerDHT.peerAddress();
log.debug("storedPeerAddress = " + storedPeerAddress);
}
else {
log.error("storedPeerAddress not successful");
throw new NetworkException("Storing address was not successful. Reason: " + future.failedReason());
}
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.error("Exception at storedPeerAddress " + t.toString());
throw new NetworkException("Exception at storeAddress.", t);
}
});
} catch (IOException e) {
e.printStackTrace();
log.error("Exception at storePeerAddress " + e.toString());
}
}
}

View File

@ -1,270 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.crypto.KeyRing;
import io.bitsquare.p2p.DHTService;
import java.security.KeyPair;
import java.security.PublicKey;
import javax.inject.Inject;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.FutureRemove;
import net.tomp2p.dht.StorageLayer;
import net.tomp2p.peers.Number160;
import net.tomp2p.storage.Data;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PDHTService extends TomP2PService implements DHTService {
private static final Logger log = LoggerFactory.getLogger(TomP2PDHTService.class);
private final KeyPair dhtSignatureKeyPair;
private final Number160 pubKeyHashForMyDomain;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TomP2PDHTService(TomP2PNode tomP2PNode, KeyRing keyRing) {
super(tomP2PNode);
dhtSignatureKeyPair = keyRing.getDhtSignatureKeyPair();
pubKeyHashForMyDomain = Utils.makeSHAHash(dhtSignatureKeyPair.getPublic().getEncoded());
}
@Override
public void bootstrapCompleted() {
super.bootstrapCompleted();
StorageLayer.ProtectionEnable protectionDomainEnable = StorageLayer.ProtectionEnable.ALL;
StorageLayer.ProtectionMode protectionDomainMode = StorageLayer.ProtectionMode.MASTER_PUBLIC_KEY;
StorageLayer.ProtectionEnable protectionEntryEnable = StorageLayer.ProtectionEnable.ALL;
StorageLayer.ProtectionMode protectionEntryMode = StorageLayer.ProtectionMode.MASTER_PUBLIC_KEY;
peerDHT.storageLayer().protection(protectionDomainEnable, protectionDomainMode, protectionEntryEnable, protectionEntryMode);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Put/Get: Public access.
///////////////////////////////////////////////////////////////////////////////////////////
// Use case: Used for offerbook invalidation timestamp. Everybody can write that data.
/**
* Store data to given location key.
* Write access: Anyone with locationKey
*
* @param locationKey
* @param data
* @return
*/
public FuturePut putData(Number160 locationKey, Data data) {
log.trace("putData");
return peerDHT.put(locationKey).data(data).start();
}
// No protection, everybody can read.
/**
* Get data for given locationKey
* Read access: Anyone with locationKey
*
* @param locationKey
* @return
*/
public FutureGet getData(Number160 locationKey) {
//log.trace("getData");
return peerDHT.get(locationKey).start();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Put/Get: Domain protected, entry protected.
///////////////////////////////////////////////////////////////////////////////////////////
// Use case: Used for storing address. Only domain owner can write and change that data. Data protection gives additional protection (is it needed?)
/**
* Store data to given location key and my domain.
* Write access: Anybody who has pubKey if domain is not used before. KeyPair owner of pubKey can overwrite and reserve that domain.
* We save early an entry so we have that domain reserved and nobody else can use it.
* Additionally we use entry protection, so domain owner is data owner.
*
* @param locationKey
* @param data
* @return
*/
public FuturePut putDataToMyProtectedDomain(Number160 locationKey, Data data) {
log.trace("putDataToMyProtectedDomain");
data.protectEntry(dhtSignatureKeyPair);
return peerDHT.put(locationKey).data(data).protectDomain().domainKey(pubKeyHashForMyDomain).start();
}
/**
* Removes data for given location and my domain.
* Access: Domain owner only can remove
*
* @param locationKey
* @return
*/
public FutureRemove removeDataFromMyProtectedDomain(Number160 locationKey) {
log.trace("removeDataOfProtectedDomain");
if (peerDHT != null)
return peerDHT.remove(locationKey).domainKey(pubKeyHashForMyDomain).keyPair(dhtSignatureKeyPair).start();
else
return null;
}
/**
* Read data for given location and publicKey of that domain.
* Read access: Anyone who has publicKey
*
* @param locationKey
* @param publicKey
* @return
*/
public FutureGet getDataOfProtectedDomain(Number160 locationKey, PublicKey publicKey) {
log.trace("getDataOfProtectedDomain");
final Number160 pubKeyHashOfDomainOwner = Utils.makeSHAHash(publicKey.getEncoded());
return peerDHT.get(locationKey).domainKey(pubKeyHashOfDomainOwner).start();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Add/remove/get from map: Entry protected, no domain protection.
///////////////////////////////////////////////////////////////////////////////////////////
// Use case: Used for offerbook and arbitrators. Everybody can add entries, but those entries are data protected so only the owner can remove it.
/**
* Add data to a map. For the entry contentKey of data is used (internally).
* Write access: Anyone can add entries. But nobody can overwrite an existing entry as it is protected by data protection.
*
* @param locationKey
* @param data
* @return
*/
public FuturePut addProtectedDataToMap(Number160 locationKey, Data data) {
log.trace("addProtectedDataToMap locationKey = " + locationKey);
data.protectEntry(dhtSignatureKeyPair);
log.trace("addProtectedDataToMap with contentKey " + data.hash().toString());
return peerDHT.add(locationKey).data(data).keyPair(dhtSignatureKeyPair).start();
}
/**
* Remove entry from map for given locationKey. ContentKey of data is used for removing the entry.
* Access: Only the owner of the data entry can remove it, as it was written with entry protection.
*
* @param locationKey
* @param data
* @return
*/
public FutureRemove removeProtectedDataFromMap(Number160 locationKey, Data data) {
log.trace("removeProtectedDataFromMap locationKey = " + locationKey);
Number160 contentKey = data.hash();
log.trace("removeProtectedDataFromMap with contentKey " + contentKey.toString());
return peerDHT.remove(locationKey).contentKey(contentKey).keyPair(dhtSignatureKeyPair).start();
}
/**
* Get map for given locationKey with all entries.
* Access: Everybody can read.
*
* @param locationKey
* @return
*/
public FutureGet getMap(Number160 locationKey) {
log.trace("getMap");
return peerDHT.get(locationKey).all().start();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Add/remove/get from map: Domain protection, no data protection.
///////////////////////////////////////////////////////////////////////////////////////////
// Use case: Used for mailbox. Everybody can add message entries to ones mailbox, but only mailbox owner (domain owner) can remove entries.
// For protecting privacy we use encryption for the messages (not part of DHT infrastructure), so everybody can read the messages but only domain owner
// can decrypt it.
/**
* Add data to a map. For the entry contentKey of data is used (internally).
* Write access: Anyone can add entries. But nobody expect the domain owner can overwrite/remove an existing entry as it is protected by the domain owner.
*
* @param locationKey
* @param data
* @return
*/
public FuturePut addDataToMapOfProtectedDomain(Number160 locationKey, Data data, PublicKey publicKey) {
log.trace("addDataToMapOfProtectedDomain");
log.trace("addDataToMapOfProtectedDomain with contentKey " + data.hash().toString());
final Number160 pubKeyHashOfDomainOwner = Utils.makeSHAHash(publicKey.getEncoded());
return peerDHT.add(locationKey).protectDomain().domainKey(pubKeyHashOfDomainOwner).keyPair(dhtSignatureKeyPair)
.data(data).protectDomain().domainKey(pubKeyHashOfDomainOwner).keyPair(dhtSignatureKeyPair).start();
}
/**
* Remove entry from map for given locationKey. ContentKey of data is used for removing the entry.
* Access: Only the owner of the data entry can remove it, as it was written with entry protection.
*
* @param locationKey
* @param data
* @return
*/
public FutureRemove removeDataFromMapOfMyProtectedDomain(Number160 locationKey, Data data) {
log.trace("removeDataFromMapOfMyProtectedDomain");
Number160 contentKey = data.hash();
log.trace("removeDataFromMapOfMyProtectedDomain with contentKey " + contentKey.toString());
return peerDHT.remove(locationKey).contentKey(contentKey).domainKey(pubKeyHashForMyDomain).keyPair(dhtSignatureKeyPair).start();
}
/**
* Get map for given locationKey with all entries.
* Access: Everybody can read.
*
* @param locationKey
* @return
*/
public FutureGet getDataFromMapOfMyProtectedDomain(Number160 locationKey) {
log.trace("getDataFromMapOfMyProtectedDomain");
return peerDHT.get(locationKey).all().domainKey(pubKeyHashForMyDomain).start();
}
/**
* Remove all data from map for given locationKey.
* Access: Only the domain owner.
*
* @param locationKey
* @return
*/
public FutureRemove removeAllDataFromMapOfMyProtectedDomain(Number160 locationKey) {
log.trace("getDataFromMapOfMyProtectedDomain");
return peerDHT.remove(locationKey).domainKey(pubKeyHashForMyDomain).keyPair(dhtSignatureKeyPair).all().domainKey(pubKeyHashForMyDomain).keyPair
(dhtSignatureKeyPair).start();
}
}

View File

@ -1,188 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.KeyRing;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.crypto.SealedAndSignedMessage;
import io.bitsquare.p2p.MailboxMessagesResultHandler;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.trade.offer.OfferBookService;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.FutureRemove;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.BaseFutureListener;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PMailboxService extends TomP2PDHTService implements MailboxService {
private static final Logger log = LoggerFactory.getLogger(TomP2PMailboxService.class);
private static final int TTL = 21 * 24 * 60 * 60; // the message is default 21 days valid, as a max trade period might be about 2 weeks.
private final List<OfferBookService.Listener> offerRepositoryListeners = new ArrayList<>();
private final KeyPair dhtSignatureKeyPair;
@Inject
public TomP2PMailboxService(TomP2PNode tomP2PNode, KeyRing keyRing) {
super(tomP2PNode, keyRing);
dhtSignatureKeyPair = keyRing.getDhtSignatureKeyPair();
}
@Override
public void bootstrapCompleted() {
super.bootstrapCompleted();
}
@Override
public void shutDown() {
super.shutDown();
}
@Override
public void addMessage(PubKeyRing pubKeyRing, SealedAndSignedMessage message, ResultHandler resultHandler, FaultHandler faultHandler) {
try {
final Data data = new Data(message);
data.ttlSeconds(TTL);
Number160 locationKey = getLocationKey(pubKeyRing.getDhtSignaturePubKey());
log.trace("Add message to DHT requested. Added data: [locationKey: " + locationKey +
", hash: " + data.hash().toString() + "]");
openRequestsUp();
FuturePut futurePut = addDataToMapOfProtectedDomain(locationKey,
data, pubKeyRing.getDhtSignaturePubKey());
futurePut.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
openRequestsDown();
if (future.isSuccess()) {
executor.execute(() -> {
log.trace("Add message to mailbox was successful. Added data: [locationKey: " + locationKey + ", value: " + data + "]");
resultHandler.handleResult();
});
}
else {
// Seems to be a bug in TomP2P that when one peer shuts down the expected nr of peers and the delivered are not matching
// As far tested the storage succeeded, so seems to be a wrong message.
//Future (compl/canc):true/false, FAILED, Expected 3 result, but got 2
log.warn("Ignoring isSuccess=false case. failedReason: {}", future.failedReason());
resultHandler.handleResult();
}
}
@Override
public void exceptionCaught(Throwable ex) throws Exception {
openRequestsDown();
executor.execute(() -> faultHandler.handleFault("Add message to mailbox failed.", ex));
}
});
} catch (IOException ex) {
openRequestsDown();
executor.execute(() -> faultHandler.handleFault("Add message to mailbox failed.", ex));
}
}
@Override
public void getAllMessages(MailboxMessagesResultHandler resultHandler) {
log.trace("Get messages from DHT requested for locationKey: " + getLocationKey(dhtSignatureKeyPair.getPublic()));
FutureGet futureGet = getDataFromMapOfMyProtectedDomain(getLocationKey(dhtSignatureKeyPair.getPublic()));
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (future.isSuccess()) {
final Map<Number640, Data> dataMap = futureGet.dataMap();
List<SealedAndSignedMessage> messages = new ArrayList<>();
if (dataMap != null) {
for (Data messageData : dataMap.values()) {
try {
Object messageDataObject = messageData.object();
if (messageDataObject instanceof SealedAndSignedMessage) {
messages.add((SealedAndSignedMessage) messageDataObject);
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
executor.execute(() -> resultHandler.handleResult(messages));
}
log.trace("Get messages from DHT was successful. Stored data: [key: " + getLocationKey(dhtSignatureKeyPair.getPublic())
+ ", values: " + futureGet.dataMap() + "]");
}
else {
final Map<Number640, Data> dataMap = futureGet.dataMap();
if (dataMap == null || dataMap.size() == 0) {
log.trace("Get messages from DHT delivered empty dataMap.");
executor.execute(() -> resultHandler.handleResult(new ArrayList<>()));
}
else {
log.error("Get messages from DHT was not successful with reason:" + future.failedReason());
}
}
}
});
}
@Override
public void removeAllMessages(ResultHandler resultHandler, FaultHandler faultHandler) {
log.trace("Remove all messages from DHT requested. locationKey: " + getLocationKey(dhtSignatureKeyPair.getPublic()));
FutureRemove futureRemove = removeAllDataFromMapOfMyProtectedDomain(getLocationKey(dhtSignatureKeyPair.getPublic()));
futureRemove.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
// We don't test futureRemove.isSuccess() as this API does not fit well to that operation,
// it might change in future to something like foundAndRemoved and notFound
// See discussion at: https://github.com/tomp2p/TomP2P/issues/57#issuecomment-62069840
log.trace("isRemoved? " + futureRemove.isRemoved());
executor.execute(resultHandler::handleResult);
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.error("Remove all messages from DHT failed. Error: " + t.getMessage());
faultHandler.handleFault("Remove all messages from DHT failed. Error: " + t.getMessage(), t);
}
});
}
private Number160 getLocationKey(PublicKey p2pSigPubKey) {
return Number160.createHash("mailbox" + p2pSigPubKey.hashCode());
}
}

View File

@ -1,226 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.crypto.CryptoException;
import io.bitsquare.crypto.CryptoService;
import io.bitsquare.crypto.MessageWithPubKey;
import io.bitsquare.crypto.PubKeyRing;
import io.bitsquare.crypto.SealedAndSignedMessage;
import io.bitsquare.p2p.DecryptedMessageHandler;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.MessageHandler;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.listener.SendMessageListener;
import io.bitsquare.util.Utilities;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import net.tomp2p.futures.BaseFuture;
import net.tomp2p.futures.BaseFutureListener;
import net.tomp2p.futures.FutureDirect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PMessageService extends TomP2PService implements MessageService {
private static final Logger log = LoggerFactory.getLogger(TomP2PMessageService.class);
private static final int MAX_MESSAGE_SIZE = 100 * 1024; // 34 kb is currently the max size used
private final CopyOnWriteArrayList<MessageHandler> messageHandlers = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<DecryptedMessageHandler> decryptedMessageHandlers = new CopyOnWriteArrayList<>();
private final MailboxService mailboxService;
private final CryptoService<MailboxMessage> cryptoService;
@Inject
public TomP2PMessageService(TomP2PNode tomP2PNode, MailboxService mailboxService, CryptoService<MailboxMessage> cryptoService) {
super(tomP2PNode);
this.mailboxService = mailboxService;
this.cryptoService = cryptoService;
}
@Override
public void bootstrapCompleted() {
super.bootstrapCompleted();
setupReplyHandler();
}
@Override
public void shutDown() {
super.shutDown();
}
@Override
public void sendEncryptedMessage(Peer peer, PubKeyRing pubKeyRing, Message message, boolean useMailboxIfUnreachable, SendMessageListener listener) {
assert pubKeyRing != null;
log.debug("sendMessage called");
if (peer == null)
throw new IllegalArgumentException("Peer must not be null");
else if (!(peer instanceof TomP2PPeer))
throw new IllegalArgumentException("Peer must be of type TomP2PPeer");
try {
final Message encryptedMessage = cryptoService.encryptAndSignMessage(pubKeyRing, message);
openRequestsUp();
FutureDirect futureDirect = peerDHT.peer().sendDirect(((TomP2PPeer) peer).getPeerAddress()).object(encryptedMessage).start();
futureDirect.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (future.isSuccess()) {
openRequestsDown();
log.debug("sendMessage completed");
executor.execute(listener::handleResult);
}
else {
log.info("sendMessage failed. We will try to send the message to the mailbox. Fault reason: " +
futureDirect.failedReason());
if (useMailboxIfUnreachable) {
sendMailboxMessage(pubKeyRing, (SealedAndSignedMessage) encryptedMessage, listener);
}
else {
openRequestsDown();
log.error("Send message was not successful");
executor.execute(listener::handleFault);
}
}
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.info("sendMessage failed with exception. We will try to send the message to the mailbox. Exception: "
+ t.getMessage());
if (useMailboxIfUnreachable) {
sendMailboxMessage(pubKeyRing, (SealedAndSignedMessage) encryptedMessage, listener);
}
else {
openRequestsDown();
log.error("Send message was not successful");
executor.execute(listener::handleFault);
}
}
}
);
} catch (Throwable t) {
openRequestsDown();
t.printStackTrace();
log.error(t.getMessage());
executor.execute(listener::handleFault);
}
}
private void sendMailboxMessage(PubKeyRing pubKeyRing, SealedAndSignedMessage message, SendMessageListener listener) {
log.info("sendMailboxMessage called");
mailboxService.addMessage(
pubKeyRing,
message,
() -> {
openRequestsDown();
log.debug("Message successfully added to peers mailbox.");
executor.execute(listener::handleResult);
},
(errorMessage, throwable) -> {
openRequestsDown();
log.error("Message failed to add to peers mailbox.");
executor.execute(listener::handleFault);
}
);
}
@Override
public void addMessageHandler(MessageHandler listener) {
if (!messageHandlers.add(listener))
log.error("Add listener did not change list. Probably listener has been already added.");
}
@Override
public void removeMessageHandler(MessageHandler listener) {
if (!messageHandlers.remove(listener))
log.error("Try to remove listener which was never added.");
}
@Override
public void addDecryptedMessageHandler(DecryptedMessageHandler listener) {
if (!decryptedMessageHandlers.add(listener))
log.error("Add listener did not change list. Probably listener has been already added.");
}
@Override
public void removeDecryptedMessageHandler(DecryptedMessageHandler listener) {
if (!decryptedMessageHandlers.remove(listener))
log.error("Try to remove listener which was never added.");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void setupReplyHandler() {
peerDHT.peer().objectDataReply((sender, message) -> {
//log.debug("Incoming message with peerAddress " + sender);
//log.debug("Incoming message with type " + message);
int messageSize = 0;
if (message != null)
messageSize = Utilities.objectToBytArray(message).length;
log.debug("Incoming message with size " + messageSize);
if (!sender.equals(peerDHT.peer().peerAddress())) {
if (messageSize == 0)
log.warn("Received msg is null");
else if (messageSize > MAX_MESSAGE_SIZE)
log.warn("Received msg size of {} is exceeding the max message size of {}.",
Utilities.objectToBytArray(message).length, MAX_MESSAGE_SIZE);
else if (message instanceof SealedAndSignedMessage)
executor.execute(() -> decryptedMessageHandlers.stream().forEach(e -> {
MessageWithPubKey messageWithPubKey = null;
try {
messageWithPubKey = getDecryptedMessageWithPubKey((SealedAndSignedMessage) message);
//log.debug("decrypted message " + messageWithPubKey.getMessage());
e.handleMessage(messageWithPubKey, new TomP2PPeer(sender));
} catch (CryptoException e1) {
e1.printStackTrace();
log.warn("decryptAndVerifyMessage msg failed", e1.getMessage());
}
}
));
else if (message instanceof Message)
executor.execute(() -> messageHandlers.stream().forEach(e -> e.handleMessage((Message) message, new TomP2PPeer(sender))));
else
log.warn("We got an object which is not type of Message. Object = " + message);
}
else {
log.error("Received msg from myself. That must never happen.");
}
return true;
});
}
private MessageWithPubKey getDecryptedMessageWithPubKey(SealedAndSignedMessage message) throws CryptoException {
return cryptoService.decryptAndVerifyMessage(message);
}
}

View File

@ -1,88 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.p2p.AddressService;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.ClientNode;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Node;
import io.bitsquare.p2p.P2PModule;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.google.inject.name.Names;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder.NETWORK_INTERFACE_UNSPECIFIED;
public class TomP2PModule extends P2PModule {
private static final Logger log = LoggerFactory.getLogger(TomP2PModule.class);
public static final String BOOTSTRAP_NODE_NAME_KEY = "bootstrap.node.name";
public static final String BOOTSTRAP_NODE_IP_KEY = "bootstrap.node.ip";
public static final String BOOTSTRAP_NODE_P2P_ID_KEY = "bootstrap.node.p2pId";
public static final String BOOTSTRAP_NODE_PORT_KEY = "bootstrap.node.port";
public static final String NETWORK_INTERFACE_KEY = BootstrappedPeerBuilder.NETWORK_INTERFACE_KEY;
public static final String USE_MANUAL_PORT_FORWARDING_KEY = BootstrappedPeerBuilder.USE_MANUAL_PORT_FORWARDING_KEY;
public TomP2PModule(Environment env) {
super(env);
}
@Override
protected void doConfigure() {
// Used both ClientNode and TomP2PNode for injection
bind(BootstrapNodes.class).in(Singleton.class);
bind(ClientNode.class).to(TomP2PNode.class).in(Singleton.class);
bind(TomP2PNode.class).in(Singleton.class);
bind(BootstrappedPeerBuilder.class).in(Singleton.class);
bind(AddressService.class).to(TomP2PAddressService.class).in(Singleton.class);
bind(MessageService.class).to(TomP2PMessageService.class).in(Singleton.class);
bind(MailboxService.class).to(TomP2PMailboxService.class).in(Singleton.class);
bind(int.class).annotatedWith(Names.named(Node.PORT_KEY)).toInstance(env.getProperty(Node.PORT_KEY, int.class, Node.CLIENT_PORT));
bind(boolean.class).annotatedWith(Names.named(USE_MANUAL_PORT_FORWARDING_KEY)).toInstance(
env.getProperty(USE_MANUAL_PORT_FORWARDING_KEY, boolean.class, false));
bind(Node.class).annotatedWith(Names.named(BootstrapNodes.BOOTSTRAP_NODE_KEY)).toInstance(
Node.at(env.getProperty(BOOTSTRAP_NODE_NAME_KEY, ""),
env.getProperty(BOOTSTRAP_NODE_IP_KEY, ""),
Integer.valueOf(env.getProperty(BOOTSTRAP_NODE_P2P_ID_KEY, "-1")),
Integer.valueOf(env.getProperty(BOOTSTRAP_NODE_PORT_KEY, "-1"))
));
bindConstant().annotatedWith(Names.named(NETWORK_INTERFACE_KEY)).to(env.getProperty(NETWORK_INTERFACE_KEY, NETWORK_INTERFACE_UNSPECIFIED));
}
@Override
protected void doClose(Injector injector) {
log.trace("doClose " + getClass().getSimpleName());
// First shut down AddressService to remove address from DHT
injector.getInstance(AddressService.class).shutDown();
injector.getInstance(BootstrappedPeerBuilder.class).shutDown();
injector.getInstance(MailboxService.class).shutDown();
injector.getInstance(MessageService.class).shutDown();
}
}

View File

@ -1,196 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.BitsquareException;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.BaseP2PService;
import io.bitsquare.p2p.ClientNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import java.security.KeyPair;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMapChangeListener;
import net.tomp2p.peers.PeerStatistic;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import rx.subjects.Subject;
public class TomP2PNode implements ClientNode {
private static final Logger log = LoggerFactory.getLogger(TomP2PNode.class);
private PeerDHT peerDHT;
private BootstrappedPeerBuilder bootstrappedPeerBuilder;
private final Subject<BootstrappedPeerBuilder.State, BootstrappedPeerBuilder.State> bootstrapStateSubject;
private final List<ResultHandler> resultHandlers = new CopyOnWriteArrayList<>();
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TomP2PNode(BootstrappedPeerBuilder bootstrappedPeerBuilder) {
this.bootstrappedPeerBuilder = bootstrappedPeerBuilder;
bootstrapStateSubject = BehaviorSubject.create();
}
// for unit testing
TomP2PNode(KeyPair keyPair, PeerDHT peerDHT) {
this.peerDHT = peerDHT;
peerDHT.peerBean().keyPair(keyPair);
bootstrapStateSubject = BehaviorSubject.create();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public methods
///////////////////////////////////////////////////////////////////////////////////////////
public void setExecutor(Executor executor) {
bootstrappedPeerBuilder.setExecutor(executor);
}
public Observable<BootstrappedPeerBuilder.State> bootstrap(KeyPair keyPair) {
bootstrappedPeerBuilder.setKeyPair(keyPair);
bootstrappedPeerBuilder.getState().addListener((ov, oldValue, newValue) -> {
log.debug("BootstrapState changed " + newValue);
bootstrapStateSubject.onNext(newValue);
});
SettableFuture<PeerDHT> bootstrapFuture = bootstrappedPeerBuilder.start();
Futures.addCallback(bootstrapFuture, new FutureCallback<PeerDHT>() {
@Override
public void onSuccess(@Nullable PeerDHT peerDHT) {
if (peerDHT != null) {
TomP2PNode.this.peerDHT = peerDHT;
BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().all().size()));
log.debug("Number of peers = " + peerDHT.peerBean().peerMap().all().size());
peerDHT.peerBean().peerMap().addPeerMapChangeListener(new PeerMapChangeListener() {
@Override
public void peerInserted(PeerAddress peerAddress, boolean b) {
BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().all().size()));
log.debug("peerInserted " + peerAddress);
log.debug("Number of peers = " + peerDHT.peerBean().peerMap().all().size());
}
@Override
public void peerRemoved(PeerAddress peerAddress, PeerStatistic peerStatistic) {
BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().all().size()));
log.debug("peerRemoved " + peerAddress);
log.debug("Number of peers = " + peerDHT.peerBean().peerMap().all().size());
}
@Override
public void peerUpdated(PeerAddress peerAddress, PeerStatistic peerStatistic) {
BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().all().size()));
// log.debug("peerUpdated " + peerAddress);
// log.debug("Number of peers = " + peerDHT.peerBean().peerMap().all().size());
}
});
/* peerDHT.peerBean().addPeerStatusListener(new PeerStatusListener() {
@Override
public boolean peerFailed(PeerAddress peerAddress, PeerException e) {
return false;
}
@Override
public boolean peerFound(PeerAddress peerAddress, PeerAddress peerAddress1, PeerConnection peerConnection, RTT rtt) {
BaseP2PService.getUserThread().execute(() -> numPeers.set(peerDHT.peerBean().peerMap().size()));
return false;
}
});*/
resultHandlers.stream().forEach(ResultHandler::handleResult);
bootstrapStateSubject.onCompleted();
}
else {
log.error("Error at bootstrap: peerDHT = null");
bootstrapStateSubject.onError(new BitsquareException("Error at bootstrap: peerDHT = null"));
}
}
@Override
public void onFailure(@NotNull Throwable t) {
log.error("Exception at bootstrap " + t.getMessage());
bootstrapStateSubject.onError(t);
}
});
return bootstrapStateSubject.asObservable();
}
public PeerDHT getPeerDHT() {
return peerDHT;
}
@Override
public BootstrappedPeerBuilder.ConnectionType getConnectionType() {
return bootstrappedPeerBuilder.getConnectionType();
}
public String getClientNodeInfo() {
PeerAddress peerAddress = peerDHT.peerBean().serverPeerAddress();
return "IP='" + peerAddress.inetAddress().getHostAddress() + '\'' +
"; P2P network ID='" + peerDHT.peer().p2pId() + '\'' +
"; port=" + peerAddress.peerSocketAddress().tcpPort();
}
public void addResultHandler(ResultHandler resultHandler) {
resultHandlers.add(resultHandler);
}
public void removeResultHandler(ResultHandler resultHandler) {
resultHandlers.remove(resultHandler);
}
public int getNumPeers() {
return numPeers.get();
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
}

View File

@ -1,56 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer;
import com.google.common.base.Objects;
import java.io.Serializable;
import javax.annotation.concurrent.Immutable;
import net.tomp2p.peers.PeerAddress;
/**
* A {@link Peer} implementation that encapsulates a TomP2P {@link PeerAddress}.
*
* @author Chris Beams
*/
@Immutable
public class TomP2PPeer implements Peer, Serializable {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
private final PeerAddress peerAddress;
public TomP2PPeer(PeerAddress peerAddress) {
this.peerAddress = peerAddress;
}
public PeerAddress getPeerAddress() {
return peerAddress;
}
public String toString() {
return Objects.toStringHelper(this)
.add("peerAddress", peerAddress)
.toString();
}
}

View File

@ -1,50 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p.tomp2p;
import io.bitsquare.p2p.BaseP2PService;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* That service delivers direct messaging and DHT functionality from the TomP2P library
* It is the translating domain specific functionality to the messaging layer.
* The TomP2P library codebase shall not be used outside that service.
* That way we limit the dependency of the TomP2P library only to that class (and it's sub components).
* <p/>
*/
public class TomP2PService extends BaseP2PService {
private static final Logger log = LoggerFactory.getLogger(TomP2PService.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TomP2PService(TomP2PNode tomP2PNode) {
tomP2PNode.addResultHandler(() -> {
peerDHT = tomP2PNode.getPeerDHT();
bootstrapCompleted();
});
}
}

View File

@ -18,13 +18,13 @@
/** /**
* Copyright 2013 Google Inc. * Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach * Copyright 2014 Andreas Schildbach
* <p/> * <p>
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* <p/> * <p>
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* <p/> * <p>
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -35,30 +35,22 @@
package io.bitsquare.storage; package io.bitsquare.storage;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.Threading;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.bitsquare.common.UserThread;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.Threading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
/** /**
@ -97,17 +89,17 @@ public class FileManager<T> {
.setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread. .setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread.
// An executor that starts up threads when needed and shuts them down later. // An executor that starts up threads when needed and shuts them down later.
this.executor = new ScheduledThreadPoolExecutor(1, builder.build()); executor = new ScheduledThreadPoolExecutor(1, builder.build());
this.executor.setKeepAliveTime(5, TimeUnit.SECONDS); executor.setKeepAliveTime(5, TimeUnit.SECONDS);
this.executor.allowCoreThreadTimeOut(true); executor.allowCoreThreadTimeOut(true);
this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
// File must only be accessed from the auto-save executor from now on, to avoid simultaneous access. // File must only be accessed from the auto-save executor from now on, to avoid simultaneous access.
this.savePending = new AtomicBoolean(); savePending = new AtomicBoolean();
this.delay = delay; this.delay = delay;
this.delayTimeUnit = checkNotNull(delayTimeUnit); this.delayTimeUnit = checkNotNull(delayTimeUnit);
this.saver = () -> { saver = () -> {
// Runs in an auto save thread. // Runs in an auto save thread.
if (!savePending.getAndSet(false)) { if (!savePending.getAndSet(false)) {
// Some other scheduled request already beat us to it. // Some other scheduled request already beat us to it.
@ -121,7 +113,7 @@ public class FileManager<T> {
@Override @Override
public void run() { public void run() {
try { try {
FileManager.this.shutdown(); FileManager.this.shutDown();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -137,7 +129,7 @@ public class FileManager<T> {
/** /**
* Actually write the wallet file to disk, using an atomic rename when possible. Runs on the current thread. * Actually write the wallet file to disk, using an atomic rename when possible. Runs on the current thread.
*/ */
public void saveNow(T serializable) throws IOException { public void saveNow(T serializable) {
saveNowInternal(serializable); saveNowInternal(serializable);
} }
@ -152,12 +144,15 @@ public class FileManager<T> {
executor.schedule(saver, delay, delayTimeUnit); executor.schedule(saver, delay, delayTimeUnit);
} }
public T read(File file) throws IOException, ClassNotFoundException { public T read(File file) {
log.debug("read" + file); log.debug("read" + file);
lock.lock(); lock.lock();
try (final FileInputStream fileInputStream = new FileInputStream(file); try (final FileInputStream fileInputStream = new FileInputStream(file);
final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
return (T) objectInputStream.readObject(); return (T) objectInputStream.readObject();
} catch (Throwable t) {
log.error("Exception at read: " + t.getMessage());
return null;
} finally { } finally {
lock.unlock(); lock.unlock();
} }
@ -190,7 +185,7 @@ public class FileManager<T> {
/** /**
* Shut down auto-saving. * Shut down auto-saving.
*/ */
public void shutdown() { public void shutDown() {
/* if (serializable != null) /* if (serializable != null)
log.debug("shutDown " + serializable.getClass().getSimpleName()); log.debug("shutDown " + serializable.getClass().getSimpleName());
else else
@ -198,9 +193,10 @@ public class FileManager<T> {
executor.shutdown(); executor.shutdown();
try { try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); // forever //executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); // forever
} catch (InterruptedException x) { executor.awaitTermination(5, TimeUnit.SECONDS);
throw new RuntimeException(x); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
} }
} }
@ -241,7 +237,7 @@ public class FileManager<T> {
private void saveNowInternal(T serializable) { private void saveNowInternal(T serializable) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
saveToFile(serializable, dir, storageFile); saveToFile(serializable, dir, storageFile);
Threading.USER_THREAD.execute(() -> log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now)); UserThread.execute(() -> log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now));
} }
private void saveToFile(T serializable, File dir, File storageFile) { private void saveToFile(T serializable, File dir, File storageFile) {
@ -264,7 +260,6 @@ public class FileManager<T> {
// TODO ConcurrentModificationException happens sometimes at that line // TODO ConcurrentModificationException happens sometimes at that line
objectOutputStream.writeObject(serializable); objectOutputStream.writeObject(serializable);
// Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide
// to not write through to physical media for at least a few seconds, but this is the best we can do. // to not write through to physical media for at least a few seconds, but this is the best we can do.
fileOutputStream.flush(); fileOutputStream.flush();
@ -313,8 +308,7 @@ public class FileManager<T> {
if (!tempFile.renameTo(canonical)) { if (!tempFile.renameTo(canonical)) {
throw new IOException("Failed to rename " + tempFile + " to " + canonical); throw new IOException("Failed to rename " + tempFile + " to " + canonical);
} }
} } else if (!tempFile.renameTo(file)) {
else if (!tempFile.renameTo(file)) {
throw new IOException("Failed to rename " + tempFile + " to " + file); throw new IOException("Failed to rename " + tempFile + " to " + file);
} }
} finally { } finally {

View File

@ -18,20 +18,18 @@
package io.bitsquare.storage; package io.bitsquare.storage;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable; import static com.google.common.base.Preconditions.checkNotNull;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* That class handles the storage of a particular object to disk using Java serialisation. * That class handles the storage of a particular object to disk using Java serialisation.
@ -39,15 +37,15 @@ import org.slf4j.LoggerFactory;
* Java serialisation is tolerant with added fields, but removing or changing existing fields will break the backwards compatibility. * Java serialisation is tolerant with added fields, but removing or changing existing fields will break the backwards compatibility.
* Alternative frameworks for serialisation like Kyro or mapDB have shown problems with version migration, so we stuck with plain Java * Alternative frameworks for serialisation like Kyro or mapDB have shown problems with version migration, so we stuck with plain Java
* serialisation. * serialisation.
* <p/> * <p>
* For every data object we write a separate file to minimize the risk of corrupted files in case of inconsistency from newer versions. * For every data object we write a separate file to minimize the risk of corrupted files in case of inconsistency from newer versions.
* In case of a corrupted file we backup the old file to a separate directory, so if it holds critical data it might be helpful for recovery. * In case of a corrupted file we backup the old file to a separate directory, so if it holds critical data it might be helpful for recovery.
* <p/> * <p>
* We also backup at first read the file, so we have a valid file form the latest version in case a write operation corrupted the file. * We also backup at first read the file, so we have a valid file form the latest version in case a write operation corrupted the file.
* <p/> * <p>
* The read operation is triggered just at object creation (startup) and is at the moment not executed on a background thread to avoid asynchronous behaviour. * The read operation is triggered just at object creation (startup) and is at the moment not executed on a background thread to avoid asynchronous behaviour.
* As the data are small and it is just one read access the performance penalty is small and might be even worse to create and setup a thread for it. * As the data are small and it is just one read access the performance penalty is small and might be even worse to create and setup a thread for it.
* <p/> * <p>
* The write operation used a background thread and supports a delayed write to avoid too many repeated write operations. * The write operation used a background thread and supports a delayed write to avoid too many repeated write operations.
*/ */
public class Storage<T extends Serializable> { public class Storage<T extends Serializable> {
@ -98,8 +96,7 @@ public class Storage<T extends Serializable> {
// Save delayed and on a background thread // Save delayed and on a background thread
public void queueUpForSave() { public void queueUpForSave() {
log.debug("save " + fileName); log.debug("save " + fileName);
if (storageFile == null) checkNotNull(storageFile, "storageFile = null. Call setupFileStorage before using read/write.");
throw new RuntimeException("storageFile = null. Call setupFileStorage before using read/write.");
fileManager.saveLater(serializable); fileManager.saveLater(serializable);
} }
@ -129,7 +126,7 @@ public class Storage<T extends Serializable> {
log.info("Backup {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); log.info("Backup {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now);
return persistedObject; return persistedObject;
} catch (ClassCastException | ClassNotFoundException | IOException e) { } catch (ClassCastException | IOException e) {
e.printStackTrace(); e.printStackTrace();
log.error("Version of persisted class has changed. We cannot read the persisted data anymore. We make a backup and remove the inconsistent " + log.error("Version of persisted class has changed. We cannot read the persisted data anymore. We make a backup and remove the inconsistent " +
"file."); "file.");

View File

@ -18,21 +18,22 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol; import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.OffererProtocol; import io.bitsquare.trade.protocol.trade.OffererProtocol;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger; import static com.google.common.base.Preconditions.checkNotNull;
import org.slf4j.LoggerFactory;
public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, Serializable { public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, 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.
@ -50,10 +51,13 @@ public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, Ser
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
@Override @Override
@ -67,13 +71,15 @@ public class BuyerAsOffererTrade extends BuyerTrade implements OffererTrade, Ser
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void handleTakeOfferRequest(TradeMessage message, Peer taker) { public void handleTakeOfferRequest(TradeMessage message, Address taker) {
((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker); ((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker);
} }
@Override @Override
public Coin getPayoutAmount() { public Coin getPayoutAmount() {
return getSecurityDeposit().add(getTradeAmount()); checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount());
} }
} }

View File

@ -18,20 +18,22 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.BuyerAsTakerProtocol; import io.bitsquare.trade.protocol.trade.BuyerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.TakerProtocol; import io.bitsquare.trade.protocol.trade.TakerProtocol;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger; import static com.google.common.base.Preconditions.checkArgument;
import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull;
public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, Serializable { public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, 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.
@ -44,15 +46,18 @@ public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, Seriali
// Constructor, initialization // Constructor, initialization
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public BuyerAsTakerTrade(Offer offer, Coin tradeAmount, Peer tradingPeer, Storage<? extends TradableList> storage) { public BuyerAsTakerTrade(Offer offer, Coin tradeAmount, Address tradingPeerAddress, Storage<? extends TradableList> storage) {
super(offer, tradeAmount, tradingPeer, storage); super(offer, tradeAmount, tradingPeerAddress, storage);
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
@Override @Override
@ -67,12 +72,14 @@ public class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade, Seriali
@Override @Override
public void takeAvailableOffer() { public void takeAvailableOffer() {
assert tradeProtocol instanceof TakerProtocol; checkArgument(tradeProtocol instanceof TakerProtocol, "tradeProtocol NOT instanceof TakerProtocol");
((TakerProtocol) tradeProtocol).takeAvailableOffer(); ((TakerProtocol) tradeProtocol).takeAvailableOffer();
} }
@Override @Override
public Coin getPayoutAmount() { public Coin getPayoutAmount() {
return getSecurityDeposit().add(getTradeAmount()); checkNotNull(getTradeAmount(), "Invalid state: getTradeAmount() = null");
return FeePolicy.SECURITY_DEPOSIT.add(getTradeAmount());
} }
} }

View File

@ -18,19 +18,17 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.BuyerProtocol; import io.bitsquare.trade.protocol.trade.BuyerProtocol;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import static com.google.common.base.Preconditions.checkArgument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BuyerTrade extends Trade implements Serializable { public abstract class BuyerTrade extends Trade 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.
@ -38,8 +36,8 @@ public abstract class BuyerTrade extends Trade implements Serializable {
transient private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererTrade.class); transient private static final Logger log = LoggerFactory.getLogger(BuyerAsOffererTrade.class);
public BuyerTrade(Offer offer, Coin tradeAmount, Peer tradingPeer, Storage<? extends TradableList> storage) { public BuyerTrade(Offer offer, Coin tradeAmount, Address tradingPeerAddress, Storage<? extends TradableList> storage) {
super(offer, tradeAmount, tradingPeer, storage); super(offer, tradeAmount, tradingPeerAddress, storage);
} }
public BuyerTrade(Offer offer, Storage<? extends TradableList> storage) { public BuyerTrade(Offer offer, Storage<? extends TradableList> storage) {
@ -48,92 +46,26 @@ public abstract class BuyerTrade extends Trade implements Serializable {
@Override @Override
protected void initStates() { protected void initStates() {
if (tradeState == null) if (state == null)
tradeState = TradeState.BuyerState.PREPARATION; state = State.PREPARATION;
initStateProperties();
} }
public void onFiatPaymentStarted() { public void onFiatPaymentStarted() {
assert tradeProtocol instanceof BuyerProtocol; checkArgument(tradeProtocol instanceof BuyerProtocol, "tradeProtocol NOT instanceof BuyerProtocol");
((BuyerProtocol) tradeProtocol).onFiatPaymentStarted(); ((BuyerProtocol) tradeProtocol).onFiatPaymentStarted();
} }
@Override
public boolean isFailedState() {
return tradeState == TradeState.BuyerState.FAILED;
}
@Override
public void setFailedState() {
TradeState tradeState = TradeState.BuyerState.FAILED;
// We store the phase of the last state into the failed state
tradeState.setPhase(tradeState.getPhase());
setTradeState(tradeState);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Setter for Mutable objects // Setter for Mutable objects
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void setTradeState(TradeState tradeState) { public void setState(State state) {
super.setTradeState(tradeState); super.setState(state);
switch ((TradeState.BuyerState) tradeState) { if (state == State.WITHDRAW_COMPLETED)
case PREPARATION: tradeProtocol.completed();
break;
case DEPOSIT_PUBLISHED:
takeOfferDate = new Date();
if (this instanceof OffererTrade)
openOfferManager.closeOpenOffer(getOffer());
break;
case DEPOSIT_PUBLISHED_MSG_SENT:
break;
case DEPOSIT_CONFIRMED:
break;
case FIAT_PAYMENT_STARTED:
break;
case FIAT_PAYMENT_STARTED_MSG_SENT:
break;
case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
break;
case PAYOUT_TX_COMMITTED:
break;
case PAYOUT_TX_SENT:
break;
case PAYOUT_BROAD_CASTED:
break;
case WITHDRAW_COMPLETED:
disposeProtocol();
break;
case FAILED:
disposeProtocol();
break;
default:
log.error("Unhandled state " + tradeState);
break;
}
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void handleConfidenceResult() {
if (((TradeState.BuyerState) tradeState).ordinal() < TradeState.BuyerState.DEPOSIT_CONFIRMED.ordinal())
setTradeState(TradeState.BuyerState.DEPOSIT_CONFIRMED);
}
} }

View File

@ -18,64 +18,232 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.crypto.PubKeyRing; import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.fiat.FiatAccount; import io.bitsquare.common.util.JsonExclude;
import io.bitsquare.p2p.Address;
import io.bitsquare.payment.PaymentAccountContractData;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import java.io.Serializable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import java.io.Serializable;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkArgument;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Immutable @Immutable
public class Contract implements Serializable { public class Contract implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility. // That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; @JsonExclude
public static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION;
public final Offer offer; public final Offer offer;
private final long tradeAmount;
public final String takeOfferFeeTxID; public final String takeOfferFeeTxID;
public final Coin tradeAmount; public final Address arbitratorAddress;
public final String buyerAccountID; private final boolean isBuyerOffererOrSellerTaker;
public final String sellerAccountID; private final String offererAccountId;
public final FiatAccount buyerFiatAccount; private final String takerAccountId;
public final FiatAccount sellerFiatAccount; private final PaymentAccountContractData offererPaymentAccountContractData;
public final String buyerP2pSigPubKeyAsString; private final PaymentAccountContractData takerPaymentAccountContractData;
public final String sellerP2pSigPubKeyAsString; @JsonExclude
private final PubKeyRing offererPubKeyRing;
@JsonExclude
private final PubKeyRing takerPubKeyRing;
private final Address buyerAddress;
private final Address sellerAddress;
private final String offererPayoutAddressString;
private final String takerPayoutAddressString;
@JsonExclude
private final byte[] offererBtcPubKey;
@JsonExclude
private final byte[] takerBtcPubKey;
// TODO some basic TAC
public final String tac = "With my signature I commit to the trading agreement of Bitsquare and to fulfill the trade as defined there.";
public Contract(Offer offer, public Contract(Offer offer,
Coin tradeAmount, Coin tradeAmount,
String takeOfferFeeTxID, String takeOfferFeeTxID,
String buyerAccountID, Address buyerAddress,
String sellerAccountID, Address sellerAddress,
FiatAccount buyerFiatAccount, Address arbitratorAddress,
FiatAccount sellerFiatAccount, boolean isBuyerOffererOrSellerTaker,
PubKeyRing buyerPubKeyRing, String offererAccountId,
PubKeyRing sellerPubKeyRing) { String takerAccountId,
PaymentAccountContractData offererPaymentAccountContractData,
PaymentAccountContractData takerPaymentAccountContractData,
PubKeyRing offererPubKeyRing,
PubKeyRing takerPubKeyRing,
String offererPayoutAddressString,
String takerPayoutAddressString,
byte[] offererBtcPubKey,
byte[] takerBtcPubKey) {
this.offer = offer; this.offer = offer;
this.tradeAmount = tradeAmount; this.buyerAddress = buyerAddress;
this.sellerAddress = sellerAddress;
this.tradeAmount = tradeAmount.value;
this.takeOfferFeeTxID = takeOfferFeeTxID; this.takeOfferFeeTxID = takeOfferFeeTxID;
this.buyerAccountID = buyerAccountID; this.arbitratorAddress = arbitratorAddress;
this.sellerAccountID = sellerAccountID; this.isBuyerOffererOrSellerTaker = isBuyerOffererOrSellerTaker;
this.buyerFiatAccount = buyerFiatAccount; this.offererAccountId = offererAccountId;
this.sellerFiatAccount = sellerFiatAccount; this.takerAccountId = takerAccountId;
this.buyerP2pSigPubKeyAsString = buyerPubKeyRing.toString(); this.offererPaymentAccountContractData = offererPaymentAccountContractData;
this.sellerP2pSigPubKeyAsString = sellerPubKeyRing.toString(); this.takerPaymentAccountContractData = takerPaymentAccountContractData;
this.offererPubKeyRing = offererPubKeyRing;
this.takerPubKeyRing = takerPubKeyRing;
this.offererPayoutAddressString = offererPayoutAddressString;
this.takerPayoutAddressString = takerPayoutAddressString;
this.offererBtcPubKey = offererBtcPubKey;
this.takerBtcPubKey = takerBtcPubKey;
}
public String getBuyerAccountId() {
return isBuyerOffererOrSellerTaker ? offererAccountId : takerAccountId;
}
public String getSellerAccountId() {
return isBuyerOffererOrSellerTaker ? takerAccountId : offererAccountId;
}
public String getBuyerPayoutAddressString() {
return isBuyerOffererOrSellerTaker ? offererPayoutAddressString : takerPayoutAddressString;
}
public String getSellerPayoutAddressString() {
return isBuyerOffererOrSellerTaker ? takerPayoutAddressString : offererPayoutAddressString;
}
public PubKeyRing getBuyerPubKeyRing() {
return isBuyerOffererOrSellerTaker ? offererPubKeyRing : takerPubKeyRing;
}
public PubKeyRing getSellerPubKeyRing() {
return isBuyerOffererOrSellerTaker ? takerPubKeyRing : offererPubKeyRing;
}
public byte[] getBuyerBtcPubKey() {
return isBuyerOffererOrSellerTaker ? offererBtcPubKey : takerBtcPubKey;
}
public byte[] getSellerBtcPubKey() {
return isBuyerOffererOrSellerTaker ? takerBtcPubKey : offererBtcPubKey;
}
public PaymentAccountContractData getBuyerPaymentAccountContractData() {
return isBuyerOffererOrSellerTaker ? offererPaymentAccountContractData : takerPaymentAccountContractData;
}
public PaymentAccountContractData getSellerPaymentAccountContractData() {
return isBuyerOffererOrSellerTaker ? takerPaymentAccountContractData : offererPaymentAccountContractData;
}
public String getPaymentMethodName() {
// PaymentMethod need to be the same
checkArgument(offererPaymentAccountContractData.getPaymentMethodName().equals(takerPaymentAccountContractData.getPaymentMethodName()),
"NOT offererPaymentAccountContractData.getPaymentMethodName().equals(takerPaymentAccountContractData.getPaymentMethodName())");
return offererPaymentAccountContractData.getPaymentMethodName();
}
public Coin getTradeAmount() {
return Coin.valueOf(tradeAmount);
}
public Address getBuyerAddress() {
return buyerAddress;
}
public Address getSellerAddress() {
return sellerAddress;
} }
@Override @Override
public String toString() { public String toString() {
return "Contract{" + return "Contract{" +
"offer=" + offer + "tac='" + tac + '\'' +
", takeOfferFeeTxID='" + takeOfferFeeTxID + '\'' + ", offer=" + offer +
", tradeAmount=" + tradeAmount + ", tradeAmount=" + tradeAmount +
", buyerAccountID='" + buyerAccountID + '\'' + ", isBuyerOffererOrSellerTaker=" + isBuyerOffererOrSellerTaker +
", sellerAccountID='" + sellerAccountID + '\'' + ", takeOfferFeeTxID='" + takeOfferFeeTxID + '\'' +
", buyerFiatAccount=" + buyerFiatAccount + ", offererAccountID='" + offererAccountId + '\'' +
", sellerFiatAccount=" + sellerFiatAccount + ", takerAccountID='" + takerAccountId + '\'' +
", buyerP2pSigPubKeyAsString='" + buyerP2pSigPubKeyAsString + '\'' + ", offererPaymentAccount=" + offererPaymentAccountContractData +
", sellerP2pSigPubKeyAsString='" + sellerP2pSigPubKeyAsString + '\'' + ", takerPaymentAccount=" + takerPaymentAccountContractData +
", offererPubKeyRing=" + offererPubKeyRing +
", takerPubKeyRing=" + takerPubKeyRing +
", offererPayoutAddressString='" + offererPayoutAddressString + '\'' +
", takerPayoutAddressString='" + takerPayoutAddressString + '\'' +
", offererBtcPubKey=" + Arrays.toString(offererBtcPubKey) +
", takerBtcPubKey=" + Arrays.toString(takerBtcPubKey) +
'}'; '}';
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Contract)) return false;
Contract contract = (Contract) o;
if (tradeAmount != contract.tradeAmount) return false;
if (isBuyerOffererOrSellerTaker != contract.isBuyerOffererOrSellerTaker) return false;
if (offer != null ? !offer.equals(contract.offer) : contract.offer != null) return false;
if (takeOfferFeeTxID != null ? !takeOfferFeeTxID.equals(contract.takeOfferFeeTxID) : contract.takeOfferFeeTxID != null)
return false;
if (arbitratorAddress != null ? !arbitratorAddress.equals(contract.arbitratorAddress) : contract.arbitratorAddress != null)
return false;
if (offererAccountId != null ? !offererAccountId.equals(contract.offererAccountId) : contract.offererAccountId != null)
return false;
if (takerAccountId != null ? !takerAccountId.equals(contract.takerAccountId) : contract.takerAccountId != null)
return false;
if (offererPaymentAccountContractData != null ? !offererPaymentAccountContractData.equals(contract.offererPaymentAccountContractData) : contract.offererPaymentAccountContractData != null)
return false;
if (takerPaymentAccountContractData != null ? !takerPaymentAccountContractData.equals(contract.takerPaymentAccountContractData) : contract.takerPaymentAccountContractData != null)
return false;
if (offererPubKeyRing != null ? !offererPubKeyRing.equals(contract.offererPubKeyRing) : contract.offererPubKeyRing != null)
return false;
if (takerPubKeyRing != null ? !takerPubKeyRing.equals(contract.takerPubKeyRing) : contract.takerPubKeyRing != null)
return false;
if (buyerAddress != null ? !buyerAddress.equals(contract.buyerAddress) : contract.buyerAddress != null)
return false;
if (sellerAddress != null ? !sellerAddress.equals(contract.sellerAddress) : contract.sellerAddress != null)
return false;
if (offererPayoutAddressString != null ? !offererPayoutAddressString.equals(contract.offererPayoutAddressString) : contract.offererPayoutAddressString != null)
return false;
if (takerPayoutAddressString != null ? !takerPayoutAddressString.equals(contract.takerPayoutAddressString) : contract.takerPayoutAddressString != null)
return false;
if (!Arrays.equals(offererBtcPubKey, contract.offererBtcPubKey)) return false;
if (!Arrays.equals(takerBtcPubKey, contract.takerBtcPubKey)) return false;
return !(tac != null ? !tac.equals(contract.tac) : contract.tac != null);
}
@Override
public int hashCode() {
int result = offer != null ? offer.hashCode() : 0;
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
result = 31 * result + (takeOfferFeeTxID != null ? takeOfferFeeTxID.hashCode() : 0);
result = 31 * result + (arbitratorAddress != null ? arbitratorAddress.hashCode() : 0);
result = 31 * result + (isBuyerOffererOrSellerTaker ? 1 : 0);
result = 31 * result + (offererAccountId != null ? offererAccountId.hashCode() : 0);
result = 31 * result + (takerAccountId != null ? takerAccountId.hashCode() : 0);
result = 31 * result + (offererPaymentAccountContractData != null ? offererPaymentAccountContractData.hashCode() : 0);
result = 31 * result + (takerPaymentAccountContractData != null ? takerPaymentAccountContractData.hashCode() : 0);
result = 31 * result + (offererPubKeyRing != null ? offererPubKeyRing.hashCode() : 0);
result = 31 * result + (takerPubKeyRing != null ? takerPubKeyRing.hashCode() : 0);
result = 31 * result + (buyerAddress != null ? buyerAddress.hashCode() : 0);
result = 31 * result + (sellerAddress != null ? sellerAddress.hashCode() : 0);
result = 31 * result + (offererPayoutAddressString != null ? offererPayoutAddressString.hashCode() : 0);
result = 31 * result + (takerPayoutAddressString != null ? takerPayoutAddressString.hashCode() : 0);
result = 31 * result + (offererBtcPubKey != null ? Arrays.hashCode(offererBtcPubKey) : 0);
result = 31 * result + (takerBtcPubKey != null ? Arrays.hashCode(takerBtcPubKey) : 0);
result = 31 * result + (tac != null ? tac.hashCode() : 0);
return result;
}
} }

View File

@ -17,9 +17,10 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.Address;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
public interface OffererTrade { public interface OffererTrade {
void handleTakeOfferRequest(TradeMessage message, Peer taker); void handleTakeOfferRequest(TradeMessage message, Address peerAddress);
} }

View File

@ -18,20 +18,19 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.OffererProtocol; import io.bitsquare.trade.protocol.trade.OffererProtocol;
import io.bitsquare.trade.protocol.trade.SellerAsOffererProtocol; import io.bitsquare.trade.protocol.trade.SellerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SellerAsOffererTrade extends SellerTrade implements OffererTrade, Serializable { public class SellerAsOffererTrade extends SellerTrade implements OffererTrade, 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;
@ -48,10 +47,13 @@ public class SellerAsOffererTrade extends SellerTrade implements OffererTrade, S
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
@Override @Override
@ -65,7 +67,7 @@ public class SellerAsOffererTrade extends SellerTrade implements OffererTrade, S
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void handleTakeOfferRequest(TradeMessage message, Peer taker) { public void handleTakeOfferRequest(TradeMessage message, Address taker) {
((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker); ((OffererProtocol) tradeProtocol).handleTakeOfferRequest(message, taker);
} }
} }

View File

@ -18,19 +18,19 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol; import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.TakerProtocol; import io.bitsquare.trade.protocol.trade.TakerProtocol;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger; import static com.google.common.base.Preconditions.checkArgument;
import org.slf4j.LoggerFactory;
public class SellerAsTakerTrade extends SellerTrade implements TakerTrade, Serializable { public class SellerAsTakerTrade extends SellerTrade implements TakerTrade, 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.
@ -43,15 +43,18 @@ public class SellerAsTakerTrade extends SellerTrade implements TakerTrade, Seria
// Constructor, initialization // Constructor, initialization
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public SellerAsTakerTrade(Offer offer, Coin tradeAmount, Peer tradingPeer, Storage<? extends TradableList> storage) { public SellerAsTakerTrade(Offer offer, Coin tradeAmount, Address tradingPeerAddress, Storage<? extends TradableList> storage) {
super(offer, tradeAmount, tradingPeer, storage); super(offer, tradeAmount, tradingPeerAddress, storage);
} }
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
@Override @Override
@ -66,7 +69,7 @@ public class SellerAsTakerTrade extends SellerTrade implements TakerTrade, Seria
@Override @Override
public void takeAvailableOffer() { public void takeAvailableOffer() {
assert tradeProtocol instanceof TakerProtocol; checkArgument(tradeProtocol instanceof TakerProtocol, "tradeProtocol NOT instanceof TakerProtocol");
((TakerProtocol) tradeProtocol).takeAvailableOffer(); ((TakerProtocol) tradeProtocol).takeAvailableOffer();
} }
} }

View File

@ -18,19 +18,17 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.Address;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.protocol.trade.SellerProtocol; import io.bitsquare.trade.protocol.trade.SellerProtocol;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import static com.google.common.base.Preconditions.checkArgument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class SellerTrade extends Trade implements Serializable { public abstract class SellerTrade extends Trade 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.
@ -38,8 +36,8 @@ public abstract class SellerTrade extends Trade implements Serializable {
transient private static final Logger log = LoggerFactory.getLogger(BuyerAsTakerTrade.class); transient private static final Logger log = LoggerFactory.getLogger(BuyerAsTakerTrade.class);
public SellerTrade(Offer offer, Coin tradeAmount, Peer tradingPeer, Storage<? extends TradableList> storage) { public SellerTrade(Offer offer, Coin tradeAmount, Address tradingPeerAddress, Storage<? extends TradableList> storage) {
super(offer, tradeAmount, tradingPeer, storage); super(offer, tradeAmount, tradingPeerAddress, storage);
} }
public SellerTrade(Offer offer, Storage<? extends TradableList> storage) { public SellerTrade(Offer offer, Storage<? extends TradableList> storage) {
@ -48,88 +46,27 @@ public abstract class SellerTrade extends Trade implements Serializable {
@Override @Override
protected void initStates() { protected void initStates() {
if (tradeState == null) if (state == null)
tradeState = TradeState.SellerState.PREPARATION; state = State.PREPARATION;
initStateProperties();
} }
public void onFiatPaymentReceived() { public void onFiatPaymentReceived() {
assert tradeProtocol instanceof SellerProtocol; checkArgument(tradeProtocol instanceof SellerProtocol, "tradeProtocol NOT instanceof SellerProtocol");
((SellerProtocol) tradeProtocol).onFiatPaymentReceived(); ((SellerProtocol) tradeProtocol).onFiatPaymentReceived();
} }
public boolean isFailedState() {
return tradeState == TradeState.SellerState.FAILED;
}
public void setFailedState() {
TradeState tradeState = TradeState.SellerState.FAILED;
// We store the phase of the last state into the failed state
tradeState.setPhase(tradeState.getPhase());
setTradeState(tradeState);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Setter for Mutable objects // Setter for Mutable objects
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void setTradeState(TradeState tradeState) { public void setState(State state) {
super.setTradeState(tradeState); super.setState(state);
switch ((TradeState.SellerState) tradeState) { if (state == State.WITHDRAW_COMPLETED)
tradeProtocol.completed();
case PREPARATION:
break;
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
takeOfferDate = new Date();
if (this instanceof OffererTrade)
openOfferManager.closeOpenOffer(getOffer());
break;
case DEPOSIT_CONFIRMED:
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
break;
case FIAT_PAYMENT_RECEIPT:
break;
case FIAT_PAYMENT_RECEIPT_MSG_SENT:
break;
case PAYOUT_TX_RECEIVED:
break;
case PAYOUT_TX_COMMITTED:
break;
case PAYOUT_BROAD_CASTED:
break;
case WITHDRAW_COMPLETED:
disposeProtocol();
break;
case FAILED:
disposeProtocol();
break;
default:
log.error("Unhandled state " + tradeState);
break;
}
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void handleConfidenceResult() {
if (((TradeState.SellerState) tradeState).ordinal() < TradeState.SellerState.DEPOSIT_CONFIRMED.ordinal())
setTradeState(TradeState.SellerState.DEPOSIT_CONFIRMED);
}
} }

View File

@ -20,7 +20,6 @@ package io.bitsquare.trade;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
public interface Tradable extends Serializable { public interface Tradable extends Serializable {
@ -29,4 +28,6 @@ public interface Tradable extends Serializable {
Date getDate(); Date getDate();
String getId(); String getId();
String getShortId();
} }

View File

@ -19,19 +19,16 @@ package io.bitsquare.trade;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TradableList<T extends Tradable> extends ArrayList<T> implements Serializable { public class TradableList<T extends Tradable> extends ArrayList<T> 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;
@ -59,7 +56,11 @@ public class TradableList<T extends Tradable> extends ArrayList<T> implements Se
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
@Override @Override

View File

@ -17,52 +17,41 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitrationRepository; import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BlockChainService; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.taskrunner.Model; import io.bitsquare.common.taskrunner.Model;
import io.bitsquare.crypto.CryptoService; import io.bitsquare.p2p.Address;
import io.bitsquare.crypto.KeyRing; import io.bitsquare.p2p.P2PService;
import io.bitsquare.crypto.MessageWithPubKey; import io.bitsquare.p2p.messaging.DecryptedMessageWithPubKey;
import io.bitsquare.p2p.AddressService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Peer;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.trade.protocol.trade.ProcessModel; import io.bitsquare.trade.protocol.trade.ProcessModel;
import io.bitsquare.trade.protocol.trade.TradeProtocol; import io.bitsquare.trade.protocol.trade.TradeProtocol;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import javafx.beans.property.*;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.utils.Fiat; import org.bitcoinj.utils.Fiat;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Throwables; import javax.annotation.Nullable;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import javax.annotation.Nullable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are * Holds all data which are relevant to the trade, but not those which are only needed in the trade process as shared data between tasks. Those data are
* stored in the task model. * stored in the task model.
@ -73,20 +62,72 @@ abstract public class Trade implements Tradable, Model, Serializable {
private transient static final Logger log = LoggerFactory.getLogger(Trade.class); private transient static final Logger log = LoggerFactory.getLogger(Trade.class);
/* public enum CriticalPhase { public enum State {
PREPARATION(Phase.PREPARATION),
TAKER_FEE_PAID(Phase.TAKER_FEE_PAID),
DEPOSIT_PUBLISH_REQUESTED(Phase.DEPOSIT_REQUESTED),
DEPOSIT_PUBLISHED(Phase.DEPOSIT_PAID),
DEPOSIT_SEEN_IN_NETWORK(Phase.DEPOSIT_PAID), // triggered by balance update, used only in error cases
DEPOSIT_PUBLISHED_MSG_SENT(Phase.DEPOSIT_PAID),
DEPOSIT_PUBLISHED_MSG_RECEIVED(Phase.DEPOSIT_PAID),
DEPOSIT_CONFIRMED(Phase.DEPOSIT_PAID),
FIAT_PAYMENT_STARTED(Phase.FIAT_SENT),
FIAT_PAYMENT_STARTED_MSG_SENT(Phase.FIAT_SENT),
FIAT_PAYMENT_STARTED_MSG_RECEIVED(Phase.FIAT_SENT),
FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED),
FIAT_PAYMENT_RECEIPT_MSG_SENT(Phase.FIAT_RECEIVED),
FIAT_PAYMENT_RECEIPT_MSG_RECEIVED(Phase.FIAT_RECEIVED),
PAYOUT_TX_COMMITTED(Phase.PAYOUT_PAID),
PAYOUT_TX_SENT(Phase.PAYOUT_PAID),
PAYOUT_TX_RECEIVED(Phase.PAYOUT_PAID),
PAYOUT_BROAD_CASTED(Phase.PAYOUT_PAID),
WITHDRAW_COMPLETED(Phase.WITHDRAWN);
public Phase getPhase() {
return phase;
}
private final Phase phase;
State(Phase phase) {
this.phase = phase;
}
}
public enum Phase {
PREPARATION, PREPARATION,
TAKER_FEE_PAID, TAKER_FEE_PAID,
DEPOSIT_REQUESTED,
DEPOSIT_PAID, DEPOSIT_PAID,
FIAT_SENT, FIAT_SENT,
FIAT_RECEIVED, FIAT_RECEIVED,
PAYOUT_PAID, PAYOUT_PAID,
WITHDRAWN, WITHDRAWN,
FAILED DISPUTE
}*/ }
public enum DisputeState {
NONE,
DISPUTE_REQUESTED,
DISPUTE_STARTED_BY_PEER,
DISPUTE_CLOSED
}
public enum TradePeriodState {
NORMAL,
HALF_REACHED,
TRADE_PERIOD_OVER
}
// Mutable // Mutable
private Coin tradeAmount; private Coin tradeAmount;
private Peer tradingPeer; private Address tradingPeerAddress;
private transient ObjectProperty<Coin> tradeAmountProperty; private transient ObjectProperty<Coin> tradeAmountProperty;
private transient ObjectProperty<Fiat> tradeVolumeProperty; private transient ObjectProperty<Fiat> tradeVolumeProperty;
@ -96,31 +137,41 @@ abstract public class Trade implements Tradable, Model, Serializable {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Transient/Immutable // Transient/Immutable
private transient ObjectProperty<TradeState> processStateProperty; private transient ObjectProperty<State> processStateProperty;
private transient ObjectProperty<DisputeState> disputeStateProperty;
private transient ObjectProperty<TradePeriodState> tradePeriodStateProperty;
// Trades are saved in the TradeList // Trades are saved in the TradeList
transient private Storage<? extends TradableList> storage; transient private Storage<? extends TradableList> storage;
transient protected TradeProtocol tradeProtocol; transient protected TradeProtocol tradeProtocol;
transient protected TradeManager tradeManager;
transient protected OpenOfferManager openOfferManager;
// Immutable // Immutable
private final Offer offer; private final Offer offer;
private final ProcessModel processModel; private final ProcessModel processModel;
private final Date creationDate;
// Mutable // Mutable
private MessageWithPubKey messageWithPubKey; private DecryptedMessageWithPubKey decryptedMessageWithPubKey;
protected Date takeOfferDate; private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
protected TradeState tradeState;
protected State state;
private DisputeState disputeState = DisputeState.NONE;
private TradePeriodState tradePeriodState = TradePeriodState.NORMAL;
private Transaction depositTx; private Transaction depositTx;
private Contract contract; private Contract contract;
private String contractAsJson; private String contractAsJson;
private String sellerContractSignature; private byte[] contractHash;
private String buyerContractSignature; private String takerContractSignature;
private String offererContractSignature;
private Transaction payoutTx; private Transaction payoutTx;
private long lockTime; private long lockTimeAsBlockHeight;
private int openDisputeTimeAsBlockHeight;
private int checkPaymentTimeAsBlockHeight;
private String arbitratorId;
private Address arbitratorAddress;
private String takerPaymentAccountId;
private boolean halfTradePeriodReachedWarningDisplayed;
private boolean tradePeriodOverWarningDisplayed;
private String errorMessage; private String errorMessage;
transient private StringProperty errorMessageProperty;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -134,55 +185,50 @@ abstract public class Trade implements Tradable, Model, Serializable {
processModel = new ProcessModel(); processModel = new ProcessModel();
tradeVolumeProperty = new SimpleObjectProperty<>(); tradeVolumeProperty = new SimpleObjectProperty<>();
tradeAmountProperty = new SimpleObjectProperty<>(); tradeAmountProperty = new SimpleObjectProperty<>();
errorMessageProperty = new SimpleStringProperty();
initStates(); initStates();
initStateProperties(); initStateProperties();
// That will only be used in case of a canceled open offer trade
creationDate = new Date();
} }
// taker // taker
protected Trade(Offer offer, Coin tradeAmount, Peer tradingPeer, protected Trade(Offer offer, Coin tradeAmount, Address tradingPeerAddress,
Storage<? extends TradableList> storage) { Storage<? extends TradableList> storage) {
this(offer, storage); this(offer, storage);
this.tradeAmount = tradeAmount; this.tradeAmount = tradeAmount;
this.tradingPeer = tradingPeer; this.tradingPeerAddress = tradingPeerAddress;
tradeAmountProperty.set(tradeAmount); tradeAmountProperty.set(tradeAmount);
tradeVolumeProperty.set(getTradeVolume()); tradeVolumeProperty.set(getTradeVolume());
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); try {
in.defaultReadObject();
initStateProperties(); initStateProperties();
initAmountProperty(); initAmountProperty();
errorMessageProperty = new SimpleStringProperty(errorMessage);
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
} }
public void init(MessageService messageService, public void init(P2PService p2PService,
WalletService walletService, WalletService walletService,
AddressService addressService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
BlockChainService blockChainService, ArbitratorManager arbitratorManager,
CryptoService cryptoService,
ArbitrationRepository arbitrationRepository,
TradeManager tradeManager, TradeManager tradeManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
User user, User user,
KeyRing keyRing) { KeyRing keyRing) {
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
processModel.onAllServicesInitialized(offer, processModel.onAllServicesInitialized(offer,
messageService, tradeManager,
addressService, openOfferManager,
p2PService,
walletService, walletService,
tradeWalletService, tradeWalletService,
blockChainService, arbitratorManager,
cryptoService,
arbitrationRepository,
user, user,
keyRing); keyRing);
@ -190,15 +236,15 @@ abstract public class Trade implements Tradable, Model, Serializable {
tradeProtocol.checkPayoutTxTimeLock(this); tradeProtocol.checkPayoutTxTimeLock(this);
if (messageWithPubKey != null) { if (decryptedMessageWithPubKey != null) {
tradeProtocol.applyMailboxMessage(messageWithPubKey, this); tradeProtocol.applyMailboxMessage(decryptedMessageWithPubKey, this);
// After applied to protocol we remove it
messageWithPubKey = null;
} }
} }
protected void initStateProperties() { protected void initStateProperties() {
processStateProperty = new SimpleObjectProperty<>(tradeState); processStateProperty = new SimpleObjectProperty<>(state);
disputeStateProperty = new SimpleObjectProperty<>(disputeState);
tradePeriodStateProperty = new SimpleObjectProperty<>(tradePeriodState);
} }
protected void initAmountProperty() { protected void initAmountProperty() {
@ -223,43 +269,72 @@ abstract public class Trade implements Tradable, Model, Serializable {
} }
public void setDepositTx(Transaction tx) { public void setDepositTx(Transaction tx) {
log.debug("setDepositTx " + tx);
this.depositTx = tx; this.depositTx = tx;
setupConfidenceListener(); setupConfidenceListener();
storage.queueUpForSave(); storage.queueUpForSave();
} }
public void disposeProtocol() { @Nullable
if (tradeProtocol != null) { public Transaction getDepositTx() {
tradeProtocol.cleanup(); return depositTx;
tradeProtocol = null;
}
} }
public void setMailboxMessage(MessageWithPubKey messageWithPubKey) { public void setMailboxMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey) {
this.messageWithPubKey = messageWithPubKey; log.trace("setMailboxMessage " + decryptedMessageWithPubKey);
this.decryptedMessageWithPubKey = decryptedMessageWithPubKey;
}
public DecryptedMessageWithPubKey getMailboxMessage() {
return decryptedMessageWithPubKey;
} }
public void setStorage(Storage<? extends TradableList> storage) { public void setStorage(Storage<? extends TradableList> storage) {
this.storage = storage; this.storage = storage;
} }
public void setTradeState(TradeState tradeState) {
this.tradeState = tradeState; ///////////////////////////////////////////////////////////////////////////////////////////
processStateProperty.set(tradeState); // States
///////////////////////////////////////////////////////////////////////////////////////////
public void setState(State state) {
this.state = state;
processStateProperty.set(state);
storage.queueUpForSave(); storage.queueUpForSave();
} }
abstract public boolean isFailedState(); public void setDisputeState(DisputeState disputeState) {
this.disputeState = disputeState;
abstract public void setFailedState(); disputeStateProperty.set(disputeState);
storage.queueUpForSave();
public boolean isCriticalFault() {
return tradeState.getPhase() != null && tradeState.getPhase().ordinal() >= TradeState.Phase.DEPOSIT_PAID.ordinal();
} }
public DisputeState getDisputeState() {
return disputeState;
}
public void setTradePeriodState(TradePeriodState tradePeriodState) {
this.tradePeriodState = tradePeriodState;
tradePeriodStateProperty.set(tradePeriodState);
storage.queueUpForSave();
}
public TradePeriodState getTradePeriodState() {
return tradePeriodState;
}
public boolean isTakerFeePaid() {
return state.getPhase() != null && state.getPhase().ordinal() >= Phase.TAKER_FEE_PAID.ordinal();
}
public State getState() {
return state;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Storage // Model implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Get called from taskRunner after each completed task // Get called from taskRunner after each completed task
@ -282,22 +357,16 @@ abstract public class Trade implements Tradable, Model, Serializable {
return offer.getId(); return offer.getId();
} }
public String getShortId() {
return offer.getShortId();
}
public Offer getOffer() { public Offer getOffer() {
return offer; return offer;
} }
@Nullable
public Transaction getDepositTx() {
return depositTx;
}
@NotNull
public Coin getSecurityDeposit() {
return offer.getSecurityDeposit();
}
public Coin getPayoutAmount() { public Coin getPayoutAmount() {
return getSecurityDeposit(); return FeePolicy.SECURITY_DEPOSIT;
} }
public ProcessModel getProcessModel() { public ProcessModel getProcessModel() {
@ -312,7 +381,8 @@ abstract public class Trade implements Tradable, Model, Serializable {
return null; return null;
} }
public ReadOnlyObjectProperty<? extends TradeState> tradeStateProperty() {
public ReadOnlyObjectProperty<? extends State> stateProperty() {
return processStateProperty; return processStateProperty;
} }
@ -325,25 +395,36 @@ abstract public class Trade implements Tradable, Model, Serializable {
} }
public ReadOnlyObjectProperty<DisputeState> disputeStateProperty() {
return disputeStateProperty;
}
public ReadOnlyObjectProperty<TradePeriodState> getTradePeriodStateProperty() {
return tradePeriodStateProperty;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getter/Setter for Mutable objects // Getter/Setter for Mutable objects
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public Date getDate() { public Date getDate() {
return takeOfferDate != null ? takeOfferDate : creationDate; return takeOfferDate;
} }
public void setTakeOfferDate(Date takeOfferDate) { public void setTakeOfferDate(Date takeOfferDate) {
this.takeOfferDate = takeOfferDate; this.takeOfferDate = takeOfferDate;
} }
public void setTradingPeer(Peer tradingPeer) { public void setTradingPeerAddress(Address tradingPeerAddress) {
this.tradingPeer = tradingPeer; if (tradingPeerAddress == null)
log.error("tradingPeerAddress=null");
else
this.tradingPeerAddress = tradingPeerAddress;
} }
@Nullable @Nullable
public Peer getTradingPeer() { public Address getTradingPeerAddress() {
return tradingPeer; return tradingPeerAddress;
} }
public void setTradeAmount(Coin tradeAmount) { public void setTradeAmount(Coin tradeAmount) {
@ -357,31 +438,46 @@ abstract public class Trade implements Tradable, Model, Serializable {
return tradeAmount; return tradeAmount;
} }
public void setLockTime(long lockTime) { public void setLockTimeAsBlockHeight(long lockTimeAsBlockHeight) {
log.debug("lockTime " + lockTime); this.lockTimeAsBlockHeight = lockTimeAsBlockHeight;
this.lockTime = lockTime;
} }
public long getLockTime() { public long getLockTimeAsBlockHeight() {
return lockTime; return lockTimeAsBlockHeight;
} }
public void setSellerContractSignature(String takerSignature) { public int getOpenDisputeTimeAsBlockHeight() {
this.sellerContractSignature = takerSignature; return openDisputeTimeAsBlockHeight;
}
public void setOpenDisputeTimeAsBlockHeight(int openDisputeTimeAsBlockHeight) {
this.openDisputeTimeAsBlockHeight = openDisputeTimeAsBlockHeight;
}
public int getCheckPaymentTimeAsBlockHeight() {
return checkPaymentTimeAsBlockHeight;
}
public void setCheckPaymentTimeAsBlockHeight(int checkPaymentTimeAsBlockHeight) {
this.checkPaymentTimeAsBlockHeight = checkPaymentTimeAsBlockHeight;
}
public void setTakerContractSignature(String takerSignature) {
this.takerContractSignature = takerSignature;
} }
@Nullable @Nullable
public String getSellerContractSignature() { public String getTakerContractSignature() {
return sellerContractSignature; return takerContractSignature;
} }
public void setBuyerContractSignature(String buyerContractSignature) { public void setOffererContractSignature(String offererContractSignature) {
this.buyerContractSignature = buyerContractSignature; this.offererContractSignature = offererContractSignature;
} }
@Nullable @Nullable
public String getBuyerContractSignature() { public String getOffererContractSignature() {
return buyerContractSignature; return offererContractSignature;
} }
public void setContractAsJson(String contractAsJson) { public void setContractAsJson(String contractAsJson) {
@ -414,20 +510,53 @@ abstract public class Trade implements Tradable, Model, Serializable {
public void setErrorMessage(String errorMessage) { public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
errorMessageProperty.set(errorMessage);
if (errorMessage != null && errorMessage.length() > 0) {
setFailedState();
if (isCriticalFault())
tradeManager.addTradeToFailedTrades(this);
else if (isFailedState())
tradeManager.addTradeToClosedTrades(this);
}
} }
@Nullable public ReadOnlyStringProperty errorMessageProperty() {
public String getErrorMessage() { return errorMessageProperty;
return errorMessage; }
public Address getArbitratorAddress() {
return arbitratorAddress;
}
public void setArbitratorAddress(Address arbitratorAddress) {
this.arbitratorAddress = arbitratorAddress;
}
public String getTakerPaymentAccountId() {
return takerPaymentAccountId;
}
public void setTakerPaymentAccountId(String takerPaymentAccountId) {
this.takerPaymentAccountId = takerPaymentAccountId;
}
public void setHalfTradePeriodReachedWarningDisplayed(boolean halfTradePeriodReachedWarningDisplayed) {
this.halfTradePeriodReachedWarningDisplayed = halfTradePeriodReachedWarningDisplayed;
storage.queueUpForSave();
}
public boolean isHalfTradePeriodReachedWarningDisplayed() {
return halfTradePeriodReachedWarningDisplayed;
}
public void setTradePeriodOverWarningDisplayed(boolean tradePeriodOverWarningDisplayed) {
this.tradePeriodOverWarningDisplayed = tradePeriodOverWarningDisplayed;
storage.queueUpForSave();
}
public boolean isTradePeriodOverWarningDisplayed() {
return tradePeriodOverWarningDisplayed;
}
public void setContractHash(byte[] contractHash) {
this.contractHash = contractHash;
}
public byte[] getContractHash() {
return contractHash;
} }
@ -436,28 +565,42 @@ abstract public class Trade implements Tradable, Model, Serializable {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setupConfidenceListener() { private void setupConfidenceListener() {
log.debug("setupConfidenceListener");
if (depositTx != null) { if (depositTx != null) {
TransactionConfidence transactionConfidence = depositTx.getConfidence(); TransactionConfidence transactionConfidence = depositTx.getConfidence();
ListenableFuture<TransactionConfidence> future = transactionConfidence.getDepthFuture(1); log.debug("transactionConfidence " + transactionConfidence.getDepthInBlocks());
Futures.addCallback(future, new FutureCallback<TransactionConfidence>() { if (transactionConfidence.getDepthInBlocks() > 0) {
@Override handleConfidenceResult();
public void onSuccess(TransactionConfidence result) { } else {
handleConfidenceResult(); ListenableFuture<TransactionConfidence> future = transactionConfidence.getDepthFuture(1);
} Futures.addCallback(future, new FutureCallback<TransactionConfidence>() {
@Override
public void onSuccess(TransactionConfidence result) {
log.debug("transactionConfidence " + transactionConfidence.getDepthInBlocks());
log.debug("state " + state);
handleConfidenceResult();
}
@Override @Override
public void onFailure(@NotNull Throwable t) { public void onFailure(@NotNull Throwable t) {
t.printStackTrace(); t.printStackTrace();
log.error(t.getMessage()); log.error(t.getMessage());
Throwables.propagate(t); Throwables.propagate(t);
} }
}); });
}
} else {
log.error("depositTx == null. That must not happen.");
} }
} }
abstract protected void createProtocol(); abstract protected void createProtocol();
abstract protected void handleConfidenceResult(); private void handleConfidenceResult() {
if (state.ordinal() < State.DEPOSIT_CONFIRMED.ordinal())
setState(State.DEPOSIT_CONFIRMED);
}
abstract protected void initStates(); abstract protected void initStates();
@ -465,7 +608,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
public String toString() { public String toString() {
return "Trade{" + return "Trade{" +
"tradeAmount=" + tradeAmount + "tradeAmount=" + tradeAmount +
", tradingPeer=" + tradingPeer + ", tradingPeer=" + tradingPeerAddress +
", tradeAmountProperty=" + tradeAmountProperty + ", tradeAmountProperty=" + tradeAmountProperty +
", tradeVolumeProperty=" + tradeVolumeProperty + ", tradeVolumeProperty=" + tradeVolumeProperty +
", processStateProperty=" + processStateProperty + ", processStateProperty=" + processStateProperty +
@ -474,8 +617,8 @@ abstract public class Trade implements Tradable, Model, Serializable {
", offer=" + offer + ", offer=" + offer +
", date=" + takeOfferDate + ", date=" + takeOfferDate +
", processModel=" + processModel + ", processModel=" + processModel +
", processState=" + tradeState + ", processState=" + state +
", messageWithPubKey=" + messageWithPubKey + ", messageWithPubKey=" + decryptedMessageWithPubKey +
", depositTx=" + depositTx + ", depositTx=" + depositTx +
/* ", contract=" + contract + /* ", contract=" + contract +
", contractAsJson='" + contractAsJson + '\'' +*/ ", contractAsJson='" + contractAsJson + '\'' +*/
@ -485,5 +628,4 @@ abstract public class Trade implements Tradable, Model, Serializable {
", errorMessage='" + errorMessage + '\'' + ", errorMessage='" + errorMessage + '\'' +
'}'; '}';
} }
} }

View File

@ -17,61 +17,52 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.arbitration.ArbitrationRepository; import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.FaultHandler; import io.bitsquare.common.handlers.FaultHandler;
import io.bitsquare.common.handlers.ResultHandler; import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.crypto.CryptoService; import io.bitsquare.p2p.Address;
import io.bitsquare.crypto.KeyRing;
import io.bitsquare.crypto.MessageWithPubKey;
import io.bitsquare.crypto.SealedAndSignedMessage;
import io.bitsquare.p2p.AddressService;
import io.bitsquare.p2p.DecryptedMessageHandler;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.p2p.messaging.DecryptedMailListener;
import io.bitsquare.p2p.messaging.DecryptedMailboxListener;
import io.bitsquare.p2p.messaging.DecryptedMessageWithPubKey;
import io.bitsquare.storage.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.closed.ClosedTradableManager; import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager; import io.bitsquare.trade.failed.FailedTradesManager;
import io.bitsquare.trade.handlers.TakeOfferResultHandler; import io.bitsquare.trade.handlers.TradeResultHandler;
import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOffer; import io.bitsquare.trade.offer.OpenOffer;
import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.trade.protocol.availability.OfferAvailabilityModel; import io.bitsquare.trade.protocol.availability.OfferAvailabilityModel;
import io.bitsquare.trade.protocol.trade.messages.DepositTxInputsRequest;
import io.bitsquare.trade.protocol.trade.messages.PayDepositRequest; import io.bitsquare.trade.protocol.trade.messages.PayDepositRequest;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ObservableList;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import com.google.common.util.concurrent.FutureCallback; import javax.inject.Inject;
import javax.inject.Named;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.collections.ObservableList;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.util.Validator.nonEmptyStringOf; import static io.bitsquare.util.Validator.nonEmptyStringOf;
public class TradeManager { public class TradeManager {
@ -79,20 +70,18 @@ public class TradeManager {
private final User user; private final User user;
private final KeyRing keyRing; private final KeyRing keyRing;
private final MessageService messageService;
private final MailboxService mailboxService;
private final AddressService addressService;
private final BlockChainService blockChainService;
private final WalletService walletService; private final WalletService walletService;
private final TradeWalletService tradeWalletService; private final TradeWalletService tradeWalletService;
private final CryptoService<MailboxMessage> cryptoService;
private final OpenOfferManager openOfferManager; private final OpenOfferManager openOfferManager;
private final ClosedTradableManager closedTradableManager; private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager; private final FailedTradesManager failedTradesManager;
private final ArbitrationRepository arbitrationRepository; private final ArbitratorManager arbitratorManager;
private final P2PService p2PService;
private final Storage<TradableList<Trade>> pendingTradesStorage; private final Storage<TradableList<Trade>> tradableListStorage;
private final TradableList<Trade> pendingTrades; private final TradableList<Trade> trades;
private final BooleanProperty pendingTradesInitialized = new SimpleBooleanProperty();
private P2PServiceListener p2PServiceListener;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -102,34 +91,86 @@ public class TradeManager {
@Inject @Inject
public TradeManager(User user, public TradeManager(User user,
KeyRing keyRing, KeyRing keyRing,
MessageService messageService,
MailboxService mailboxService,
AddressService addressService,
BlockChainService blockChainService,
WalletService walletService, WalletService walletService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
CryptoService<MailboxMessage> cryptoService,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager, FailedTradesManager failedTradesManager,
ArbitrationRepository arbitrationRepository, ArbitratorManager arbitratorManager,
P2PService p2PService,
@Named("storage.dir") File storageDir) { @Named("storage.dir") File storageDir) {
this.user = user; this.user = user;
this.keyRing = keyRing; this.keyRing = keyRing;
this.messageService = messageService;
this.mailboxService = mailboxService;
this.addressService = addressService;
this.blockChainService = blockChainService;
this.walletService = walletService; this.walletService = walletService;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
this.cryptoService = cryptoService;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
this.closedTradableManager = closedTradableManager; this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager; this.failedTradesManager = failedTradesManager;
this.arbitrationRepository = arbitrationRepository; this.arbitratorManager = arbitratorManager;
this.p2PService = p2PService;
pendingTradesStorage = new Storage<>(storageDir); tradableListStorage = new Storage<>(storageDir);
this.pendingTrades = new TradableList<>(pendingTradesStorage, "PendingTrades"); this.trades = new TradableList<>(tradableListStorage, "PendingTrades");
p2PService.addDecryptedMailListener(new DecryptedMailListener() {
@Override
public void onMailMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Address peerAddress) {
Message message = decryptedMessageWithPubKey.message;
// Handler for incoming initial messages from taker
if (message instanceof PayDepositRequest) {
log.trace("Received PayDepositRequest: " + message);
handleInitialTakeOfferRequest((PayDepositRequest) message, peerAddress);
}
}
});
p2PService.addDecryptedMailboxListener(new DecryptedMailboxListener() {
@Override
public void onMailboxMessageAdded(DecryptedMessageWithPubKey decryptedMessageWithPubKey, Address senderAddress) {
log.trace("onMailboxMessageAdded decryptedMessageWithPubKey: " + decryptedMessageWithPubKey);
log.trace("onMailboxMessageAdded senderAddress: " + senderAddress);
Message message = decryptedMessageWithPubKey.message;
if (message instanceof PayDepositRequest) {
//TODO is that used????
PayDepositRequest payDepositRequest = (PayDepositRequest) message;
log.trace("Received payDepositRequest: " + payDepositRequest);
if (payDepositRequest.getSenderAddress().equals(senderAddress))
handleInitialTakeOfferRequest(payDepositRequest, senderAddress);
else
log.warn("Peer address not matching for payDepositRequest");
} else if (message instanceof TradeMessage) {
log.trace("Received TradeMessage: " + message);
String tradeId = ((TradeMessage) message).tradeId;
Optional<Trade> tradeOptional = trades.stream().filter(e -> e.getId().equals(tradeId)).findAny();
if (tradeOptional.isPresent())
tradeOptional.get().setMailboxMessage(decryptedMessageWithPubKey);
}
}
});
p2PServiceListener = new P2PServiceListener() {
@Override
public void onTorNodeReady() {
}
@Override
public void onHiddenServiceReady() {
}
@Override
public void onSetupFailed(Throwable throwable) {
}
@Override
public void onAllDataReceived() {
}
@Override
public void onAuthenticated() {
initPendingTrades();
}
};
p2PService.addP2PServiceListener(p2PServiceListener);
} }
@ -141,35 +182,41 @@ public class TradeManager {
// OffererAsBuyerProtocol listens for take offer requests, so we need to instantiate it early. // OffererAsBuyerProtocol listens for take offer requests, so we need to instantiate it early.
public void onAllServicesInitialized() { public void onAllServicesInitialized() {
log.trace("onAllServicesInitialized"); log.trace("onAllServicesInitialized");
// If there are messages in our mailbox we apply it and remove them from the DHT
// We run that before initializing the pending trades to be sure the state is correct
mailboxService.getAllMessages(
(encryptedMailboxMessages) -> {
log.trace("mailboxService.getAllMessages success");
setMailboxMessagesToTrades(encryptedMailboxMessages);
emptyMailbox();
initPendingTrades();
});
// Handler for incoming initial messages from taker
messageService.addDecryptedMessageHandler(new DecryptedMessageHandler() {
@Override
public void handleMessage(MessageWithPubKey messageWithPubKey, Peer sender) {
// We get an encrypted message but don't do the signature check as we don't know the peer yet.
// A basic sig check is in done also at decryption time
Message message = messageWithPubKey.getMessage();
// Those 2 messages are initial request form the taker.
// RequestPayDepositMessage is used also in case of SellerAsTaker but there it is handled in the protocol as it is not an initial request
if (message instanceof DepositTxInputsRequest ||
(message instanceof PayDepositRequest && ((PayDepositRequest) message).isInitialRequest))
handleInitialTakeOfferRequest((TradeMessage) message, sender);
}
});
} }
private void handleInitialTakeOfferRequest(TradeMessage message, Peer sender) { private void initPendingTrades() {
log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + sender); if (p2PServiceListener != null) p2PService.removeP2PServiceListener(p2PServiceListener);
List<Trade> failedTrades = new ArrayList<>();
for (Trade trade : trades) {
// We continue an interrupted trade.
// TODO if the peer has changed its IP address, we need to make another findPeer request. At the moment we use the peer stored in trade to
// continue the trade, but that might fail.
// TODO
/* if (trade.isFailedState()) {
failedTrades.add(trade);
}
else {*/
trade.setStorage(tradableListStorage);
trade.updateDepositTxFromWallet(tradeWalletService);
initTrade(trade);
// after we are authenticated we remove mailbox messages.
DecryptedMessageWithPubKey mailboxMessage = trade.getMailboxMessage();
if (mailboxMessage != null) {
p2PService.removeEntryFromMailbox(mailboxMessage);
trade.setMailboxMessage(null);
}
// }
}
pendingTradesInitialized.set(true);
failedTrades.stream().filter(Trade::isTakerFeePaid).forEach(this::addTradeToFailedTrades);
}
private void handleInitialTakeOfferRequest(TradeMessage message, Address peerAddress) {
log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + peerAddress);
try { try {
nonEmptyStringOf(message.tradeId); nonEmptyStringOf(message.tradeId);
} catch (Throwable t) { } catch (Throwable t) {
@ -184,93 +231,35 @@ public class TradeManager {
Trade trade; Trade trade;
if (offer.getDirection() == Offer.Direction.BUY) if (offer.getDirection() == Offer.Direction.BUY)
trade = new BuyerAsOffererTrade(offer, pendingTradesStorage); trade = new BuyerAsOffererTrade(offer, tradableListStorage);
else else
trade = new SellerAsOffererTrade(offer, pendingTradesStorage); trade = new SellerAsOffererTrade(offer, tradableListStorage);
trade.setStorage(pendingTradesStorage); trade.setStorage(tradableListStorage);
initTrade(trade); initTrade(trade);
pendingTrades.add(trade); trades.add(trade);
((OffererTrade) trade).handleTakeOfferRequest(message, sender); ((OffererTrade) trade).handleTakeOfferRequest(message, peerAddress);
} } else {
else {
// TODO respond // TODO respond
//(RequestDepositTxInputsMessage)message. //(RequestDepositTxInputsMessage)message.
// messageService.sendEncryptedMessage(sender,messageWithPubKey.getMessage().); // messageService.sendEncryptedMessage(peerAddress,messageWithPubKey.getMessage().);
log.info("We received a take offer request but don't have that offer anymore."); log.info("We received a take offer request but don't have that offer anymore.");
} }
} }
private void initTrade(Trade trade) { private void initTrade(Trade trade) {
trade.init(messageService, trade.init(p2PService,
walletService, walletService,
addressService,
tradeWalletService, tradeWalletService,
blockChainService, arbitratorManager,
cryptoService,
arbitrationRepository,
this, this,
openOfferManager, openOfferManager,
user, user,
keyRing); keyRing);
} }
private void setMailboxMessagesToTrades(List<SealedAndSignedMessage> encryptedMessages) {
log.trace("applyMailboxMessage encryptedMailboxMessage.size=" + encryptedMessages.size());
for (SealedAndSignedMessage encrypted : encryptedMessages) {
try {
MessageWithPubKey messageWithPubKey = cryptoService.decryptAndVerifyMessage(encrypted);
Message message = messageWithPubKey.getMessage();
if (message instanceof MailboxMessage && message instanceof TradeMessage) {
String tradeId = ((TradeMessage) message).tradeId;
Optional<Trade> tradeOptional = pendingTrades.stream().filter(e -> e.getId().equals(tradeId)).findAny();
if (tradeOptional.isPresent())
tradeOptional.get().setMailboxMessage(messageWithPubKey);
}
} catch (Throwable e) {
e.printStackTrace();
log.error(e.getMessage());
}
}
}
private void emptyMailbox() {
mailboxService.removeAllMessages(
() -> log.debug("All mailbox entries removed"),
(errorMessage, fault) -> {
log.error(errorMessage);
log.error(fault.getMessage());
});
}
private void initPendingTrades() {
List<Trade> failedTrades = new ArrayList<>();
for (Trade trade : pendingTrades) {
// We continue an interrupted trade.
// TODO if the peer has changed its IP address, we need to make another findPeer request. At the moment we use the peer stored in trade to
// continue the trade, but that might fail.
if (trade.isFailedState()) {
failedTrades.add(trade);
}
else {
trade.setStorage(pendingTradesStorage);
trade.updateDepositTxFromWallet(tradeWalletService);
initTrade(trade);
}
}
for (Trade trade : failedTrades) {
if (trade.isCriticalFault())
addTradeToFailedTrades(trade);
else
addTradeToClosedTrades(trade);
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Called from Offerbook when offer gets removed from DHT // Called from Offerbook when offer gets removed from P2P network
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void onOfferRemovedFromRemoteOfferBook(Offer offer) { public void onOfferRemovedFromRemoteOfferBook(Offer offer) {
@ -282,8 +271,9 @@ public class TradeManager {
// Take offer // Take offer
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void onCheckOfferAvailability(Offer offer) { public void checkOfferAvailability(Offer offer,
offer.checkOfferAvailability(getOfferAvailabilityModel(offer)); ResultHandler resultHandler) {
offer.checkOfferAvailability(getOfferAvailabilityModel(offer), resultHandler);
} }
// When closing take offer view, we are not interested in the onCheckOfferAvailability result anymore, so remove from the map // When closing take offer view, we are not interested in the onCheckOfferAvailability result anymore, so remove from the map
@ -292,34 +282,44 @@ public class TradeManager {
} }
// First we check if offer is still available then we create the trade with the protocol // First we check if offer is still available then we create the trade with the protocol
public void onTakeOffer(Coin amount, Offer offer, TakeOfferResultHandler takeOfferResultHandler) { public void onTakeOffer(Coin amount,
Offer offer,
String paymentAccountId,
TradeResultHandler tradeResultHandler) {
final OfferAvailabilityModel model = getOfferAvailabilityModel(offer); final OfferAvailabilityModel model = getOfferAvailabilityModel(offer);
offer.checkOfferAvailability(model, () -> { offer.checkOfferAvailability(model,
if (offer.getState() == Offer.State.AVAILABLE) () -> {
createTrade(amount, offer, model, takeOfferResultHandler); if (offer.getState() == Offer.State.AVAILABLE)
}); createTrade(amount, offer, paymentAccountId, model, tradeResultHandler);
});
} }
private void createTrade(Coin amount, Offer offer, OfferAvailabilityModel model, TakeOfferResultHandler takeOfferResultHandler) { private void createTrade(Coin amount,
Offer offer,
String paymentAccountId,
OfferAvailabilityModel model,
TradeResultHandler tradeResultHandler) {
Trade trade; Trade trade;
if (offer.getDirection() == Offer.Direction.BUY) if (offer.getDirection() == Offer.Direction.BUY)
trade = new SellerAsTakerTrade(offer, amount, model.getPeer(), pendingTradesStorage); trade = new SellerAsTakerTrade(offer, amount, model.getPeerAddress(), tradableListStorage);
else else
trade = new BuyerAsTakerTrade(offer, amount, model.getPeer(), pendingTradesStorage); trade = new BuyerAsTakerTrade(offer, amount, model.getPeerAddress(), tradableListStorage);
trade.setTakeOfferDate(new Date()); trade.setTakeOfferDate(new Date());
trade.setTakerPaymentAccountId(paymentAccountId);
initTrade(trade); initTrade(trade);
pendingTrades.add(trade);
trades.add(trade);
((TakerTrade) trade).takeAvailableOffer(); ((TakerTrade) trade).takeAvailableOffer();
takeOfferResultHandler.handleResult(trade); tradeResultHandler.handleResult(trade);
} }
private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer) { private OfferAvailabilityModel getOfferAvailabilityModel(Offer offer) {
return new OfferAvailabilityModel( return new OfferAvailabilityModel(
offer, offer,
keyRing.getPubKeyRing(), keyRing.getPubKeyRing(),
messageService, p2PService);
addressService);
} }
@ -327,8 +327,8 @@ public class TradeManager {
// Trade // Trade
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void onWithdrawRequest(String toAddress, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) { public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) {
AddressEntry addressEntry = walletService.getAddressEntry(trade.getId()); AddressEntry addressEntry = walletService.getAddressEntryByOfferId(trade.getId());
String fromAddress = addressEntry.getAddressString(); String fromAddress = addressEntry.getAddressString();
FutureCallback<Transaction> callback = new FutureCallback<Transaction>() { FutureCallback<Transaction> callback = new FutureCallback<Transaction>() {
@ -336,15 +336,8 @@ public class TradeManager {
public void onSuccess(@javax.annotation.Nullable Transaction transaction) { public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) { if (transaction != null) {
log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString()); log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
trade.setState(Trade.State.WITHDRAW_COMPLETED);
if (trade instanceof BuyerTrade) addTradeToClosedTrades(trade);
trade.setTradeState(TradeState.BuyerState.WITHDRAW_COMPLETED);
else if (trade instanceof SellerTrade)
trade.setTradeState(TradeState.SellerState.WITHDRAW_COMPLETED);
pendingTrades.remove(trade);
closedTradableManager.add(trade);
resultHandler.handleResult(); resultHandler.handleResult();
} }
} }
@ -357,7 +350,7 @@ public class TradeManager {
} }
}; };
try { try {
walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), callback); walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, callback);
} catch (AddressFormatException | InsufficientMoneyException e) { } catch (AddressFormatException | InsufficientMoneyException e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.getMessage()); log.error(e.getMessage());
@ -365,26 +358,57 @@ public class TradeManager {
} }
} }
// In a fault case we remove it and add it to the closed trades // If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
public void addTradeToClosedTrades(Trade trade) { private void addTradeToClosedTrades(Trade trade) {
pendingTrades.remove(trade); trades.remove(trade);
closedTradableManager.add(trade); closedTradableManager.add(trade);
} }
// If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published)
// we move the trade to failedTradesManager
public void addTradeToFailedTrades(Trade trade) { public void addTradeToFailedTrades(Trade trade) {
pendingTrades.remove(trade); trades.remove(trade);
failedTradesManager.add(trade); failedTradesManager.add(trade);
} }
// If trade is in preparation (if taker role: before taker fee is paid; both roles: before deposit published)
// we just remove the trade from our list. We don't store those trades.
public void removePreparedTrade(Trade trade) {
trades.remove(trade);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dispute
///////////////////////////////////////////////////////////////////////////////////////////
public void closeDisputedTrade(String tradeId) {
Optional<Trade> tradeOptional = getTradeById(tradeId);
if (tradeOptional.isPresent()) {
Trade trade = tradeOptional.get();
trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
addTradeToClosedTrades(trade);
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public ObservableList<Trade> getPendingTrades() { public ObservableList<Trade> getTrades() {
return pendingTrades.getObservableList(); return trades.getObservableList();
}
public BooleanProperty pendingTradesInitializedProperty() {
return pendingTradesInitialized;
} }
public boolean isMyOffer(Offer offer) { public boolean isMyOffer(Offer offer) {
return offer.isMyOffer(keyRing); return offer.isMyOffer(keyRing);
} }
public Optional<Trade> getTradeById(String tradeId) {
return trades.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
}
} }

View File

@ -17,18 +17,15 @@
package io.bitsquare.trade; package io.bitsquare.trade;
import io.bitsquare.BitsquareModule; import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.trade.closed.ClosedTradableManager; import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager; import io.bitsquare.trade.failed.FailedTradesManager;
import com.google.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
public class TradeModule extends BitsquareModule { public class TradeModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(TradeModule.class); private static final Logger log = LoggerFactory.getLogger(TradeModule.class);
public TradeModule(Environment env) { public TradeModule(Environment env) {

View File

@ -1,111 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade;
public interface TradeState {
Phase getPhase();
void setPhase(Phase phase);
enum SellerState implements TradeState {
PREPARATION(Phase.PREPARATION),
DEPOSIT_PUBLISHED_MSG_RECEIVED(Phase.DEPOSIT_PAID),
DEPOSIT_CONFIRMED(Phase.DEPOSIT_PAID),
FIAT_PAYMENT_STARTED_MSG_RECEIVED(Phase.FIAT_SENT),
FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED),
FIAT_PAYMENT_RECEIPT_MSG_SENT(Phase.FIAT_RECEIVED),
PAYOUT_TX_RECEIVED(Phase.PAYOUT_PAID),
PAYOUT_TX_COMMITTED(Phase.PAYOUT_PAID),
PAYOUT_BROAD_CASTED(Phase.PAYOUT_PAID),
WITHDRAW_COMPLETED(Phase.WITHDRAWN),
FAILED();
public Phase getPhase() {
return phase;
}
public void setPhase(Phase phase) {
this.phase = phase;
}
private Phase phase;
SellerState() {
}
SellerState(Phase phase) {
this.phase = phase;
}
}
enum BuyerState implements TradeState {
PREPARATION(Phase.PREPARATION),
DEPOSIT_PUBLISHED(Phase.DEPOSIT_PAID),
DEPOSIT_PUBLISHED_MSG_SENT(Phase.DEPOSIT_PAID),
DEPOSIT_CONFIRMED(Phase.DEPOSIT_PAID),
FIAT_PAYMENT_STARTED(Phase.FIAT_SENT),
FIAT_PAYMENT_STARTED_MSG_SENT(Phase.FIAT_SENT),
FIAT_PAYMENT_RECEIPT_MSG_RECEIVED(Phase.FIAT_RECEIVED),
PAYOUT_TX_COMMITTED(Phase.PAYOUT_PAID),
PAYOUT_TX_SENT(Phase.PAYOUT_PAID),
PAYOUT_BROAD_CASTED(Phase.PAYOUT_PAID),
WITHDRAW_COMPLETED(Phase.WITHDRAWN),
FAILED();
public Phase getPhase() {
return phase;
}
public void setPhase(Phase phase) {
this.phase = phase;
}
private Phase phase;
BuyerState() {
}
BuyerState(Phase phase) {
this.phase = phase;
}
}
enum Phase {
PREPARATION,
TAKER_FEE_PAID,
DEPOSIT_PAID,
FIAT_SENT,
FIAT_RECEIVED,
PAYOUT_PAID,
WITHDRAWN
}
}

Some files were not shown because too many files have changed in this diff Show More