Add default arbitrator

This commit is contained in:
Manfred Karrer 2015-03-25 20:03:19 +01:00
parent 486c83efaa
commit 312807a6ef
25 changed files with 399 additions and 192 deletions

View File

@ -0,0 +1,132 @@
/*
* 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.locale.LanguageUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.util.DSAKeyUtil;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Utils;
import com.google.inject.Inject;
import java.io.Serializable;
import java.security.PublicKey;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ArbitrationRepository implements Serializable {
private static final long serialVersionUID = 1L;
transient private static final Logger log = LoggerFactory.getLogger(ArbitrationRepository.class);
transient private Storage<ArbitrationRepository> storage;
transient private ArbitratorService arbitratorService;
transient private Arbitrator defaultArbitrator;
// Persisted fields
private final Map<String, Arbitrator> arbitratorsMap = new HashMap<>();
transient private final ObservableMap<String, Arbitrator> arbitratorsObservableMap = FXCollections.observableHashMap();
transient private boolean allArbitratorsSynced;
@Inject
public ArbitrationRepository(Storage<ArbitrationRepository> storage,
Storage<Arbitrator> arbitratorStorage,
ArbitratorService arbitratorService) {
this.storage = storage;
this.arbitratorService = arbitratorService;
byte[] walletPubKey = Utils.HEX.decode("03a418bf0cb60a35ce217c7f80a2db08a4f5efbe56a0e7602fbc392dea6b63f840");
PublicKey p2pSigPubKey = DSAKeyUtil.decodePubKeyHex
("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...");
arbitratorsMap.put(defaultArbitrator.getId(), defaultArbitrator);
ArbitrationRepository persisted = storage.initAndGetPersisted(this);
if (persisted != null) {
arbitratorsMap.putAll(persisted.getArbitratorsMap());
}
arbitratorsObservableMap.putAll(arbitratorsMap);
arbitratorsObservableMap.addListener((MapChangeListener<String, Arbitrator>) change -> storage.save());
allArbitratorsSynced = false;
}
/* private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
allArbitratorsObservableList = FXCollections.observableArrayList(allArbitrators);
}
*/
// 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;
},
(errorMessage -> log.error(errorMessage)));
}
public Map<String, Arbitrator> getArbitratorsMap() {
return arbitratorsMap;
}
public ObservableMap<String, Arbitrator> getArbitratorsObservableMap() {
return arbitratorsObservableMap;
}
public boolean areAllArbitratorsSynced() {
return allArbitratorsSynced;
}
public Arbitrator getDefaultArbitrator() {
return defaultArbitrator;
}
}

View File

