mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 09:52:23 +01:00
Merge branch 'master' into 09-refactor-grpc-error-handling
This commit is contained in:
commit
1cd47fd0aa
@ -375,7 +375,10 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||
// reference to the persistable object.
|
||||
getWriteToDiskExecutor().execute(() -> writeToDisk(serialized, completeHandler));
|
||||
|
||||
log.info("Serializing {} took {} msec", fileName, System.currentTimeMillis() - ts);
|
||||
long duration = System.currentTimeMillis() - ts;
|
||||
if (duration > 100) {
|
||||
log.info("Serializing {} took {} msec", fileName, duration);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error("Error in saveToFile toProtoMessage: {}, {}", persistable.getClass().getSimpleName(), fileName);
|
||||
e.printStackTrace();
|
||||
@ -437,7 +440,10 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||
e.printStackTrace();
|
||||
log.error("Cannot close resources." + e.getMessage());
|
||||
}
|
||||
log.info("Writing the serialized {} completed in {} msec", fileName, System.currentTimeMillis() - ts);
|
||||
long duration = System.currentTimeMillis() - ts;
|
||||
if (duration > 100) {
|
||||
log.info("Writing the serialized {} completed in {} msec", fileName, duration);
|
||||
}
|
||||
persistenceRequested = false;
|
||||
if (completeHandler != null) {
|
||||
UserThread.execute(completeHandler);
|
||||
|
@ -51,6 +51,7 @@ import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.crypto.Sig;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.util.MathUtils;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
@ -880,4 +881,29 @@ public class AccountAgeWitnessService {
|
||||
!peerHasSignedWitness(trade) &&
|
||||
tradeAmountIsSufficient(trade.getTradeAmount());
|
||||
}
|
||||
|
||||
public String getSignInfoFromAccount(PaymentAccount paymentAccount) {
|
||||
var pubKey = keyRing.getSignatureKeyPair().getPublic();
|
||||
var witness = getMyWitness(paymentAccount.getPaymentAccountPayload());
|
||||
return Utilities.bytesAsHexString(witness.getHash()) + "," + Utilities.bytesAsHexString(pubKey.getEncoded());
|
||||
}
|
||||
|
||||
public Tuple2<AccountAgeWitness, byte[]> getSignInfoFromString(String signInfo) {
|
||||
var parts = signInfo.split(",");
|
||||
if (parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
byte[] pubKeyHash;
|
||||
Optional<AccountAgeWitness> accountAgeWitness;
|
||||
try {
|
||||
var accountAgeWitnessHash = Utilities.decodeFromHex(parts[0]);
|
||||
pubKeyHash = Utilities.decodeFromHex(parts[1]);
|
||||
accountAgeWitness = getWitnessByHash(accountAgeWitnessHash);
|
||||
return accountAgeWitness
|
||||
.map(ageWitness -> new Tuple2<>(ageWitness, pubKeyHash))
|
||||
.orElse(null);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.btc.wallet.WalletsManager;
|
||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||
import bisq.core.dao.governance.voteresult.VoteResultException;
|
||||
import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService;
|
||||
import bisq.core.locale.Res;
|
||||
@ -41,6 +42,7 @@ import bisq.core.user.User;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
|
||||
@ -210,7 +212,8 @@ public class BisqSetup {
|
||||
TorSetup torSetup,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
AppStartupState appStartupState) {
|
||||
AppStartupState appStartupState,
|
||||
Socks5ProxyProvider socks5ProxyProvider) {
|
||||
this.domainInitialisation = domainInitialisation;
|
||||
this.p2PNetworkSetup = p2PNetworkSetup;
|
||||
this.walletAppSetup = walletAppSetup;
|
||||
@ -230,6 +233,8 @@ public class BisqSetup {
|
||||
this.formatter = formatter;
|
||||
this.localBitcoinNode = localBitcoinNode;
|
||||
this.appStartupState = appStartupState;
|
||||
|
||||
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -24,6 +24,7 @@ import bisq.core.btc.exceptions.WalletException;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.model.AddressEntryList;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
@ -967,6 +968,10 @@ public class BtcWalletService extends WalletService {
|
||||
try {
|
||||
sendResult = wallet.sendCoins(sendRequest);
|
||||
printTx("FeeEstimationTransaction", newTransaction);
|
||||
|
||||
// For better redundancy in case the broadcast via BitcoinJ fails we also
|
||||
// publish the tx via mempool nodes.
|
||||
MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx);
|
||||
} catch (InsufficientMoneyException e2) {
|
||||
errorMessageHandler.handleErrorMessage("We did not get the correct fee calculated. " + (e2.missing != null ? e2.missing.toFriendlyString() : ""));
|
||||
}
|
||||
@ -1160,7 +1165,11 @@ public class BtcWalletService extends WalletService {
|
||||
if (memo != null) {
|
||||
sendResult.tx.setMemo(memo);
|
||||
}
|
||||
printTx("sendFunds", sendResult.tx);
|
||||
|
||||
// For better redundancy in case the broadcast via BitcoinJ fails we also
|
||||
// publish the tx via mempool nodes.
|
||||
MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx);
|
||||
|
||||
return sendResult.tx.getTxId().toString();
|
||||
}
|
||||
|
||||
@ -1181,6 +1190,11 @@ public class BtcWalletService extends WalletService {
|
||||
sendResult.tx.setMemo(memo);
|
||||
}
|
||||
printTx("sendFunds", sendResult.tx);
|
||||
|
||||
// For better redundancy in case the broadcast via BitcoinJ fails we also
|
||||
// publish the tx via mempool nodes.
|
||||
MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx);
|
||||
|
||||
return sendResult.tx;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ package bisq.core.btc.wallet;
|
||||
|
||||
import bisq.core.btc.exceptions.TxBroadcastException;
|
||||
import bisq.core.btc.exceptions.TxBroadcastTimeoutException;
|
||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
@ -135,6 +136,10 @@ public class TxBroadcaster {
|
||||
"the peerGroup.broadcastTransaction callback.", throwable)));
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
|
||||
// For better redundancy in case the broadcast via BitcoinJ fails we also
|
||||
// publish the tx via mempool nodes.
|
||||
MemPoolSpaceTxBroadcaster.broadcastTx(tx);
|
||||
}
|
||||
|
||||
private static void stopAndRemoveTimer(String txId) {
|
||||
|
@ -23,6 +23,7 @@ import bisq.core.btc.listeners.AddressConfidenceListener;
|
||||
import bisq.core.btc.listeners.BalanceListener;
|
||||
import bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import bisq.core.btc.setup.WalletsSetup;
|
||||
import bisq.core.btc.wallet.http.MemPoolSpaceTxBroadcaster;
|
||||
import bisq.core.provider.fee.FeeService;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
@ -535,7 +536,12 @@ public abstract class WalletService {
|
||||
sendRequest.aesKey = aesKey;
|
||||
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
|
||||
printTx("empty btc wallet", sendResult.tx);
|
||||
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
|
||||
|
||||
// For better redundancy in case the broadcast via BitcoinJ fails we also
|
||||
// publish the tx via mempool nodes.
|
||||
MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx);
|
||||
|
||||
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction result) {
|
||||
log.info("emptyBtcWallet onSuccess Transaction=" + result);
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq 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.
|
||||
*
|
||||
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.btc.wallet.http;
|
||||
|
||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.http.HttpException;
|
||||
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class MemPoolSpaceTxBroadcaster {
|
||||
private static Socks5ProxyProvider socks5ProxyProvider;
|
||||
private static Preferences preferences;
|
||||
private static LocalBitcoinNode localBitcoinNode;
|
||||
private static final ListeningExecutorService executorService = Utilities.getListeningExecutorService(
|
||||
"MemPoolSpaceTxBroadcaster", 3, 5, 10 * 60);
|
||||
|
||||
public static void init(Socks5ProxyProvider socks5ProxyProvider,
|
||||
Preferences preferences,
|
||||
LocalBitcoinNode localBitcoinNode) {
|
||||
MemPoolSpaceTxBroadcaster.socks5ProxyProvider = socks5ProxyProvider;
|
||||
MemPoolSpaceTxBroadcaster.preferences = preferences;
|
||||
MemPoolSpaceTxBroadcaster.localBitcoinNode = localBitcoinNode;
|
||||
}
|
||||
|
||||
public static void broadcastTx(Transaction tx) {
|
||||
if (!Config.baseCurrencyNetwork().isMainnet()) {
|
||||
log.info("MemPoolSpaceTxBroadcaster only supports mainnet");
|
||||
return;
|
||||
}
|
||||
|
||||
if (localBitcoinNode.shouldBeUsed()) {
|
||||
log.info("A localBitcoinNode is detected and used. For privacy reasons we do not use the tx " +
|
||||
"broadcast to mempool nodes in that case.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (socks5ProxyProvider == null) {
|
||||
log.warn("We got broadcastTx called before init was called.");
|
||||
return;
|
||||
}
|
||||
|
||||
String txIdToSend = tx.getTxId().toString();
|
||||
String rawTx = Utils.HEX.encode(tx.bitcoinSerialize(true));
|
||||
|
||||
List<String> txBroadcastServices = new ArrayList<>(preferences.getDefaultTxBroadcastServices());
|
||||
// Broadcast to first service
|
||||
String serviceAddress = broadcastTx(txIdToSend, rawTx, txBroadcastServices);
|
||||
if (serviceAddress != null) {
|
||||
// Broadcast to second service
|
||||
txBroadcastServices.remove(serviceAddress);
|
||||
broadcastTx(txIdToSend, rawTx, txBroadcastServices);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String broadcastTx(String txIdToSend, String rawTx, List<String> txBroadcastServices) {
|
||||
String serviceAddress = getRandomServiceAddress(txBroadcastServices);
|
||||
if (serviceAddress == null) {
|
||||
log.warn("We don't have a serviceAddress available. txBroadcastServices={}", txBroadcastServices);
|
||||
return null;
|
||||
}
|
||||
broadcastTx(serviceAddress, txIdToSend, rawTx);
|
||||
return serviceAddress;
|
||||
}
|
||||
|
||||
private static void broadcastTx(String serviceAddress, String txIdToSend, String rawTx) {
|
||||
TxBroadcastHttpClient httpClient = new TxBroadcastHttpClient(socks5ProxyProvider);
|
||||
httpClient.setBaseUrl(serviceAddress);
|
||||
httpClient.setIgnoreSocks5Proxy(false);
|
||||
|
||||
log.info("We broadcast rawTx {} to {}", rawTx, serviceAddress);
|
||||
ListenableFuture<String> future = executorService.submit(() -> {
|
||||
Thread.currentThread().setName("MemPoolSpaceTxBroadcaster @ " + serviceAddress);
|
||||
return httpClient.post(rawTx, "User-Agent", "bisq/" + Version.VERSION);
|
||||
});
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
public void onSuccess(String txId) {
|
||||
if (txId.equals(txIdToSend)) {
|
||||
log.info("Broadcast of raw tx with txId {} to {} was successful. rawTx={}",
|
||||
txId, serviceAddress, rawTx);
|
||||
} else {
|
||||
log.error("The txId we got returned from the service does not match " +
|
||||
"out tx of the sending tx. txId={}; txIdToSend={}",
|
||||
txId, txIdToSend);
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(@NotNull Throwable throwable) {
|
||||
Throwable cause = throwable.getCause();
|
||||
if (cause instanceof HttpException) {
|
||||
int responseCode = ((HttpException) cause).getResponseCode();
|
||||
String message = cause.getMessage();
|
||||
// See all error codes at: https://github.com/bitcoin/bitcoin/blob/master/src/rpc/protocol.h
|
||||
if (responseCode == 400 && message.contains("code\":-27")) {
|
||||
log.info("Broadcast of raw tx to {} failed as transaction {} is already confirmed",
|
||||
serviceAddress, txIdToSend);
|
||||
} else {
|
||||
log.info("Broadcast of raw tx to {} failed for transaction {}. responseCode={}, error={}",
|
||||
serviceAddress, txIdToSend, responseCode, message);
|
||||
}
|
||||
} else {
|
||||
log.warn("Broadcast of raw tx with txId {} to {} failed. Error={}",
|
||||
txIdToSend, serviceAddress, throwable.toString());
|
||||
}
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getRandomServiceAddress(List<String> txBroadcastServices) {
|
||||
List<String> list = checkNotNull(txBroadcastServices);
|
||||
return !list.isEmpty() ? list.get(new Random().nextInt(list.size())) : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq 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.
|
||||
*
|
||||
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.btc.wallet.http;
|
||||
|
||||
import bisq.core.trade.txproof.AssetTxProofHttpClient;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.http.HttpClientImpl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
class TxBroadcastHttpClient extends HttpClientImpl implements AssetTxProofHttpClient {
|
||||
TxBroadcastHttpClient(Socks5ProxyProvider socks5ProxyProvider) {
|
||||
super(socks5ProxyProvider);
|
||||
}
|
||||
}
|
@ -46,6 +46,9 @@ public final class Role implements PersistablePayload, NetworkPayload, BondedAss
|
||||
private final String link;
|
||||
private final BondedRoleType bondedRoleType;
|
||||
|
||||
// Only used as cache
|
||||
transient private final byte[] hash;
|
||||
|
||||
/**
|
||||
* @param name Full name or nickname
|
||||
* @param link GitHub account or forum account of user
|
||||
@ -74,6 +77,8 @@ public final class Role implements PersistablePayload, NetworkPayload, BondedAss
|
||||
this.name = name;
|
||||
this.link = link;
|
||||
this.bondedRoleType = bondedRoleType;
|
||||
|
||||
hash = Hash.getSha256Ripemd160hash(toProtoMessage().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,8 +105,7 @@ public final class Role implements PersistablePayload, NetworkPayload, BondedAss
|
||||
|
||||
@Override
|
||||
public byte[] getHash() {
|
||||
byte[] bytes = toProtoMessage().toByteArray();
|
||||
return Hash.getSha256Ripemd160hash(bytes);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -42,6 +42,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@ -53,7 +54,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@Slf4j
|
||||
public class CurrencyUtil {
|
||||
|
||||
public static void setup() {
|
||||
setBaseCurrencyCode(Config.baseCurrencyNetwork().getCurrencyCode());
|
||||
}
|
||||
@ -62,6 +62,14 @@ public class CurrencyUtil {
|
||||
|
||||
private static String baseCurrencyCode = "BTC";
|
||||
|
||||
// Calls to isFiatCurrency and isCryptoCurrency are very frequent so we use a cache of the results.
|
||||
// The main improvement was already achieved with using memoize for the source maps, but
|
||||
// the caching still reduces performance costs by about 20% for isCryptoCurrency (1752 ms vs 2121 ms) and about 50%
|
||||
// for isFiatCurrency calls (1777 ms vs 3467 ms).
|
||||
// See: https://github.com/bisq-network/bisq/pull/4955#issuecomment-745302802
|
||||
private static final Map<String, Boolean> isFiatCurrencyMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, Boolean> isCryptoCurrencyMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static Supplier<Map<String, FiatCurrency>> fiatCurrencyMapSupplier = Suppliers.memoize(
|
||||
CurrencyUtil::createFiatCurrencyMap)::get;
|
||||
private static Supplier<Map<String, CryptoCurrency>> cryptoCurrencyMapSupplier = Suppliers.memoize(
|
||||
@ -391,12 +399,25 @@ public class CurrencyUtil {
|
||||
}
|
||||
|
||||
public static boolean isFiatCurrency(String currencyCode) {
|
||||
if (currencyCode != null && isFiatCurrencyMap.containsKey(currencyCode)) {
|
||||
return isFiatCurrencyMap.get(currencyCode);
|
||||
}
|
||||
|
||||
try {
|
||||
return currencyCode != null
|
||||
boolean isFiatCurrency = currencyCode != null
|
||||
&& !currencyCode.isEmpty()
|
||||
&& !isCryptoCurrency(currencyCode)
|
||||
&& Currency.getInstance(currencyCode) != null;
|
||||
|
||||
if (currencyCode != null) {
|
||||
isFiatCurrencyMap.put(currencyCode, isFiatCurrency);
|
||||
}
|
||||
|
||||
return isFiatCurrency;
|
||||
} catch (Throwable t) {
|
||||
if (currencyCode != null) {
|
||||
isFiatCurrencyMap.put(currencyCode, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -417,28 +438,37 @@ public class CurrencyUtil {
|
||||
* contains 3 entries (CryptoCurrency, Fiat, Undefined).
|
||||
*/
|
||||
public static boolean isCryptoCurrency(String currencyCode) {
|
||||
// Some tests call that method with null values. Should be fixed in the tests but to not break them return false.
|
||||
if (currencyCode == null)
|
||||
return false;
|
||||
if (currencyCode != null && isCryptoCurrencyMap.containsKey(currencyCode)) {
|
||||
return isCryptoCurrencyMap.get(currencyCode);
|
||||
}
|
||||
|
||||
// BTC is not part of our assetRegistry so treat it extra here. Other old base currencies (LTC, DOGE, DASH)
|
||||
// are not supported anymore so we can ignore that case.
|
||||
if (currencyCode.equals("BTC"))
|
||||
return true;
|
||||
boolean isCryptoCurrency;
|
||||
if (currencyCode == null) {
|
||||
// Some tests call that method with null values. Should be fixed in the tests but to not break them return false.
|
||||
isCryptoCurrency = false;
|
||||
} else if (currencyCode.equals("BTC")) {
|
||||
// BTC is not part of our assetRegistry so treat it extra here. Other old base currencies (LTC, DOGE, DASH)
|
||||
// are not supported anymore so we can ignore that case.
|
||||
isCryptoCurrency = true;
|
||||
} else if (getCryptoCurrency(currencyCode).isPresent()) {
|
||||
// If we find the code in our assetRegistry we return true.
|
||||
// It might be that an asset was removed from the assetsRegistry, we deal with such cases below by checking if
|
||||
// it is a fiat currency
|
||||
isCryptoCurrency = true;
|
||||
} else if (!getFiatCurrency(currencyCode).isPresent()) {
|
||||
// In case the code is from a removed asset we cross check if there exist a fiat currency with that code,
|
||||
// if we don't find a fiat currency we treat it as a crypto currency.
|
||||
isCryptoCurrency = true;
|
||||
} else {
|
||||
// If we would have found a fiat currency we return false
|
||||
isCryptoCurrency = false;
|
||||
}
|
||||
|
||||
// If we find the code in our assetRegistry we return true.
|
||||
// It might be that an asset was removed from the assetsRegistry, we deal with such cases below by checking if
|
||||
// it is a fiat currency
|
||||
if (getCryptoCurrency(currencyCode).isPresent())
|
||||
return true;
|
||||
if (currencyCode != null) {
|
||||
isCryptoCurrencyMap.put(currencyCode, isCryptoCurrency);
|
||||
}
|
||||
|
||||
// In case the code is from a removed asset we cross check if there exist a fiat currency with that code,
|
||||
// if we don't find a fiat currency we treat it as a crypto currency.
|
||||
if (!getFiatCurrency(currencyCode).isPresent())
|
||||
return true;
|
||||
|
||||
// If we would have found a fiat currency we return false
|
||||
return false;
|
||||
return isCryptoCurrency;
|
||||
}
|
||||
|
||||
public static Optional<CryptoCurrency> getCryptoCurrency(String currencyCode) {
|
||||
@ -527,7 +557,9 @@ public class CurrencyUtil {
|
||||
return new CryptoCurrency(asset.getTickerSymbol(), asset.getName(), asset instanceof Token);
|
||||
}
|
||||
|
||||
private static boolean isNotBsqOrBsqTradingActivated(Asset asset, BaseCurrencyNetwork baseCurrencyNetwork, boolean daoTradingActivated) {
|
||||
private static boolean isNotBsqOrBsqTradingActivated(Asset asset,
|
||||
BaseCurrencyNetwork baseCurrencyNetwork,
|
||||
boolean daoTradingActivated) {
|
||||
return !(asset instanceof BSQ) ||
|
||||
daoTradingActivated && assetMatchesNetwork(asset, baseCurrencyNetwork);
|
||||
}
|
||||
@ -582,7 +614,8 @@ public class CurrencyUtil {
|
||||
}
|
||||
|
||||
// Excludes all assets which got removed by DAO voting
|
||||
public static List<CryptoCurrency> getActiveSortedCryptoCurrencies(AssetService assetService, FilterManager filterManager) {
|
||||
public static List<CryptoCurrency> getActiveSortedCryptoCurrencies(AssetService assetService,
|
||||
FilterManager filterManager) {
|
||||
return getAllSortedCryptoCurrencies().stream()
|
||||
.filter(e -> e.getCode().equals("BSQ") || assetService.isActive(e.getCode()))
|
||||
.filter(e -> !filterManager.isCurrencyBanned(e.getCode()))
|
||||
|
@ -109,6 +109,11 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||
@Setter
|
||||
transient private PriceFeedService priceFeedService;
|
||||
|
||||
// Used only as cache
|
||||
@Nullable
|
||||
@JsonExclude
|
||||
transient private String currencyCode;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -346,8 +351,9 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||
|
||||
|
||||
public Optional<String> getAccountAgeWitnessHashAsHex() {
|
||||
if (getExtraDataMap() != null && getExtraDataMap().containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
|
||||
return Optional.of(getExtraDataMap().get(OfferPayload.ACCOUNT_AGE_WITNESS_HASH));
|
||||
Map<String, String> extraDataMap = getExtraDataMap();
|
||||
if (extraDataMap != null && extraDataMap.containsKey(OfferPayload.ACCOUNT_AGE_WITNESS_HASH))
|
||||
return Optional.of(extraDataMap.get(OfferPayload.ACCOUNT_AGE_WITNESS_HASH));
|
||||
else
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -421,9 +427,14 @@ public class Offer implements NetworkPayload, PersistablePayload {
|
||||
}
|
||||
|
||||
public String getCurrencyCode() {
|
||||
return offerPayload.getBaseCurrencyCode().equals("BTC") ?
|
||||
if (currencyCode != null) {
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
currencyCode = offerPayload.getBaseCurrencyCode().equals("BTC") ?
|
||||
offerPayload.getCounterCurrencyCode() :
|
||||
offerPayload.getBaseCurrencyCode();
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public long getProtocolVersion() {
|
||||
|
@ -332,7 +332,7 @@ public final class PaymentMethod implements PersistablePayload, Comparable<Payme
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull PaymentMethod other) {
|
||||
return id.compareTo(other.id);
|
||||
return Res.get(id).compareTo(Res.get(other.id));
|
||||
}
|
||||
|
||||
public String getDisplayString() {
|
||||
|
@ -182,6 +182,10 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl
|
||||
@Getter
|
||||
private final Map<String, String> extraDataMap;
|
||||
|
||||
// We cache the date object to avoid reconstructing a new Date at each getDate call.
|
||||
@JsonExclude
|
||||
private transient final Date dateObj;
|
||||
|
||||
public TradeStatistics3(String currency,
|
||||
long price,
|
||||
long amount,
|
||||
@ -251,6 +255,8 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl
|
||||
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
|
||||
|
||||
this.hash = hash == null ? createHash() : hash;
|
||||
|
||||
dateObj = new Date(date);
|
||||
}
|
||||
|
||||
public byte[] createHash() {
|
||||
@ -319,7 +325,7 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl
|
||||
|
||||
@Override
|
||||
public Date getDate() {
|
||||
return new Date(date);
|
||||
return dateObj;
|
||||
}
|
||||
|
||||
public long getDateAsLong() {
|
||||
|
@ -131,6 +131,19 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
"devinxmrwu4jrfq2zmq5kqjpxb44hx7i7didebkwrtvmvygj4uuop2ad.onion" // @devinbileck
|
||||
));
|
||||
|
||||
|
||||
private static final ArrayList<String> TX_BROADCAST_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList(
|
||||
"https://mempool.space/api/tx", // @wiz
|
||||
"https://mempool.emzy.de/api/tx", // @emzy
|
||||
"https://mempool.bisq.services/api/tx" // @devinbileck
|
||||
));
|
||||
|
||||
private static final ArrayList<String> TX_BROADCAST_SERVICES = new ArrayList<>(Arrays.asList(
|
||||
"http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/tx", // @wiz
|
||||
"http://mempool4t6mypeemozyterviq3i5de4kpoua65r3qkn5i3kknu5l2cad.onion/api/tx", // @emzy
|
||||
"http://mempoolusb2f67qi7mz2it7n5e77a6komdzx6wftobcduxszkdfun2yd.onion/api/tx" // @devinbileck
|
||||
));
|
||||
|
||||
public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true;
|
||||
|
||||
|
||||
@ -912,6 +925,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getDefaultTxBroadcastServices() {
|
||||
if (config.useLocalhostForP2P) {
|
||||
return TX_BROADCAST_SERVICES_CLEAR_NET;
|
||||
} else {
|
||||
return TX_BROADCAST_SERVICES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
@ -2892,15 +2892,12 @@ popup.accountSigning.peerLimitLifted=The initial limit for one of your accounts
|
||||
popup.accountSigning.peerSigner=One of your accounts is mature enough to sign other payment accounts \
|
||||
and the initial limit for one of your accounts has been lifted.\n\n{0}
|
||||
|
||||
popup.accountSigning.singleAccountSelect.headline=Select account age witness
|
||||
popup.accountSigning.singleAccountSelect.description=Search for account age witness.
|
||||
popup.accountSigning.singleAccountSelect.datePicker=Select point of time for signing
|
||||
popup.accountSigning.singleAccountSelect.headline=Import unsigned account age witness
|
||||
popup.accountSigning.confirmSingleAccount.headline=Confirm selected account age witness
|
||||
popup.accountSigning.confirmSingleAccount.selectedHash=Selected witness hash
|
||||
popup.accountSigning.confirmSingleAccount.button=Sign account age witness
|
||||
popup.accountSigning.successSingleAccount.description=Witness {0} was signed
|
||||
popup.accountSigning.successSingleAccount.success.headline=Success
|
||||
popup.accountSigning.successSingleAccount.signError=Failed to sign witness, {0}
|
||||
|
||||
popup.accountSigning.unsignedPubKeys.headline=Unsigned Pubkeys
|
||||
popup.accountSigning.unsignedPubKeys.sign=Sign Pubkeys
|
||||
|
@ -104,7 +104,7 @@ public class PeerInfoIcon extends Group {
|
||||
role,
|
||||
numTrades,
|
||||
privateNotificationManager,
|
||||
null,
|
||||
trade.getOffer(),
|
||||
trade,
|
||||
preferences,
|
||||
accountAgeWitnessService,
|
||||
@ -246,7 +246,7 @@ public class PeerInfoIcon extends Group {
|
||||
|
||||
getChildren().addAll(outerBackground, innerBackground, avatarImageView, tagPane, numTradesPane);
|
||||
|
||||
addMouseListener(numTrades, privateNotificationManager, offer, preferences, useDevPrivilegeKeys,
|
||||
addMouseListener(numTrades, privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys,
|
||||
isFiatCurrency, accountAge, signAge, peersAccount.third, peersAccount.fourth, peersAccount.fifth);
|
||||
}
|
||||
|
||||
@ -297,6 +297,7 @@ public class PeerInfoIcon extends Group {
|
||||
|
||||
protected void addMouseListener(int numTrades,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
@Nullable Trade trade,
|
||||
Offer offer,
|
||||
Preferences preferences,
|
||||
boolean useDevPrivilegeKeys,
|
||||
@ -319,7 +320,7 @@ public class PeerInfoIcon extends Group {
|
||||
Res.get("peerInfo.unknownAge") :
|
||||
null;
|
||||
|
||||
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer, preferences, useDevPrivilegeKeys)
|
||||
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, trade, offer, preferences, useDevPrivilegeKeys)
|
||||
.fullAddress(fullAddress)
|
||||
.numTrades(numTrades)
|
||||
.accountAge(accountAgeFormatted)
|
||||
|
@ -3,13 +3,17 @@ package bisq.desktop.components;
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.alert.PrivateNotificationManager;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PeerInfoIconSmall extends PeerInfoIcon {
|
||||
public PeerInfoIconSmall(NodeAddress nodeAddress,
|
||||
String role, Offer offer,
|
||||
String role,
|
||||
Offer offer,
|
||||
Preferences preferences,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
boolean useDevPrivilegeKeys) {
|
||||
@ -32,6 +36,7 @@ public class PeerInfoIconSmall extends PeerInfoIcon {
|
||||
@Override
|
||||
protected void addMouseListener(int numTrades,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
@Nullable Trade trade,
|
||||
Offer offer,
|
||||
Preferences preferences,
|
||||
boolean useDevPrivilegeKeys,
|
||||
|
@ -62,6 +62,8 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().logSigners();
|
||||
} else if (Utilities.isCtrlShiftPressed(KeyCode.U, event)) {
|
||||
accountAgeWitnessService.getAccountAgeWitnessUtils().logUnsignedSignerPubKeys();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.C, event)) {
|
||||
copyAccount();
|
||||
}
|
||||
};
|
||||
|
||||
@ -174,4 +176,7 @@ public abstract class PaymentAccountsView<R extends Node, M extends ActivatableW
|
||||
protected abstract void buildForm();
|
||||
|
||||
protected abstract void onSelectAccount(PaymentAccount paymentAccount);
|
||||
|
||||
protected void copyAccount() {
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ import bisq.core.util.validation.InputValidator;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Tuple3;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
@ -385,6 +386,7 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
paymentMethodComboBox.setPrefWidth(250);
|
||||
List<PaymentMethod> list = PaymentMethod.getPaymentMethods().stream()
|
||||
.filter(paymentMethod -> !paymentMethod.isAsset())
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
paymentMethodComboBox.setItems(FXCollections.observableArrayList(list));
|
||||
paymentMethodComboBox.setConverter(new StringConverter<>() {
|
||||
@ -540,5 +542,14 @@ public class FiatAccountsView extends PaymentAccountsView<GridPane, FiatAccounts
|
||||
gridRow = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void copyAccount() {
|
||||
var selectedAccount = paymentAccountsListView.getSelectionModel().getSelectedItem();
|
||||
if (selectedAccount == null) {
|
||||
return;
|
||||
}
|
||||
Utilities.copyToClipboard(accountAgeWitnessService.getSignInfoFromAccount(selectedAccount));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.FiatCurrency;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferRestrictions;
|
||||
@ -86,6 +87,7 @@ import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
@ -108,8 +110,6 @@ import javafx.util.StringConverter;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
|
||||
@FxmlView
|
||||
@ -241,7 +241,31 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
o -> CurrencyUtil.getCurrencyPair(o.getOffer().getCurrencyCode()),
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())
|
||||
));
|
||||
priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
|
||||
// We sort by % so we can also sort if SHOW ALL is selected
|
||||
Comparator<OfferBookListItem> marketBasedPriceComparator = (o1, o2) -> {
|
||||
Optional<Double> marketBasedPrice1 = model.getMarketBasedPrice(o1.getOffer());
|
||||
Optional<Double> marketBasedPrice2 = model.getMarketBasedPrice(o2.getOffer());
|
||||
if (marketBasedPrice1.isPresent() && marketBasedPrice2.isPresent()) {
|
||||
return Double.compare(marketBasedPrice1.get(), marketBasedPrice2.get());
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
// If we do not have a % price we use only fix price and sort by that
|
||||
priceColumn.setComparator(marketBasedPriceComparator.thenComparing((o1, o2) -> {
|
||||
Price price2 = o2.getOffer().getPrice();
|
||||
Price price1 = o1.getOffer().getPrice();
|
||||
if (price2 == null || price1 == null) {
|
||||
return 0;
|
||||
}
|
||||
if (model.getDirection() == OfferPayload.Direction.SELL) {
|
||||
return price1.compareTo(price2);
|
||||
} else {
|
||||
return price2.compareTo(price1);
|
||||
}
|
||||
}));
|
||||
|
||||
amountColumn.setComparator(Comparator.comparing(o -> o.getOffer().getMinAmount()));
|
||||
volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getMinVolume(), Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
paymentMethodColumn.setComparator(Comparator.comparing(o -> Res.get(o.getOffer().getPaymentMethod().getId())));
|
||||
@ -308,10 +332,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
currencyComboBox.getEditor().setText(new CurrencyStringConverter(currencyComboBox).toString(currencyComboBox.getSelectionModel().getSelectedItem()));
|
||||
|
||||
volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not());
|
||||
priceColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not());
|
||||
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());
|
||||
model.priceSortTypeProperty.addListener((observable, oldValue, newValue) -> priceColumn.setSortType(newValue));
|
||||
priceColumn.setSortType(model.priceSortTypeProperty.get());
|
||||
|
||||
amountColumn.sortTypeProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == TableColumn.SortType.DESCENDING) {
|
||||
@ -751,7 +772,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
private AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> getPriceColumn() {
|
||||
AutoTooltipTableColumn<OfferBookListItem, OfferBookListItem> column = new AutoTooltipTableColumn<>("") {
|
||||
{
|
||||
setMinWidth(100);
|
||||
setMinWidth(130);
|
||||
}
|
||||
};
|
||||
column.getStyleClass().add("number-column");
|
||||
@ -767,37 +788,40 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
setGraphic(getPriceLabel(model.getPrice(item), item));
|
||||
setGraphic(getPriceAndPercentage(item));
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private AutoTooltipLabel getPriceLabel(String priceString, OfferBookListItem item) {
|
||||
final Offer offer = item.getOffer();
|
||||
final MaterialDesignIcon icon = offer.isUseMarketBasedPrice() ? MaterialDesignIcon.CHART_LINE : MaterialDesignIcon.LOCK;
|
||||
|
||||
private HBox getPriceAndPercentage(OfferBookListItem item) {
|
||||
Offer offer = item.getOffer();
|
||||
boolean useMarketBasedPrice = offer.isUseMarketBasedPrice();
|
||||
MaterialDesignIcon icon = useMarketBasedPrice ? MaterialDesignIcon.CHART_LINE : MaterialDesignIcon.LOCK;
|
||||
String info;
|
||||
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
if (offer.getMarketPriceMargin() == 0) {
|
||||
if (useMarketBasedPrice) {
|
||||
double marketPriceMargin = offer.getMarketPriceMargin();
|
||||
if (marketPriceMargin == 0) {
|
||||
if (offer.isBuyOffer()) {
|
||||
info = Res.get("offerbook.info.sellAtMarketPrice");
|
||||
} else {
|
||||
info = Res.get("offerbook.info.buyAtMarketPrice");
|
||||
}
|
||||
} else if (offer.getMarketPriceMargin() > 0) {
|
||||
if (offer.isBuyOffer()) {
|
||||
info = Res.get("offerbook.info.sellBelowMarketPrice", model.getAbsolutePriceMargin(offer));
|
||||
} else {
|
||||
info = Res.get("offerbook.info.buyAboveMarketPrice", model.getAbsolutePriceMargin(offer));
|
||||
}
|
||||
} else {
|
||||
if (offer.isBuyOffer()) {
|
||||
info = Res.get("offerbook.info.sellAboveMarketPrice", model.getAbsolutePriceMargin(offer));
|
||||
String absolutePriceMargin = model.getAbsolutePriceMargin(offer);
|
||||
if (marketPriceMargin > 0) {
|
||||
if (offer.isBuyOffer()) {
|
||||
info = Res.get("offerbook.info.sellBelowMarketPrice", absolutePriceMargin);
|
||||
} else {
|
||||
info = Res.get("offerbook.info.buyAboveMarketPrice", absolutePriceMargin);
|
||||
}
|
||||
} else {
|
||||
info = Res.get("offerbook.info.buyBelowMarketPrice", model.getAbsolutePriceMargin(offer));
|
||||
if (offer.isBuyOffer()) {
|
||||
info = Res.get("offerbook.info.sellAboveMarketPrice", absolutePriceMargin);
|
||||
} else {
|
||||
info = Res.get("offerbook.info.buyBelowMarketPrice", absolutePriceMargin);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -807,8 +831,17 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
info = Res.get("offerbook.info.buyAtFixedPrice");
|
||||
}
|
||||
}
|
||||
InfoAutoTooltipLabel priceLabel = new InfoAutoTooltipLabel(model.getPrice(item),
|
||||
icon, ContentDisplay.RIGHT, info);
|
||||
priceLabel.setTextAlignment(TextAlignment.RIGHT);
|
||||
AutoTooltipLabel percentageLabel = new AutoTooltipLabel(model.getPriceAsPercentage(item));
|
||||
percentageLabel.setOpacity(useMarketBasedPrice ? 1 : 0.4);
|
||||
|
||||
return new InfoAutoTooltipLabel(priceString, icon, ContentDisplay.RIGHT, info);
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(5);
|
||||
hBox.getChildren().addAll(priceLabel, percentageLabel);
|
||||
hBox.setPadding(new Insets(7, 0, 0, 0));
|
||||
return hBox;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -977,12 +1010,12 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(final OfferBookListItem newItem, boolean empty) {
|
||||
super.updateItem(newItem, empty);
|
||||
public void updateItem(final OfferBookListItem item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
TableRow<OfferBookListItem> tableRow = getTableRow();
|
||||
if (newItem != null && !empty) {
|
||||
final Offer offer = newItem.getOffer();
|
||||
if (item != null && !empty) {
|
||||
final Offer offer = item.getOffer();
|
||||
boolean myOffer = model.isMyOffer(offer);
|
||||
if (tableRow != null) {
|
||||
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
|
||||
|
@ -35,6 +35,7 @@ import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.GlobalSettings;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.locale.TradeCurrency;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.monetary.Volume;
|
||||
import bisq.core.offer.Offer;
|
||||
@ -43,11 +44,14 @@ import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.PaymentAccountUtil;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.user.User;
|
||||
import bisq.core.util.AveragePriceUtil;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
@ -58,8 +62,10 @@ import bisq.network.p2p.P2PService;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@ -67,14 +73,10 @@ import javax.inject.Named;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
@ -94,6 +96,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
class OfferBookViewModel extends ActivatableViewModel {
|
||||
private final OpenOfferManager openOfferManager;
|
||||
@ -107,10 +113,9 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
private final FilterManager filterManager;
|
||||
final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
private final TradeStatisticsManager tradeStatisticsManager;
|
||||
private final CoinFormatter btcFormatter;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
final ObjectProperty<TableColumn.SortType> priceSortTypeProperty = new SimpleObjectProperty<>();
|
||||
|
||||
|
||||
private final FilteredList<OfferBookListItem> filteredItems;
|
||||
private final SortedList<OfferBookListItem> sortedItems;
|
||||
@ -134,6 +139,9 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
final IntegerProperty maxPlacesForPrice = new SimpleIntegerProperty();
|
||||
final IntegerProperty maxPlacesForMarketPriceMargin = new SimpleIntegerProperty();
|
||||
boolean showAllPaymentMethods = true;
|
||||
@Nullable
|
||||
private Price bsq30DayAveragePrice;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
@ -151,6 +159,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
super();
|
||||
@ -166,6 +175,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
this.filterManager = filterManager;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
|
||||
@ -210,7 +220,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
|
||||
filteredItems.addListener(filterItemsListener);
|
||||
|
||||
String code = direction == OfferPayload.Direction.BUY ? preferences.getBuyScreenCurrencyCode() : preferences.getSellScreenCurrencyCode();
|
||||
@ -224,13 +233,18 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
}
|
||||
tradeCurrencyCode.set(selectedTradeCurrency.getCode());
|
||||
|
||||
applyPriceSortTypeProperty(code);
|
||||
|
||||
fillAllTradeCurrencies();
|
||||
preferences.getTradeCurrenciesAsObservable().addListener(tradeCurrencyListChangeListener);
|
||||
offerBook.fillOfferBookListItems();
|
||||
applyFilterPredicate();
|
||||
setMarketPriceFeedCurrency();
|
||||
|
||||
// Null check needed for tests passing null for tradeStatisticsManager
|
||||
if (tradeStatisticsManager != null) {
|
||||
bsq30DayAveragePrice = AveragePriceUtil.getAveragePriceTuple(preferences,
|
||||
tradeStatisticsManager,
|
||||
30).second;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,7 +281,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
this.selectedTradeCurrency = tradeCurrency;
|
||||
tradeCurrencyCode.set(code);
|
||||
}
|
||||
applyPriceSortTypeProperty(code);
|
||||
|
||||
setMarketPriceFeedCurrency();
|
||||
applyFilterPredicate();
|
||||
@ -279,15 +292,6 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyPriceSortTypeProperty(String code) {
|
||||
final OfferPayload.Direction compareDirection = CurrencyUtil.isCryptoCurrency(code) ?
|
||||
OfferPayload.Direction.SELL :
|
||||
OfferPayload.Direction.BUY;
|
||||
priceSortTypeProperty.set(getDirection() == compareDirection ?
|
||||
TableColumn.SortType.ASCENDING :
|
||||
TableColumn.SortType.DESCENDING);
|
||||
}
|
||||
|
||||
void onSetPaymentMethod(PaymentMethod paymentMethod) {
|
||||
if (paymentMethod == null)
|
||||
return;
|
||||
@ -343,6 +347,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
|
||||
ObservableList<PaymentMethod> getPaymentMethods() {
|
||||
ObservableList<PaymentMethod> list = FXCollections.observableArrayList(PaymentMethod.getPaymentMethods());
|
||||
list.sort(Comparator.naturalOrder());
|
||||
list.add(0, PaymentMethod.getDummyPaymentMethod(GUIUtil.SHOW_ALL_FLAG));
|
||||
return list;
|
||||
}
|
||||
@ -357,13 +362,14 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
|
||||
|
||||
String getPrice(OfferBookListItem item) {
|
||||
if ((item == null))
|
||||
if ((item == null)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final Offer offer = item.getOffer();
|
||||
final Price price = offer.getPrice();
|
||||
Offer offer = item.getOffer();
|
||||
Price price = offer.getPrice();
|
||||
if (price != null) {
|
||||
return formatPrice(offer, true) + formatMarketPriceMargin(offer, true);
|
||||
return formatPrice(offer, true);
|
||||
} else {
|
||||
return Res.get("shared.na");
|
||||
}
|
||||
@ -377,11 +383,90 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
return DisplayUtils.formatPrice(offer.getPrice(), decimalAligned, maxPlacesForPrice.get());
|
||||
}
|
||||
|
||||
private String formatMarketPriceMargin(Offer offer, boolean decimalAligned) {
|
||||
String getPriceAsPercentage(OfferBookListItem item) {
|
||||
return getMarketBasedPrice(item.getOffer())
|
||||
.map(price -> "(" + FormattingUtils.formatPercentagePrice(price) + ")")
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public Optional<Double> getMarketBasedPrice(Offer offer) {
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
return Optional.of(offer.getMarketPriceMargin());
|
||||
}
|
||||
|
||||
if (!hasMarketPrice(offer)) {
|
||||
if (offer.getCurrencyCode().equals("BSQ")) {
|
||||
if (bsq30DayAveragePrice != null && bsq30DayAveragePrice.isPositive()) {
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(bsq30DayAveragePrice.getValue(), 8);
|
||||
return calculatePercentage(offer, scaled);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
double marketPriceAsDouble = checkNotNull(marketPrice).getPrice();
|
||||
return calculatePercentage(offer, marketPriceAsDouble);
|
||||
}
|
||||
|
||||
protected Optional<Double> calculatePercentage(Offer offer, double marketPrice) {
|
||||
// If the offer did not use % price we calculate % from current market price
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
Price price = offer.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long priceAsLong = checkNotNull(price).getValue();
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(priceAsLong, precision);
|
||||
|
||||
double value;
|
||||
if (direction == OfferPayload.Direction.SELL) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
} else {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
}
|
||||
} else {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
} else {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
}
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
public boolean hasMarketPrice(Offer offer) {
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
Price price = offer.getPrice();
|
||||
return price != null && marketPrice != null && marketPrice.isRecentExternalPriceAvailable();
|
||||
}
|
||||
|
||||
String formatMarketPriceMargin(Offer offer, boolean decimalAligned) {
|
||||
String postFix = "";
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
postFix = " (" + FormattingUtils.formatPercentagePrice(offer.getMarketPriceMargin()) + ")";
|
||||
|
||||
}
|
||||
|
||||
if (decimalAligned) {
|
||||
@ -591,11 +676,11 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||
|
||||
boolean isMyInsufficientTradeLimit(Offer offer) {
|
||||
Optional<PaymentAccount> accountOptional = getMostMaturePaymentAccountForOffer(offer);
|
||||
final long myTradeLimit = accountOptional
|
||||
long myTradeLimit = accountOptional
|
||||
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
|
||||
offer.getCurrencyCode(), offer.getMirroredDirection()))
|
||||
.orElse(0L);
|
||||
final long offerMinAmount = offer.getMinAmount().value;
|
||||
long offerMinAmount = offer.getMinAmount().value;
|
||||
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
|
||||
accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null",
|
||||
Coin.valueOf(myTradeLimit).toFriendlyString(),
|
||||
|
@ -25,6 +25,7 @@ import bisq.core.alert.PrivateNotificationManager;
|
||||
import bisq.core.locale.GlobalSettings;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.user.Preferences;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
@ -85,6 +86,8 @@ public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
|
||||
private int numTrades;
|
||||
private ChangeListener<Boolean> focusListener;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
@Nullable
|
||||
private final Trade trade;
|
||||
private final Offer offer;
|
||||
private final Preferences preferences;
|
||||
private EventHandler<KeyEvent> keyEventEventHandler;
|
||||
@ -99,10 +102,12 @@ public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
|
||||
private String signAgeInfo;
|
||||
|
||||
public PeerInfoWithTagEditor(PrivateNotificationManager privateNotificationManager,
|
||||
@Nullable Trade trade,
|
||||
Offer offer,
|
||||
Preferences preferences,
|
||||
boolean useDevPrivilegeKeys) {
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.trade = trade;
|
||||
this.offer = offer;
|
||||
this.preferences = preferences;
|
||||
this.useDevPrivilegeKeys = useDevPrivilegeKeys;
|
||||
@ -244,13 +249,20 @@ public class PeerInfoWithTagEditor extends Overlay<PeerInfoWithTagEditor> {
|
||||
// otherwise the text input handler does not work.
|
||||
doClose();
|
||||
UserThread.runAfter(() -> {
|
||||
//TODO only taker could send msg as maker would use its own key from offer....
|
||||
new SendPrivateNotificationWindow(
|
||||
privateNotificationManager,
|
||||
offer.getPubKeyRing(),
|
||||
offer.getMakerNodeAddress(),
|
||||
useDevPrivilegeKeys
|
||||
).show();
|
||||
PubKeyRing peersPubKeyRing = null;
|
||||
if (trade != null) {
|
||||
peersPubKeyRing = trade.getProcessModel().getTradingPeer().getPubKeyRing();
|
||||
} else if (offer != null) {
|
||||
peersPubKeyRing = offer.getPubKeyRing();
|
||||
}
|
||||
if (peersPubKeyRing != null) {
|
||||
new SendPrivateNotificationWindow(
|
||||
privateNotificationManager,
|
||||
peersPubKeyRing,
|
||||
offer.getMakerNodeAddress(),
|
||||
useDevPrivilegeKeys
|
||||
).show();
|
||||
}
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
};
|
||||
|
@ -18,6 +18,7 @@
|
||||
package bisq.desktop.main.overlays.windows;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.BisqTextArea;
|
||||
import bisq.desktop.components.InputTextField;
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
@ -27,24 +28,21 @@ import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.Utils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.jfoenix.controls.JFXAutoCompletePopup;
|
||||
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
||||
import javafx.geometry.VPos;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Date;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -53,10 +51,7 @@ import static bisq.desktop.util.FormBuilder.*;
|
||||
@Slf4j
|
||||
public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow> {
|
||||
|
||||
private InputTextField searchTextField;
|
||||
private JFXAutoCompletePopup<AccountAgeWitness> searchAutoComplete;
|
||||
private AccountAgeWitness selectedWitness;
|
||||
private DatePicker datePicker;
|
||||
private Tuple2<AccountAgeWitness, byte[]> signInfo;
|
||||
private InputTextField privateKey;
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final ArbitratorManager arbitratorManager;
|
||||
@ -89,56 +84,37 @@ public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow
|
||||
}
|
||||
|
||||
private void addSelectWitnessContent() {
|
||||
searchTextField = addInputTextField(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.singleAccountSelect.description"));
|
||||
TextArea accountInfoText = new BisqTextArea();
|
||||
accountInfoText.setPrefHeight(270);
|
||||
accountInfoText.setWrapText(true);
|
||||
GridPane.setRowIndex(accountInfoText, ++rowIndex);
|
||||
gridPane.getChildren().add(accountInfoText);
|
||||
|
||||
searchAutoComplete = new JFXAutoCompletePopup<>();
|
||||
searchAutoComplete.setPrefWidth(400);
|
||||
searchAutoComplete.getSuggestions().addAll(accountAgeWitnessService.getOrphanSignedWitnesses());
|
||||
searchAutoComplete.setSuggestionsCellFactory(param -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(AccountAgeWitness item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null) {
|
||||
setText(Utilities.bytesAsHexString(item.getHash()));
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
accountInfoText.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null || newValue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
searchAutoComplete.setSelectionHandler(event -> {
|
||||
searchTextField.setText(Utilities.bytesAsHexString(event.getObject().getHash()));
|
||||
selectedWitness = event.getObject();
|
||||
if (selectedWitness != null) {
|
||||
datePicker.setValue(Instant.ofEpochMilli(selectedWitness.getDate()).atZone(
|
||||
ZoneId.systemDefault()).toLocalDate());
|
||||
signInfo = accountAgeWitnessService.getSignInfoFromString(newValue);
|
||||
if (signInfo == null) {
|
||||
actionButton.setDisable(true);
|
||||
return;
|
||||
}
|
||||
actionButton.setDisable(false);
|
||||
});
|
||||
|
||||
searchTextField.textProperty().addListener(observable -> {
|
||||
searchAutoComplete.filter(witness -> Utilities.bytesAsHexString(witness.getHash()).startsWith(
|
||||
searchTextField.getText().toLowerCase()));
|
||||
if (searchAutoComplete.getFilteredSuggestions().isEmpty()) {
|
||||
searchAutoComplete.hide();
|
||||
} else {
|
||||
searchAutoComplete.show(searchTextField);
|
||||
}
|
||||
});
|
||||
|
||||
datePicker = addTopLabelDatePicker(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.singleAccountSelect.datePicker"),
|
||||
0).second;
|
||||
datePicker.setOnAction(e -> updateWitnessSelectionState());
|
||||
}
|
||||
|
||||
private void addECKeyField() {
|
||||
privateKey = addInputTextField(gridPane, ++rowIndex, Res.get("popup.accountSigning.signAccounts.ECKey"));
|
||||
actionButton.setDisable(true);
|
||||
GridPane.setVgrow(privateKey, Priority.ALWAYS);
|
||||
GridPane.setValignment(privateKey, VPos.TOP);
|
||||
}
|
||||
|
||||
private void updateWitnessSelectionState() {
|
||||
actionButton.setDisable(selectedWitness == null || datePicker.getValue() == null);
|
||||
privateKey.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (checkedArbitratorKey() == null) {
|
||||
actionButton.setDisable(true);
|
||||
return;
|
||||
}
|
||||
actionButton.setDisable(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void removeContent() {
|
||||
@ -146,30 +122,22 @@ public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow
|
||||
rowIndex = 1;
|
||||
}
|
||||
|
||||
private void selectAccountAgeWitness() {
|
||||
private void importAccountAgeWitness() {
|
||||
removeContent();
|
||||
headLineLabel.setText(Res.get("popup.accountSigning.confirmSingleAccount.headline"));
|
||||
var selectedWitnessTextField = addTopLabelTextField(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.confirmSingleAccount.selectedHash")).second;
|
||||
selectedWitnessTextField.setText(Utilities.bytesAsHexString(selectedWitness.getHash()));
|
||||
selectedWitnessTextField.setText(Utilities.bytesAsHexString(signInfo.first.getHash()));
|
||||
addECKeyField();
|
||||
((AutoTooltipButton) actionButton).updateText(Res.get("popup.accountSigning.confirmSingleAccount.button"));
|
||||
actionButton.setOnAction(a -> {
|
||||
var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText());
|
||||
var arbitratorKey = checkedArbitratorKey();
|
||||
if (arbitratorKey != null) {
|
||||
var arbitratorPubKeyAsHex = Utils.HEX.encode(arbitratorKey.getPubKey());
|
||||
var isKeyValid = arbitratorManager.isPublicKeyInList(arbitratorPubKeyAsHex);
|
||||
if (isKeyValid) {
|
||||
var result = accountAgeWitnessService.arbitratorSignOrphanWitness(selectedWitness,
|
||||
arbitratorKey,
|
||||
datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC) * 1000);
|
||||
if (result.isEmpty()) {
|
||||
addSuccessContent();
|
||||
} else {
|
||||
new Popup().error(Res.get("popup.accountSigning.successSingleAccount.signError", result))
|
||||
.onClose(this::hide).show();
|
||||
}
|
||||
}
|
||||
accountAgeWitnessService.arbitratorSignAccountAgeWitness(signInfo.first,
|
||||
arbitratorKey,
|
||||
signInfo.second,
|
||||
new Date().getTime());
|
||||
addSuccessContent();
|
||||
} else {
|
||||
new Popup().error(Res.get("popup.accountSigning.signAccounts.ECKey.error")).onClose(this::hide).show();
|
||||
}
|
||||
@ -177,7 +145,6 @@ public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void addSuccessContent() {
|
||||
removeContent();
|
||||
closeButton.setVisible(false);
|
||||
@ -185,7 +152,7 @@ public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow
|
||||
headLineLabel.setText(Res.get("popup.accountSigning.successSingleAccount.success.headline"));
|
||||
var descriptionLabel = addMultilineLabel(gridPane, ++rowIndex,
|
||||
Res.get("popup.accountSigning.successSingleAccount.description",
|
||||
Utilities.bytesAsHexString(selectedWitness.getHash())));
|
||||
Utilities.bytesAsHexString(signInfo.first.getHash())));
|
||||
GridPane.setVgrow(descriptionLabel, Priority.ALWAYS);
|
||||
GridPane.setValignment(descriptionLabel, VPos.TOP);
|
||||
((AutoTooltipButton) actionButton).updateText(Res.get("shared.ok"));
|
||||
@ -194,15 +161,24 @@ public class SignSpecificWitnessWindow extends Overlay<SignSpecificWitnessWindow
|
||||
|
||||
@Override
|
||||
protected void addButtons() {
|
||||
var buttonTuple = add2ButtonsAfterGroup(gridPane, ++rowIndex + 1,
|
||||
var buttonTuple = add2ButtonsAfterGroup(gridPane, ++rowIndex + 2,
|
||||
Res.get("popup.accountSigning.singleAccountSelect.headline"), Res.get("shared.cancel"));
|
||||
|
||||
actionButton = buttonTuple.first;
|
||||
actionButton.setDisable(true);
|
||||
actionButton.setOnAction(e -> selectAccountAgeWitness());
|
||||
actionButton.setOnAction(e -> importAccountAgeWitness());
|
||||
|
||||
closeButton = (AutoTooltipButton) buttonTuple.second;
|
||||
closeButton.setOnAction(e -> hide());
|
||||
}
|
||||
|
||||
private ECKey checkedArbitratorKey() {
|
||||
var arbitratorKey = arbitratorManager.getRegistrationKey(privateKey.getText());
|
||||
if (arbitratorKey == null) {
|
||||
return null;
|
||||
}
|
||||
var arbitratorPubKeyAsHex = Utils.HEX.encode(arbitratorKey.getPubKey());
|
||||
var isKeyValid = arbitratorManager.isPublicKeyInList(arbitratorPubKeyAsHex);
|
||||
return isKeyValid ? arbitratorKey : null;
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ import bisq.core.payment.payload.SepaAccountPayload;
|
||||
import bisq.core.payment.payload.SpecificBanksAccountPayload;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
import bisq.core.util.coin.ImmutableCoinFormatter;
|
||||
import bisq.core.util.coin.BsqFormatter;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
|
||||
@ -229,7 +229,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForAmount.intValue());
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(6, model.maxPlacesForAmount.intValue());
|
||||
@ -261,7 +261,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(15, model.maxPlacesForAmount.intValue());
|
||||
@ -280,7 +280,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForVolume.intValue());
|
||||
}
|
||||
|
||||
@ -294,7 +294,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(5, model.maxPlacesForVolume.intValue());
|
||||
@ -312,7 +312,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(9, model.maxPlacesForVolume.intValue());
|
||||
@ -331,7 +331,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForPrice.intValue());
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(7, model.maxPlacesForPrice.intValue());
|
||||
@ -363,7 +363,7 @@ public class OfferBookViewModelTest {
|
||||
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue());
|
||||
}
|
||||
|
||||
@ -391,7 +391,7 @@ public class OfferBookViewModelTest {
|
||||
offerBookListItems.addAll(item1, item2);
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
model.activate();
|
||||
|
||||
assertEquals(8, model.maxPlacesForMarketPriceMargin.intValue()); //" (1.97%)"
|
||||
@ -412,7 +412,7 @@ public class OfferBookViewModelTest {
|
||||
when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
|
||||
|
||||
final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
|
||||
null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
null, null, null, null, null, coinFormatter, new BsqFormatter());
|
||||
|
||||
final OfferBookListItem item = make(btcBuyItem.but(
|
||||
with(useMarketBasedPrice, true),
|
||||
@ -429,13 +429,15 @@ public class OfferBookViewModelTest {
|
||||
offerBookListItems.addAll(lowItem, fixedItem);
|
||||
model.activate();
|
||||
|
||||
assertEquals("12557.2046 (1.00%)", model.getPrice(lowItem));
|
||||
|
||||
assertEquals("12557.2046", model.getPrice(lowItem));
|
||||
assertEquals("(1.00%)", model.getPriceAsPercentage(lowItem));
|
||||
assertEquals("10.0000", model.getPrice(fixedItem));
|
||||
offerBookListItems.addAll(item);
|
||||
assertEquals("14206.1304 (-12.00%)", model.getPrice(item));
|
||||
assertEquals("12557.2046 (1.00%)", model.getPrice(lowItem));
|
||||
|
||||
|
||||
assertEquals("14206.1304", model.getPrice(item));
|
||||
assertEquals("(-12.00%)", model.getPriceAsPercentage(item));
|
||||
assertEquals("12557.2046", model.getPrice(lowItem));
|
||||
assertEquals("(1.00%)", model.getPriceAsPercentage(lowItem));
|
||||
}
|
||||
|
||||
private PaymentAccount getAliPayAccount(String currencyCode) {
|
||||
|
@ -22,6 +22,7 @@ import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.common.app.Version;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
@ -30,6 +31,7 @@ import org.apache.http.client.protocol.HttpClientContext;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.config.RegistryBuilder;
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
@ -43,10 +45,13 @@ import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -145,9 +150,9 @@ public class HttpClientImpl implements HttpClient {
|
||||
@Nullable String headerKey,
|
||||
@Nullable String headerValue) throws IOException {
|
||||
long ts = System.currentTimeMillis();
|
||||
String spec = baseUrl + param;
|
||||
log.info("requestWithoutProxy: URL={}, httpMethod={}", spec, httpMethod);
|
||||
log.info("requestWithoutProxy: URL={}, param={}, httpMethod={}", baseUrl, param, httpMethod);
|
||||
try {
|
||||
String spec = httpMethod == HttpMethod.GET ? baseUrl + param : baseUrl;
|
||||
URL url = new URL(spec);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod(httpMethod.name());
|
||||
@ -158,23 +163,46 @@ public class HttpClientImpl implements HttpClient {
|
||||
connection.setRequestProperty(headerKey, headerValue);
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 200) {
|
||||
if (httpMethod == HttpMethod.POST) {
|
||||
connection.setDoOutput(true);
|
||||
connection.getOutputStream().write(param.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200) {
|
||||
String response = convertInputStreamToString(connection.getInputStream());
|
||||
log.info("Response for {} took {} ms. Data size:{}, response: {}",
|
||||
spec,
|
||||
log.info("Response from {} with param {} took {} ms. Data size:{}, response: {}",
|
||||
baseUrl,
|
||||
param,
|
||||
System.currentTimeMillis() - ts,
|
||||
Utilities.readableFileSize(response.getBytes().length),
|
||||
Utilities.toTruncatedString(response));
|
||||
return response;
|
||||
} else {
|
||||
String error = convertInputStreamToString(connection.getErrorStream());
|
||||
connection.getErrorStream().close();
|
||||
throw new HttpException(error);
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
String error = convertInputStreamToString(errorStream);
|
||||
errorStream.close();
|
||||
log.info("Received errorMsg '{}' with responseCode {} from {}. Response took: {} ms. param: {}",
|
||||
error,
|
||||
responseCode,
|
||||
baseUrl,
|
||||
System.currentTimeMillis() - ts,
|
||||
param);
|
||||
throw new HttpException(error, responseCode);
|
||||
} else {
|
||||
log.info("Response with responseCode {} from {}. Response took: {} ms. param: {}",
|
||||
responseCode,
|
||||
baseUrl,
|
||||
System.currentTimeMillis() - ts,
|
||||
param);
|
||||
throw new HttpException("Request failed", responseCode);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
String message = "Error at requestWithoutProxy with URL: " + spec + ". Throwable=" + t.getMessage();
|
||||
log.error(message);
|
||||
throw new IOException(message);
|
||||
String message = "Error at requestWithoutProxy with url " + baseUrl + " and param " + param +
|
||||
". Throwable=" + t.getMessage();
|
||||
throw new IOException(message, t);
|
||||
} finally {
|
||||
try {
|
||||
if (connection != null) {
|
||||
@ -195,8 +223,7 @@ public class HttpClientImpl implements HttpClient {
|
||||
@Nullable String headerKey,
|
||||
@Nullable String headerValue) throws IOException {
|
||||
long ts = System.currentTimeMillis();
|
||||
String uri = baseUrl + param;
|
||||
log.info("requestWithoutProxy: uri={}, httpMethod={}", uri, httpMethod);
|
||||
log.info("requestWithoutProxy: baseUrl={}, param={}, httpMethod={}", baseUrl, param, httpMethod);
|
||||
// This code is adapted from:
|
||||
// http://stackoverflow.com/a/25203021/5616248
|
||||
|
||||
@ -212,7 +239,7 @@ public class HttpClientImpl implements HttpClient {
|
||||
new PoolingHttpClientConnectionManager(reg) :
|
||||
new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
|
||||
try {
|
||||
closeableHttpClient = HttpClients.custom().setConnectionManager(cm).build();
|
||||
closeableHttpClient = checkNotNull(HttpClients.custom().setConnectionManager(cm).build());
|
||||
InetSocketAddress socksAddress = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort());
|
||||
|
||||
// remove me: Use this to test with system-wide Tor proxy, or change port for another proxy.
|
||||
@ -221,23 +248,36 @@ public class HttpClientImpl implements HttpClient {
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute("socks.address", socksAddress);
|
||||
|
||||
HttpUriRequest request = getHttpUriRequest(httpMethod, uri);
|
||||
if (headerKey != null && headerValue != null)
|
||||
HttpUriRequest request = getHttpUriRequest(httpMethod, baseUrl, param);
|
||||
if (headerKey != null && headerValue != null) {
|
||||
request.setHeader(headerKey, headerValue);
|
||||
}
|
||||
|
||||
try (CloseableHttpResponse httpResponse = checkNotNull(closeableHttpClient).execute(request, context)) {
|
||||
try (CloseableHttpResponse httpResponse = closeableHttpClient.execute(request, context)) {
|
||||
String response = convertInputStreamToString(httpResponse.getEntity().getContent());
|
||||
log.info("Response for {} took {} ms. Data size:{}, response: {}",
|
||||
uri,
|
||||
System.currentTimeMillis() - ts,
|
||||
Utilities.readableFileSize(response.getBytes().length),
|
||||
Utilities.toTruncatedString(response));
|
||||
return response;
|
||||
int statusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
if (statusCode == 200) {
|
||||
log.info("Response from {} took {} ms. Data size:{}, response: {}, param: {}",
|
||||
baseUrl,
|
||||
System.currentTimeMillis() - ts,
|
||||
Utilities.readableFileSize(response.getBytes().length),
|
||||
Utilities.toTruncatedString(response),
|
||||
param);
|
||||
return response;
|
||||
} else {
|
||||
log.info("Received errorMsg '{}' with statusCode {} from {}. Response took: {} ms. param: {}",
|
||||
response,
|
||||
statusCode,
|
||||
baseUrl,
|
||||
System.currentTimeMillis() - ts,
|
||||
param);
|
||||
throw new HttpException(response, statusCode);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
String message = "Error at doRequestWithProxy with URL: " + uri + ". Throwable=" + t.getMessage();
|
||||
log.error(message);
|
||||
throw new IOException(message);
|
||||
String message = "Error at doRequestWithProxy with url " + baseUrl + " and param " + param +
|
||||
". Throwable=" + t.getMessage();
|
||||
throw new IOException(message, t);
|
||||
} finally {
|
||||
if (closeableHttpClient != null) {
|
||||
closeableHttpClient.close();
|
||||
@ -247,12 +287,17 @@ public class HttpClientImpl implements HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
private HttpUriRequest getHttpUriRequest(HttpMethod httpMethod, String uri) {
|
||||
private HttpUriRequest getHttpUriRequest(HttpMethod httpMethod, String baseUrl, String param)
|
||||
throws UnsupportedEncodingException {
|
||||
switch (httpMethod) {
|
||||
case GET:
|
||||
return new HttpGet(uri);
|
||||
return new HttpGet(baseUrl + param);
|
||||
case POST:
|
||||
return new HttpPost(uri);
|
||||
HttpPost httpPost = new HttpPost(baseUrl);
|
||||
HttpEntity httpEntity = new StringEntity(param);
|
||||
httpPost.setEntity(httpEntity);
|
||||
return httpPost;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("HttpMethod not supported: " + httpMethod);
|
||||
}
|
||||
|
@ -17,8 +17,18 @@
|
||||
|
||||
package bisq.network.http;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class HttpException extends Exception {
|
||||
@Getter
|
||||
private int responseCode;
|
||||
|
||||
public HttpException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpException(String message, int responseCode) {
|
||||
super(message);
|
||||
this.responseCode = responseCode;
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
||||
networkNode.addMessageListener(this);
|
||||
networkNode.addConnectionListener(this);
|
||||
|
||||
this.persistenceManager.initialize(sequenceNumberMap, PersistenceManager.Source.PRIVATE);
|
||||
this.persistenceManager.initialize(sequenceNumberMap, PersistenceManager.Source.PRIVATE_LOW_PRIO);
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user