Merge branch 'master' into 09-refactor-grpc-error-handling

This commit is contained in:
ghubstan 2020-12-16 13:45:29 -03:00
commit 1cd47fd0aa
No known key found for this signature in database
GPG Key ID: E35592D6800A861E
27 changed files with 731 additions and 224 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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