@ -17,23 +17,16 @@
package io.bitsquare.arbitration;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.User;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import java.io.Serializable;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import javax.inject.Inject;
public class Arbitrator implements Serializable {
private static final long serialVersionUID = 1L;
@ -83,22 +76,41 @@ public class Arbitrator implements Serializable {
// editable
private ID_TYPE idType;
private List<String> languageCodes;
private Coin fee;
private List<METHOD> arbitrationMethods;
private List<ID_VERIFICATION> idVerifications;
private String webUrl;
private String description;
@Inject
public Arbitrator(Storage<Arbitrator> storage, User user) {
public Arbitrator(Storage<Arbitrator> storage,
String id,
byte[] pubKey,
PublicKey p2pSigPubKey,
String name,
Reputation reputation,
ID_TYPE idType,
List<String> languageCodes,
Coin fee,
List<METHOD> arbitrationMethods,
List<ID_VERIFICATION> idVerifications,
String webUrl,
String description) {
this.storage = storage;
this.id = id;
this.pubKey = pubKey;
this.p2pSigPubKey = p2pSigPubKey;
this.name = name;
this.reputation = reputation;
this.idType = idType;
this.languageCodes = languageCodes;
this.fee = fee;
this.arbitrationMethods = arbitrationMethods;
this.idVerifications = idVerifications;
this.webUrl = webUrl;
this.description = description;
Arbitrator persisted = storage.initAndGetPersisted(this);
if (persisted != null) {
//TODO for mock arbitrator
id = persisted.getId();
pubKey = persisted.getPubKey();
p2pSigPubKey = persisted.getP2pSigPubKey();
@ -113,19 +125,8 @@ public class Arbitrator implements Serializable {
description = persisted.getDescription();
}
else {
// Mock
id = UUID.randomUUID().toString();
pubKey = new ECKey().getPubKey();
p2pSigPubKey = user.getP2PSigPubKey();
name = "Mr. Default";
idType = Arbitrator.ID_TYPE.REAL_LIFE_ID;
languageCodes = Arrays.asList(LanguageUtil.getDefaultLanguageLocaleAsCode());
reputation = new Reputation();
fee = Coin.parseCoin("0.1");
arbitrationMethods = Arrays.asList(Arbitrator.METHOD.TLS_NOTARY);
idVerifications = Arrays.asList(ID_VERIFICATION.PASSPORT);
webUrl = "https://bitsquare.io";
description = "Bla bla...";
// TODO mock
save();
}
}

View File

@ -19,6 +19,8 @@ package io.bitsquare.arbitration;
import io.bitsquare.BitsquareModule;
import com.google.inject.Singleton;
import org.springframework.core.env.Environment;
public abstract class ArbitratorModule extends BitsquareModule {
@ -29,6 +31,8 @@ public abstract class ArbitratorModule extends BitsquareModule {
@Override
protected final void configure() {
bind(ArbitrationRepository.class).in(Singleton.class);
doConfigure();
}

View File

@ -18,14 +18,32 @@
package io.bitsquare.arbitration;
import io.bitsquare.arbitration.listeners.ArbitratorListener;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.DHTService;
import java.util.Map;
public interface ArbitratorService extends DHTService {
void addArbitrator(Arbitrator arbitrator);
void addArbitratorListener(ArbitratorListener listener);
void addListener(Listener listener);
void getArbitrators(String defaultLanguageLocaleCode);
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 {
void handleResult(Map<String, Arbitrator> arbitratorsMap);
}
}

View File

@ -1,31 +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.listeners;
import io.bitsquare.arbitration.Arbitrator;
import java.util.List;
// Arbitration is not much developed yet
public interface ArbitratorListener {
void onArbitratorAdded(Arbitrator arbitrator);
void onArbitratorsReceived(List<Arbitrator> arbitrators);
void onArbitratorRemoved(Arbitrator arbitrator);
}

View File

@ -19,15 +19,17 @@ package io.bitsquare.arbitration.tomp2p;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.arbitration.listeners.ArbitratorListener;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.tomp2p.TomP2PDHTService;
import io.bitsquare.p2p.tomp2p.TomP2PNode;
import io.bitsquare.user.User;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
@ -46,53 +48,38 @@ import org.slf4j.LoggerFactory;
public class TomP2PArbitratorService extends TomP2PDHTService implements ArbitratorService {
private static final Logger log = LoggerFactory.getLogger(TomP2PArbitratorService.class);
private static final String ARBITRATORS_ROOT = "ArbitratorsRoot";
private static final Number160 LOCATION_KEY = Number160.createHash("ArbitratorService");
private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
@Inject
public TomP2PArbitratorService(TomP2PNode tomP2PNode, User user) {
super(tomP2PNode, user);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Arbitrators
///////////////////////////////////////////////////////////////////////////////////////////
public void addArbitrator(Arbitrator arbitrator) {
Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
public void addArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
try {
final Data arbitratorData = new Data(arbitrator);
FuturePut addFuture = addProtectedDataToMap(locationKey, arbitratorData);
FuturePut addFuture = addProtectedDataToMap(LOCATION_KEY, arbitratorData);
addFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
executor.execute(() -> arbitratorListeners.stream().forEach(listener ->
{
try {
Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) {
listener.onArbitratorAdded((Arbitrator) arbitratorDataObject);
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
log.error(e.toString());
}
}));
if (future.isSuccess()) {
log.trace("Add arbitrator to DHT was successful. Stored data: [key: " + locationKey + ", " +
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());
}
}
});
@ -102,66 +89,64 @@ public class TomP2PArbitratorService extends TomP2PDHTService implements Arbitra
}
public void removeArbitrator(Arbitrator arbitrator) throws IOException {
Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
final Data arbitratorData = new Data(arbitrator);
FutureRemove removeFuture = removeProtectedDataFromMap(locationKey, arbitratorData);
FutureRemove removeFuture = removeProtectedDataFromMap(LOCATION_KEY, arbitratorData);
removeFuture.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
executor.execute(() -> arbitratorListeners.stream().forEach(listener ->
{
for (Data arbitratorData : removeFuture.dataMap().values()) {
try {
Object arbitratorDataObject = arbitratorData.object();
if (arbitratorDataObject instanceof Arbitrator) {
Arbitrator arbitrator = (Arbitrator) arbitratorDataObject;
listener.onArbitratorRemoved(arbitrator);
}
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
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: " + locationKey + ", " +
log.trace("Remove arbitrator from DHT was successful. Stored data: [key: " + LOCATION_KEY + ", " +
"values: " + arbitratorData + "]");
}
});
}
public void getArbitrators(String languageLocaleCode) {
Number160 locationKey = Number160.createHash(ARBITRATORS_ROOT);
FutureGet futureGet = getMap(locationKey);
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 {
executor.execute(() -> arbitratorListeners.stream().forEach(listener ->
{
List<Arbitrator> arbitrators = new ArrayList<>();
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) {
arbitrators.add((Arbitrator) arbitratorDataObject);
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());
}
}
listener.onArbitratorsReceived(arbitrators);
}));
if (future.isSuccess()) {
log.trace("Get arbitrators from DHT was successful. Stored data: [key: " + locationKey + ", " +
"values: " + futureGet.dataMap() + "]");
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());
}
}
});
@ -172,12 +157,12 @@ public class TomP2PArbitratorService extends TomP2PDHTService implements Arbitra
// Event Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addArbitratorListener(ArbitratorListener listener) {
arbitratorListeners.add(listener);
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeArbitratorListener(ArbitratorListener listener) {
arbitratorListeners.remove(listener);
public void removeListener(Listener listener) {
listeners.remove(listener);
}
}

View File

@ -18,6 +18,7 @@
package io.bitsquare.gui.main;
import io.bitsquare.app.UpdateProcess;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.viewfx.model.ViewModel;
@ -86,17 +87,19 @@ class MainViewModel implements ViewModel {
private final User user;
private final WalletService walletService;
private ArbitrationRepository arbitrationRepository;
private final ClientNode clientNode;
private final TradeManager tradeManager;
private UpdateProcess updateProcess;
private final BSFormatter formatter;
@Inject
public MainViewModel(User user, WalletService walletService, ClientNode clientNode,
public MainViewModel(User user, WalletService walletService, ArbitrationRepository arbitrationRepository, ClientNode clientNode,
TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess,
BSFormatter formatter) {
this.user = user;
this.walletService = walletService;
this.arbitrationRepository = arbitrationRepository;
this.clientNode = clientNode;
this.tradeManager = tradeManager;
this.updateProcess = updateProcess;
@ -201,6 +204,10 @@ class MainViewModel implements ViewModel {
user.setAccountID(walletService.getRegistrationAddressEntry().toString());
}
// Load all arbitrators in background. Any class requiring a loaded list of arbitrators need to register itself as listener to handle the async
// operation.
log.debug("loadAllArbitrators");
arbitrationRepository.loadAllArbitrators();
tradeManager.onAllServicesInitialized();
}

View File

@ -18,7 +18,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.arbitrator.browser.ArbitratorBrowserView"
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.account.arbitrator.browser.BrowserView"
prefHeight="600" prefWidth="800" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="10.0"
AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0"
xmlns:fx="http://javafx.com/fxml">

View File

@ -19,18 +19,17 @@ package io.bitsquare.gui.main.account.arbitrator.browser;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.arbitration.listeners.ArbitratorListener;
import io.bitsquare.common.viewfx.view.ActivatableView;
import io.bitsquare.common.viewfx.view.CachingViewLoader;
import io.bitsquare.common.viewfx.view.FxmlView;
import io.bitsquare.common.viewfx.view.View;
import io.bitsquare.common.viewfx.view.ViewLoader;
import io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.user.AccountSettings;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@ -40,7 +39,7 @@ import javafx.scene.layout.*;
import javafx.stage.Stage;
@FxmlView
public class ArbitratorBrowserView extends ActivatableView<Pane, Void> implements ArbitratorListener {
public class BrowserView extends ActivatableView<Pane, Void> implements ArbitratorService.Listener {
@FXML Button prevButton, nextButton, selectButton, closeButton;
@FXML Pane arbitratorProfile;
@ -53,20 +52,21 @@ public class ArbitratorBrowserView extends ActivatableView<Pane, Void> implement
private final ViewLoader viewLoader;
private final AccountSettings accountSettings;
private final ArbitratorService messageService;
private final ArbitratorService arbitratorService;
@Inject
public ArbitratorBrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings,
ArbitratorService messageService) {
public BrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings,
ArbitratorService arbitratorService) {
this.viewLoader = viewLoader;
this.accountSettings = accountSettings;
this.messageService = messageService;
this.arbitratorService = arbitratorService;
}
@Override
public void initialize() {
messageService.addArbitratorListener(this);
messageService.getArbitrators(LanguageUtil.getDefaultLanguageLocaleAsCode());
arbitratorService.addListener(this);
/* arbitratorService.loadAllArbitrators(() -> log.debug("Arbitrators successful loaded " + arbitratorService.getAllArbitrators().size()),
(errorMessage -> log.error(errorMessage)));*/
View view = viewLoader.load(ArbitratorProfileView.class);
root.getChildren().set(0, view.getRoot());
@ -80,7 +80,12 @@ public class ArbitratorBrowserView extends ActivatableView<Pane, Void> implement
}
@Override
public void onArbitratorsReceived(List<Arbitrator> arbitrators) {
public void onAllArbitratorsLoaded(Map<String, Arbitrator> arbitratorsMap) {
}
/*
@Override
public void onAllArbitratorsLoaded(List<Arbitrator> arbitrators) {
allArbitrators.clear();
allArbitrators.addAll(arbitrators);
@ -90,7 +95,7 @@ public class ArbitratorBrowserView extends ActivatableView<Pane, Void> implement
arbitratorProfileView.applyArbitrator(currentArbitrator);
checkButtonState();
}
}
}*/
@Override
public void onArbitratorRemoved(Arbitrator arbitrator) {

View File

@ -76,18 +76,19 @@ public class ArbitratorRegistrationView extends ActivatableView<AnchorPane, Void
private List<Arbitrator.METHOD> methodList = new ArrayList<>();
private List<Arbitrator.ID_VERIFICATION> idVerificationList = new ArrayList<>();
private final Arbitrator arbitrator;
// TODO not set
private Arbitrator arbitrator;
private final WalletService walletService;
private final ArbitratorService messageService;
private final ArbitratorService arbitratorService;
private final BSFormatter formatter;
@Inject
private ArbitratorRegistrationView(Arbitrator arbitrator, WalletService walletService,
ArbitratorService messageService, BSFormatter formatter) {
this.arbitrator = arbitrator;
private ArbitratorRegistrationView(WalletService walletService,
ArbitratorService arbitratorService, BSFormatter formatter) {
this.walletService = walletService;
this.messageService = messageService;
this.arbitratorService = arbitratorService;
this.formatter = formatter;
}
@ -257,7 +258,11 @@ public class ArbitratorRegistrationView extends ActivatableView<AnchorPane, Void
accordion.setExpandedPane(paySecurityDepositTitledPane);
}
messageService.addArbitrator(arbitrator);
arbitratorService.addArbitrator(arbitrator,
() -> {
// log.debug("arbitrator added successfully " + arbitratorService.getAllArbitrators().size());
},
(errorMessage -> log.error(errorMessage)));
}
@FXML

View File

@ -24,7 +24,7 @@ import io.bitsquare.common.viewfx.view.FxmlView;
import io.bitsquare.common.viewfx.view.View;
import io.bitsquare.common.viewfx.view.ViewLoader;
import io.bitsquare.common.viewfx.view.Wizard;
import io.bitsquare.gui.main.account.arbitrator.browser.ArbitratorBrowserView;
import io.bitsquare.gui.main.account.arbitrator.browser.BrowserView;
import io.bitsquare.gui.main.help.Help;
import io.bitsquare.gui.main.help.HelpId;
import io.bitsquare.gui.util.ImageUtil;
@ -121,7 +121,7 @@ public class RestrictionsView extends ActivatableViewAndModel<GridPane, Restrict
@FXML
private void onOpenArbitratorScreen() {
View view = viewLoader.load(ArbitratorBrowserView.class);
View view = viewLoader.load(BrowserView.class);
showStage(view);
}
@ -161,7 +161,7 @@ public class RestrictionsView extends ActivatableViewAndModel<GridPane, Restrict
Scene scene = new Scene((Parent) view.getRoot(), 800, 600);
stage.setScene(scene);
stage.setOnHidden(windowEvent -> {
if (view instanceof ArbitratorBrowserView)
if (view instanceof BrowserView)
updateArbitratorList();
});
stage.show();

View File

@ -18,6 +18,7 @@
package io.bitsquare.gui.main.trade.createoffer;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
@ -68,6 +69,7 @@ class CreateOfferDataModel implements Activatable, DataModel {
private final TradeManager tradeManager;
private final WalletService walletService;
private ArbitratorService arbitratorService;
private final AccountSettings accountSettings;
private Preferences preferences;
private final BSFormatter formatter;
@ -105,11 +107,11 @@ class CreateOfferDataModel implements Activatable, DataModel {
// non private for testing
@Inject
public CreateOfferDataModel(TradeManager tradeManager, WalletService walletService, AccountSettings accountSettings,
Preferences preferences, User user,
BSFormatter formatter) {
public CreateOfferDataModel(TradeManager tradeManager, WalletService walletService, ArbitratorService arbitratorService,
AccountSettings accountSettings, Preferences preferences, User user, BSFormatter formatter) {
this.tradeManager = tradeManager;
this.walletService = walletService;
this.arbitratorService = arbitratorService;
this.accountSettings = accountSettings;
this.preferences = preferences;
this.formatter = formatter;

View File

@ -314,8 +314,11 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
.countryLocalesToString(dataModel.acceptedCountries)));
dataModel.acceptedLanguageCodes.addListener((Observable o) -> acceptedLanguages.set(formatter
.languageCodesToString(dataModel.acceptedLanguageCodes)));
dataModel.acceptedArbitrators.addListener((Observable o) -> acceptedArbitrators.set(formatter
.arbitratorsToString(dataModel.acceptedArbitrators)));
dataModel.acceptedArbitrators.addListener((Observable o) ->
acceptedArbitrators.set(formatter.arbitratorsToNames(dataModel.acceptedArbitrators)));
}
private void setupBindings() {

View File

@ -142,7 +142,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
bankAccountCountyTextField.setText(model.getBankAccountCounty());
acceptedCountriesTextField.setText(model.getAcceptedCountries());
acceptedLanguagesTextField.setText(model.getAcceptedLanguages());
acceptedArbitratorsTextField.setText(model.getAcceptedArbitrators());
acceptedArbitratorsTextField.setText(model.getAcceptedArbitratorIds());
showCheckAvailabilityScreen();
}

View File

@ -63,7 +63,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
private String bankAccountCounty;
private String acceptedCountries;
private String acceptedLanguages;
private String acceptedArbitrators;
private String acceptedArbitratorIds;
private String addressAsString;
private String paymentLabel;
@ -141,7 +141,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
acceptedCountries = formatter.countryLocalesToString(offer.getAcceptedCountries());
acceptedLanguages = formatter.languageCodesToString(offer.getAcceptedLanguageCodes());
acceptedArbitrators = formatter.arbitratorsToString(offer.getArbitrators());
acceptedArbitratorIds = formatter.arbitratorIdsToNames(offer.getArbitratorIds());
bankAccountType = BSResources.get(offer.getFiatAccountType().toString());
bankAccountCurrency = BSResources.get(CurrencyUtil.getDisplayName(offer.getCurrencyCode()));
bankAccountCounty = BSResources.get(offer.getBankAccountCountry().getName());
@ -347,8 +347,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return acceptedLanguages;
}
String getAcceptedArbitrators() {
return acceptedArbitrators;
String getAcceptedArbitratorIds() {
return acceptedArbitratorIds;
}
String getAddressAsString() {

View File

@ -17,6 +17,7 @@
package io.bitsquare.gui.util;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.Country;
@ -72,10 +73,12 @@ public class BSFormatter {
// format is like: 1,00 never more then 2 decimals
private final MonetaryFormat fiatFormat = MonetaryFormat.FIAT.repeatOptionalDecimals(0, 0).code(0, currencyCode);
private ArbitrationRepository arbitrationRepository;
@Inject
public BSFormatter(User user) {
public BSFormatter(User user, ArbitrationRepository arbitrationRepository) {
this.arbitrationRepository = arbitrationRepository;
if (user.currentFiatAccountProperty().get() == null)
setFiatCurrencyCode(CurrencyUtil.getDefaultCurrencyAsCode());
else if (user.currentFiatAccountProperty().get() != null)
@ -313,8 +316,12 @@ public class BSFormatter {
return countries.stream().map(Country::getName).collect(Collectors.joining(", "));
}
public String arbitratorsToString(List<Arbitrator> arbitrators) {
return arbitrators.stream().map(Arbitrator::getName).collect(Collectors.joining(", "));
public String arbitratorsToNames(List<Arbitrator> arbitrators) {
return arbitrators.stream().map(e -> e.getName()).collect(Collectors.joining(", "));
}
public String arbitratorIdsToNames(List<String> ids) {
return ids.stream().map(e -> arbitrationRepository.getArbitratorsMap().get(e).getName()).collect(Collectors.joining(", "));
}
public String languageCodesToString(List<String> languageLocales) {

View File

@ -17,7 +17,6 @@
package io.bitsquare.offer;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.fiat.FiatAccountType;
import io.bitsquare.locale.Country;
@ -76,7 +75,7 @@ public class Offer implements Serializable {
private final List<Country> acceptedCountries;
private final List<String> acceptedLanguageCodes;
private final String bankAccountUID;
private final List<Arbitrator> arbitrators;
private final List<String> arbitratorIds;
// Mutable property. Has to be set before offer is save in DHT as it changes the objects hash!
private String offerFeePaymentTxID;
@ -101,7 +100,7 @@ public class Offer implements Serializable {
String currencyCode,
Country bankAccountCountry,
String bankAccountUID,
List<Arbitrator> arbitrators,
List<String> arbitratorIds,
Coin securityDeposit,
List<Country> acceptedCountries,
List<String> acceptedLanguageCodes) {
@ -115,7 +114,7 @@ public class Offer implements Serializable {
this.currencyCode = currencyCode;
this.bankAccountCountry = bankAccountCountry;
this.bankAccountUID = bankAccountUID;
this.arbitrators = arbitrators;
this.arbitratorIds = arbitratorIds;
this.securityDeposit = securityDeposit;
this.acceptedCountries = acceptedCountries;
@ -206,8 +205,8 @@ public class Offer implements Serializable {
return offerFeePaymentTxID;
}
public List<Arbitrator> getArbitrators() {
return arbitrators;
public List<String> getArbitratorIds() {
return arbitratorIds;
}
public Coin getSecurityDeposit() {
@ -240,7 +239,7 @@ public class Offer implements Serializable {
checkNotNull(getAcceptedCountries(), "AcceptedCountries is null");
checkNotNull(getAcceptedLanguageCodes(), "AcceptedLanguageLocales is null");
checkNotNull(getAmount(), "Amount is null");
checkNotNull(getArbitrators(), "Arbitrator is null");
checkNotNull(getArbitratorIds(), "Arbitrator is null");
checkNotNull(getBankAccountId(), "BankAccountId is null");
checkNotNull(getSecurityDeposit(), "SecurityDeposit is null");
checkNotNull(getCreationDate(), "CreationDate is null");
@ -280,7 +279,7 @@ public class Offer implements Serializable {
", acceptedCountries=" + acceptedCountries +
", acceptedLanguageLocales=" + acceptedLanguageCodes +
", bankAccountUID='" + bankAccountUID + '\'' +
", arbitrators=" + arbitrators +
", arbitrators=" + arbitratorIds +
", offerFeePaymentTxID='" + offerFeePaymentTxID + '\'' +
'}';
}

View File

@ -17,6 +17,7 @@
package io.bitsquare.trade;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.handlers.ErrorMessageHandler;
@ -80,6 +81,7 @@ public class TradeManager {
private final SignatureService signatureService;
private EncryptionService<MailboxMessage> encryptionService;
private final OfferBookService offerBookService;
private ArbitrationRepository arbitrationRepository;
private File storageDir;
private final Map<String, CheckOfferAvailabilityProtocol> checkOfferAvailabilityProtocolMap = new HashMap<>();
@ -97,7 +99,7 @@ public class TradeManager {
public TradeManager(User user, AccountSettings accountSettings,
MessageService messageService, MailboxService mailboxService, AddressService addressService, BlockChainService blockChainService,
WalletService walletService, SignatureService signatureService, EncryptionService<MailboxMessage> encryptionService,
OfferBookService offerBookService, @Named("storage.dir") File storageDir) {
OfferBookService offerBookService, ArbitrationRepository arbitrationRepository, @Named("storage.dir") File storageDir) {
this.user = user;
this.accountSettings = accountSettings;
this.messageService = messageService;
@ -108,6 +110,7 @@ public class TradeManager {
this.signatureService = signatureService;
this.encryptionService = encryptionService;
this.offerBookService = offerBookService;
this.arbitrationRepository = arbitrationRepository;
this.storageDir = storageDir;
this.openOfferTrades = new TradeList<>(storageDir, "OpenOfferTrades");
@ -173,7 +176,7 @@ public class TradeManager {
currentFiatAccount.getCurrencyCode(),
currentFiatAccount.getCountry(),
currentFiatAccount.getId(),
accountSettings.getAcceptedArbitrators(),
accountSettings.getAcceptedArbitratorIds(),
accountSettings.getSecurityDeposit(),
accountSettings.getAcceptedCountries(),
accountSettings.getAcceptedLanguageLocaleCodes());
@ -184,7 +187,7 @@ public class TradeManager {
} catch (IOException e) {
e.printStackTrace();
}
PlaceOfferModel model = new PlaceOfferModel(offer, walletService, offerBookService);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
@ -398,6 +401,7 @@ public class TradeManager {
walletService,
blockChainService,
signatureService,
arbitrationRepository,
user,
storageDir);
@ -413,6 +417,7 @@ public class TradeManager {
walletService,
blockChainService,
signatureService,
arbitrationRepository,
user,
storageDir);

View File

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
@ -37,6 +38,7 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
private static final long serialVersionUID = -2523252022571497157L;
protected static final Logger log = LoggerFactory.getLogger(SharedTradeModel.class);
transient public MailboxMessage mailboxMessage;
// provided
transient public final Offer offer;
transient public final MessageService messageService;
@ -45,12 +47,12 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
transient public final BlockChainService blockChainService;
transient public final SignatureService signatureService;
transient public MailboxMessage mailboxMessage;
// derived
transient public final String id;
transient public final TradeWalletService tradeWalletService;
transient public final byte[] arbitratorPubKey;
// get set async when arbitrators are loaded from arbitratorService
transient public byte[] arbitratorPubKey;
// data written/read by tasks
transient private TradeMessage tradeMessage;
@ -60,7 +62,8 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
MailboxService mailboxService,
WalletService walletService,
BlockChainService blockChainService,
SignatureService signatureService) {
SignatureService signatureService,
ArbitrationRepository arbitrationRepository) {
this.offer = offer;
this.messageService = messageService;
this.mailboxService = mailboxService;
@ -70,9 +73,7 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
id = offer.getId();
tradeWalletService = walletService.getTradeWalletService();
//TODO use default arbitrator for now
arbitratorPubKey = offer.getArbitrators().get(0).getPubKey();
arbitratorPubKey = arbitrationRepository.getDefaultArbitrator().getPubKey();
}
public void setTradeMessage(TradeMessage tradeMessage) {

View File

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.offerer.models;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService;
@ -52,6 +53,7 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl
WalletService walletService,
BlockChainService blockChainService,
SignatureService signatureService,
ArbitrationRepository arbitrationRepository,
User user,
File storageDir) {
super(trade.getOffer(),
@ -59,7 +61,8 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl
mailboxService,
walletService,
blockChainService,
signatureService);
signatureService,
arbitrationRepository);
this.trade = trade;
this.storage = new Storage<>(storageDir);

View File

@ -17,6 +17,7 @@
package io.bitsquare.trade.protocol.trade.taker.models;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService;
@ -55,6 +56,7 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
WalletService walletService,
BlockChainService blockChainService,
SignatureService signatureService,
ArbitrationRepository arbitrationRepository,
User user,
File storageDir) {
super(trade.getOffer(),
@ -62,7 +64,8 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
mailboxService,
walletService,
blockChainService,
signatureService);
signatureService,
arbitrationRepository);
this.trade = trade;
this.storage = new Storage<>(storageDir);

View File

@ -17,6 +17,7 @@
package io.bitsquare.user;
import io.bitsquare.arbitration.ArbitrationRepository;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil;
@ -31,6 +32,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalLong;
import java.util.stream.Collectors;
import javax.inject.Inject;
@ -50,7 +52,7 @@ public class AccountSettings implements Serializable {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public AccountSettings(Storage<AccountSettings> storage, Arbitrator defaultArbitrator) {
public AccountSettings(Storage<AccountSettings> storage, ArbitrationRepository arbitrationRepository) {
this.storage = storage;
AccountSettings persisted = storage.initAndGetPersisted(this);
@ -62,7 +64,7 @@ public class AccountSettings implements Serializable {
else {
acceptedLanguageLocaleCodes = Arrays.asList(LanguageUtil.getDefaultLanguageLocaleAsCode(), LanguageUtil.getEnglishLanguageLocaleCode());
acceptedCountryLocales = Arrays.asList(CountryUtil.getDefaultCountry());
acceptedArbitrators = Arrays.asList(defaultArbitrator);
acceptedArbitrators = Arrays.asList(arbitrationRepository.getDefaultArbitrator());
}
}
@ -115,6 +117,10 @@ public class AccountSettings implements Serializable {
return acceptedArbitrators;
}
public List<String> getAcceptedArbitratorIds() {
return acceptedArbitrators.stream().map(e -> e.getId()).collect(Collectors.toList());
}
public List<String> getAcceptedLanguageLocaleCodes() {
return acceptedLanguageLocaleCodes;
}

View File

@ -0,0 +1,52 @@
/*
* 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.util;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DSAKeyUtil {
private static final Logger log = LoggerFactory.getLogger(DSAKeyUtil.class);
public static PublicKey decodePubKeyHex(String pubKeyHex) {
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Utils.HEX.decode(pubKeyHex));
try {
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
return keyFactory.generatePublic(pubKeySpec);
} catch (NoSuchAlgorithmException e) {
log.error("could not find algorithm", e);
return null;
} catch (InvalidKeySpecException e) {
log.error("wrong keyspec", e);
return null;
}
}
public static String encodePubKeyToHex(PublicKey pubKey) {
return Utils.HEX.encode(pubKey.getEncoded());
}
}

View File

@ -41,10 +41,10 @@ public class CreateOfferViewModelTest {
@Before
public void setup() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.setLocale(Locale.US);
formatter.setFiatCurrencyCode("USD");
model = new CreateOfferDataModel(null, null, null, null, null, formatter);
model = new CreateOfferDataModel(null, null, null, null, null, null, formatter);
presenter = new CreateOfferViewModel(model, new FiatValidator(null), new BtcValidator(), formatter);
}

View File

@ -31,7 +31,7 @@ public class BSFormatterTest {
@Test
public void testParseToBtc() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
assertEquals(Coin.ZERO, formatter.parseToCoin("0"));
assertEquals(Coin.COIN, formatter.parseToCoin("1"));
@ -64,7 +64,7 @@ public class BSFormatterTest {
@Test
public void testFormatCoin() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
assertEquals("1.00", formatter.formatCoin(Coin.COIN));
assertEquals("1.0120", formatter.formatCoin(Coin.parseCoin("1.012")));
@ -97,7 +97,7 @@ public class BSFormatterTest {
@Test
public void testFormatCoinWithCode() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
assertEquals("1.00 BTC", formatter.formatCoinWithCode(Coin.COIN));
assertEquals("1.01 BTC", formatter.formatCoinWithCode(Coin.parseCoin("1.01")));
@ -132,7 +132,7 @@ public class BSFormatterTest {
@Test
public void testParseToBtcWith4Decimals() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
assertEquals(Coin.parseCoin("0"), formatter.parseToCoinWith4Decimals("0"));
assertEquals(Coin.parseCoin("0"), formatter.parseToCoinWith4Decimals(null));
@ -143,7 +143,7 @@ public class BSFormatterTest {
@Test
public void testHasBtcValidDecimals() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
formatter.setLocale(Locale.GERMAN);
assertTrue(formatter.hasBtcValidDecimals(null));
@ -159,7 +159,7 @@ public class BSFormatterTest {
@Test
public void testParseToFiatWith2Decimals() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
formatter.setLocale(Locale.GERMAN);
assertEquals("0", formatter.parseToFiatWith2Decimals("0").toPlainString());
@ -172,7 +172,7 @@ public class BSFormatterTest {
@Test
public void testHasFiatValidDecimals() {
BSFormatter formatter = new BSFormatter(new User());
BSFormatter formatter = new BSFormatter(new User(), null);
formatter.useMilliBitFormat(false);
formatter.setLocale(Locale.GERMAN);
assertTrue(formatter.hasFiatValidDecimals(null));