UI improvements, P2P network WIP

This commit is contained in:
Manfred Karrer 2016-02-14 12:58:30 +01:00
parent 615f5570c3
commit 850b6d209c
54 changed files with 684 additions and 532 deletions

View file

@ -1,7 +1,6 @@
package io.bitsquare.btc.pricefeed;
import com.google.common.util.concurrent.*;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.providers.PriceProvider;
import io.bitsquare.common.util.Utilities;
import org.jetbrains.annotations.NotNull;
@ -44,7 +43,7 @@ class GetPriceRequest {
}
private SettableFuture<MarketPrice> requestPrice(String currencyCode, PriceProvider provider, SettableFuture<MarketPrice> resultFuture) {
Log.traceCall(currencyCode);
// Log.traceCall(currencyCode);
ListenableFuture<MarketPrice> future = executorService.submit(() -> {
Thread.currentThread().setName("requestPrice-" + provider.toString());
return provider.getPrice(currencyCode);

View file

@ -4,7 +4,6 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.providers.BitcoinAveragePriceProvider;
import io.bitsquare.btc.pricefeed.providers.PoloniexPriceProvider;
import io.bitsquare.btc.pricefeed.providers.PriceProvider;
@ -140,7 +139,7 @@ public class MarketPriceFeed {
if (priceConsumer != null && currencyCode != null && type != null) {
if (cache.containsKey(currencyCode)) {
MarketPrice marketPrice = cache.get(currencyCode);
log.debug("applyPrice type=" + type);
//log.debug("applyPrice type=" + type);
priceConsumer.accept(marketPrice.getPrice(type));
} else {
String errorMessage = "We don't have a price for currencyCode " + currencyCode;
@ -151,14 +150,14 @@ public class MarketPriceFeed {
}
private void requestPrice(PriceProvider provider) {
Log.traceCall();
//Log.traceCall();
GetPriceRequest getPriceRequest = new GetPriceRequest();
SettableFuture<MarketPrice> future = getPriceRequest.requestPrice(currencyCode, provider);
Futures.addCallback(future, new FutureCallback<MarketPrice>() {
public void onSuccess(MarketPrice marketPrice) {
UserThread.execute(() -> {
cache.put(marketPrice.currencyCode, marketPrice);
log.debug("marketPrice updated " + marketPrice);
//log.debug("marketPrice updated " + marketPrice);
priceConsumer.accept(marketPrice.getPrice(type));
});
}
@ -170,7 +169,7 @@ public class MarketPriceFeed {
}
private void requestAllPrices(PriceProvider provider, @Nullable Runnable resultHandler) {
Log.traceCall();
// Log.traceCall();
GetPriceRequest getPriceRequest = new GetPriceRequest();
SettableFuture<Map<String, MarketPrice>> future = getPriceRequest.requestAllPrices(provider);
Futures.addCallback(future, new FutureCallback<Map<String, MarketPrice>>() {

View file

@ -4,7 +4,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
@ -45,7 +44,7 @@ public class BitcoinAveragePriceProvider implements PriceProvider {
@Override
public MarketPrice getPrice(String currencyCode) throws IOException, HttpException {
Log.traceCall("currencyCode=" + currencyCode);
//Log.traceCall("currencyCode=" + currencyCode);
JsonObject jsonObject = new JsonParser()
.parse(httpClient.requestWithGET(currencyCode))
.getAsJsonObject();

View file

@ -4,7 +4,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import io.bitsquare.app.Log;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
@ -63,7 +62,7 @@ public class PoloniexPriceProvider implements PriceProvider {
@Override
public MarketPrice getPrice(String currencyCode) throws IOException, HttpException {
Log.traceCall("currencyCode=" + currencyCode);
// Log.traceCall("currencyCode=" + currencyCode);
JsonObject jsonObject = new JsonParser()
.parse(httpClient.requestWithGET(currencyCode))
.getAsJsonObject();

View file

@ -80,6 +80,10 @@ public class CountryUtil {
return new Locale(LanguageUtil.getDefaultLanguage(), countryCode).getDisplayCountry();
}
public static String getNameAndCode(String countryCode) {
return getNameByCode(countryCode) + " (" + countryCode + ")";
}
public static String getCodesString(List<String> countryCodes) {
return countryCodes.stream().collect(Collectors.joining(", "));
}

View file

@ -171,12 +171,6 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("ETH", "Ethereum"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("NMC", "Namecoin"));
// Unfortunately we cannot support CryptoNote coins yet as there is no way to proof the transaction. Payment ID helps only locate the tx but the
// arbitrator cannot see if the receiving key matches the receivers address. They might add support for exposing the tx key, but that is not
// implemented yet. To use the view key (also not available in GUI wallets) would reveal the complete wallet history for incoming payments, which is
// not acceptable from privacy point of view.
// result.add(new CryptoCurrency("XMR", "Monero"));
// result.add(new CryptoCurrency("BCN", "Bytecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("NBT", "NuBits"));
result.add(new CryptoCurrency("NSR", "NuShares"));
@ -192,6 +186,13 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("BTS", "BitShares"));
result.add(new CryptoCurrency("XCP", "Counterparty"));
result.add(new CryptoCurrency("XRP", "Ripple"));
// Unfortunately we cannot support CryptoNote coins yet as there is no way to proof the transaction. Payment ID helps only locate the tx but the
// arbitrator cannot see if the receiving key matches the receivers address. They might add support for exposing the tx key, but that is not
// implemented yet. To use the view key (also not available in GUI wallets) would reveal the complete wallet history for incoming payments, which is
// not acceptable from privacy point of view.
// result.add(new CryptoCurrency("XMR", "Monero"));
// result.add(new CryptoCurrency("BCN", "Bytecoin"));
return result;
}
@ -213,8 +214,8 @@ public class CurrencyUtil {
try {
return Currency.getInstance(currencyCode).getDisplayName(Preferences.getDefaultLocale());
} catch (Throwable t) {
// Seems that it is a crypto currency
return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findFirst().get().getCodeAndName();
// Seems that it is a cryptocurrency
return getSortedCryptoCurrencies().stream().filter(e -> e.getCode().equals(currencyCode)).findFirst().get().getName();
}
}

View file

@ -58,6 +58,10 @@ public class TradeCurrency implements Serializable {
return symbol;
}
public String getNameAndCode() {
return name + " (" + code + ")";
}
public String getCodeAndName() {
return code + " (" + name + ")";
}

View file

@ -44,6 +44,11 @@ public class AliPayAccountContractData extends PaymentAccountContractData implem
return "AliPay - Account nr.: " + accountNr;
}
@Override
public String getPaymentDetailsForTradePopup() {
return getPaymentDetails();
}
@Override
public String toString() {
return "AliPayAccountContractData{" +

View file

@ -43,9 +43,14 @@ public class BlockChainAccountContractData extends PaymentAccountContractData im
@Override
public String getPaymentDetails() {
return "Address: " + address;
return "Receivers cryptocurrency address: " + address;
}
@Override
public String getPaymentDetailsForTradePopup() {
return getPaymentDetails();
}
public void setPaymentId(String paymentId) {
this.paymentId = paymentId;
}

View file

@ -44,5 +44,8 @@ public class OKPayAccountContractData extends PaymentAccountContractData impleme
return "OKPay - Account nr.: " + accountNr;
}
@Override
public String getPaymentDetailsForTradePopup() {
return getPaymentDetails();
}
}

View file

@ -72,6 +72,8 @@ public abstract class PaymentAccountContractData implements Serializable {
abstract public String getPaymentDetails();
abstract public String getPaymentDetailsForTradePopup();
public int getMaxTradePeriod() {
return maxTradePeriod;
}

View file

@ -44,4 +44,9 @@ public class PerfectMoneyAccountContractData extends PaymentAccountContractData
return "PerfectMoney - Account nr.: " + accountNr;
}
@Override
public String getPaymentDetailsForTradePopup() {
return getPaymentDetails();
}
}

View file

@ -89,4 +89,12 @@ public class SepaAccountContractData extends PaymentAccountContractData implemen
public String getPaymentDetails() {
return "SEPA - Holder name: " + holderName + ", IBAN: " + iban + ", BIC: " + bic + ", country code: " + getCountryCode();
}
@Override
public String getPaymentDetailsForTradePopup() {
return "Holder name: " + holderName + "\n" +
"IBAN: " + iban + "\n" +
"BIC: " + bic + "\n" +
"Country of bank: " + CountryUtil.getNameAndCode(getCountryCode());
}
}

View file

@ -53,4 +53,9 @@ public class SwishAccountContractData extends PaymentAccountContractData impleme
return "Swish - Holder name: " + holderName + ", mobile nr.: " + mobileNr;
}
@Override
public String getPaymentDetailsForTradePopup() {
return "Holder name: " + holderName + "\n" +
"Mobile nr.: " + mobileNr;
}
}

View file

@ -146,7 +146,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
// Mutable
private DecryptedMsgWithPubKey decryptedMsgWithPubKey;
private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
private Date takeOfferDate;
private int takeOfferDateAsBlockHeight;
private Coin tradeAmount;
private NodeAddress tradingPeerNodeAddress;
@ -180,6 +180,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
protected Trade(Offer offer, Storage<? extends TradableList> storage) {
this.offer = offer;
this.storage = storage;
this.takeOfferDate = new Date();
processModel = new ProcessModel();
tradeVolumeProperty = new SimpleObjectProperty<>();
@ -199,6 +200,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
tradeAmountProperty.set(tradeAmount);
tradeVolumeProperty.set(getTradeVolume());
this.takeOfferDate = new Date();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
@ -411,9 +413,9 @@ abstract public class Trade implements Tradable, Model, Serializable {
return takeOfferDate;
}
public void setTakeOfferDate(Date takeOfferDate) {
/*public void setTakeOfferDate(Date takeOfferDate) {
this.takeOfferDate = takeOfferDate;
}
}*/
public int getTakeOfferDateAsBlockHeight() {
return takeOfferDateAsBlockHeight;

View file

@ -59,7 +59,6 @@ import org.spongycastle.crypto.params.KeyParameter;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.util.Date;
import java.util.Optional;
import static io.bitsquare.util.Validator.nonEmptyStringOf;
@ -98,7 +97,6 @@ public class TradeManager {
ArbitratorManager arbitratorManager,
P2PService p2PService,
@Named("storage.dir") File storageDir) {
Log.traceCall();
this.user = user;
this.keyRing = keyRing;
this.walletService = walletService;
@ -280,7 +278,7 @@ public class TradeManager {
else
trade = new BuyerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage);
trade.setTakeOfferDate(new Date());
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
trade.setTakerPaymentAccountId(paymentAccountId);
@ -312,8 +310,8 @@ public class TradeManager {
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
if (transaction != null) {
log.info("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
trade.setState(Trade.State.WITHDRAW_COMPLETED);
addTradeToClosedTrades(trade);
trade.setState(Trade.State.WITHDRAW_COMPLETED);
resultHandler.handleResult();
}
}

View file

@ -25,6 +25,7 @@ import io.bitsquare.trade.TradableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
@ -55,6 +56,18 @@ public class OpenOffer implements Tradable, Serializable {
this.storage = storage;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
// If we have a reserved state from the local db we reset it
if (state == State.RESERVED)
setState(State.AVAILABLE);
} catch (Throwable t) {
log.error("Cannot be deserialized." + t.getMessage());
}
}
public Date getDate() {
return offer.getDate();
}
@ -98,7 +111,7 @@ public class OpenOffer implements Tradable, Serializable {
stopTimeout();
timeoutTimer = UserThread.runAfter(() -> {
log.info("Timeout reached");
log.info("Timeout for resettin State.RESERVED reached");
if (state == State.RESERVED)
setState(State.AVAILABLE);
}, TIMEOUT_SEC);

View file

@ -47,13 +47,13 @@ public class ProcessFinalizePayoutTxRequest extends TradeTask {
processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(message.sellerPayoutAddress));
trade.setLockTimeAsBlockHeight(nonNegativeLongOf(message.lockTimeAsBlockHeight));
trade.setState(Trade.State.FIAT_PAYMENT_RECEIPT_MSG_RECEIVED);
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.FIAT_PAYMENT_RECEIPT_MSG_RECEIVED);
complete();
} catch (Throwable t) {
failed(t);

View file

@ -27,8 +27,6 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
public class SignAndPublishDepositTxAsBuyer extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsBuyer.class);
@ -64,7 +62,7 @@ public class SignAndPublishDepositTxAsBuyer extends TradeTask {
log.trace("takerSignAndPublishTx succeeded " + transaction);
trade.setDepositTx(transaction);
trade.setTakeOfferDate(new Date());
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);

View file

@ -26,8 +26,6 @@ import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.util.Validator.checkTradeId;
@ -53,11 +51,10 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
Transaction transactionFromSerializedTx = processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx);
// update with full tx
trade.setDepositTx(processModel.getTradeWalletService().addTransactionToWallet(transactionFromSerializedTx));
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED);
trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
if (trade instanceof OffererTrade)
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());
@ -65,6 +62,8 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED);
complete();
} catch (Throwable t) {

View file

@ -36,7 +36,6 @@ public class SendPublishDepositTxRequest extends TradeTask {
protected void run() {
try {
runInterceptHook();
trade.setState(Trade.State.DEPOSIT_PUBLISH_REQUESTED);
PublishDepositTxRequest tradeMessage = new PublishDepositTxRequest(
processModel.getId(),
processModel.getPaymentAccountContractData(trade),
@ -69,6 +68,9 @@ public class SendPublishDepositTxRequest extends TradeTask {
}
}
);
//TODO should it be in success handler?
trade.setState(Trade.State.DEPOSIT_PUBLISH_REQUESTED);
} catch (Throwable t) {
failed(t);
}

View file

@ -46,13 +46,13 @@ public class ProcessFiatTransferStartedMessage extends TradeTask {
processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(message.buyerPayoutAddress));
trade.setState(Trade.State.FIAT_PAYMENT_STARTED_MSG_RECEIVED);
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.FIAT_PAYMENT_STARTED_MSG_RECEIVED);
complete();
} catch (Throwable t) {
failed(t);

View file

@ -46,13 +46,13 @@ public class ProcessPayoutTxFinalizedMessage extends TradeTask {
checkArgument(message.payoutTx != null);
trade.setPayoutTx(processModel.getWalletService().getTransactionFromSerializedTx(message.payoutTx));
trade.setState(Trade.State.PAYOUT_TX_RECEIVED);
// update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
removeMailboxMessageAfterProcessing();
trade.setState(Trade.State.PAYOUT_TX_RECEIVED);
complete();
} catch (Throwable t) {
failed(t);

View file

@ -27,8 +27,6 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
public class SignAndPublishDepositTxAsSeller extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsSeller.class);
@ -62,7 +60,7 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
log.trace("takerSignAndPublishTx succeeded " + transaction);
trade.setDepositTx(transaction);
trade.setTakeOfferDate(new Date());
//trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);

View file

@ -634,49 +634,6 @@ textfield */
-fx-base: #dd0000;
}
#trade-notification-warning {
-fx-font-size: 14;
-fx-base: -bs-red-soft;
}
#trade-notification-information {
-fx-font-size: 14;
-fx-outer-border: linear-gradient(to bottom, #ffb34b, #ff9200);
}
#trade-notification-dispute {
-fx-font-size: 14;
-fx-base: -bs-error-red;
}
#trade-notification-support {
-fx-font-size: 14;
-fx-base: -bs-orange;
}
#support-info-label {
-fx-font-size: 14;
-fx-text-fill: -bs-red-soft;
}
#titled-group-bg-warn {
-fx-body-color: linear-gradient(to bottom, -bs-content-bg-grey, #F0F0F0);
-fx-outer-border: linear-gradient(to bottom, #ffb34b, #ff9200);
-fx-background-color: -fx-shadow-highlight-color,
-fx-outer-border,
-fx-inner-border,
-fx-body-color;
-fx-background-insets: 0 0 -1 0, 0, 1, 2;
-fx-background-radius: 3px, 3px, 2px, 1px;
}
#titled-group-bg-label-warn {
-fx-font-weight: bold;
-fx-font-size: 14;
-fx-text-fill: #ff9200;
-fx-background-color: -bs-content-bg-grey;
}
/* TitledGroupBg */
#titled-group-bg-label {
-fx-font-weight: bold;

View file

@ -93,6 +93,10 @@ public class TextFieldWithCopyIcon extends AnchorPane {
this.text.set(text);
}
public void setTooltip(Tooltip toolTip) {
textField.setTooltip(toolTip);
}
public void setCopyWithoutCurrencyPostFix(boolean copyWithoutCurrencyPostFix) {
this.copyWithoutCurrencyPostFix = copyWithoutCurrencyPostFix;
}

View file

@ -75,12 +75,15 @@ public class TxIdTextField extends AnchorPane {
copyIcon = new Label();
copyIcon.setLayoutY(3);
copyIcon.getStyleClass().add("copy-icon");
Tooltip.install(copyIcon, new Tooltip("Copy transaction ID to clipboard"));
copyIcon.setTooltip(new Tooltip("Copy transaction ID to clipboard"));
AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY);
AnchorPane.setRightAnchor(copyIcon, 30.0);
Tooltip tooltip = new Tooltip("Open a blockchain explorer with that transactions ID");
blockExplorerIcon = new Label();
blockExplorerIcon.getStyleClass().add("external-link-icon");
blockExplorerIcon.setTooltip(tooltip);
AwesomeDude.setIcon(blockExplorerIcon, AwesomeIcon.EXTERNAL_LINK);
blockExplorerIcon.setMinWidth(20);
AnchorPane.setRightAnchor(blockExplorerIcon, 52.0);
@ -89,7 +92,7 @@ public class TxIdTextField extends AnchorPane {
textField = new TextField();
textField.setId("address-text-field");
textField.setEditable(false);
Tooltip.install(textField, new Tooltip("Open a blockchain explorer with that transactions ID"));
textField.setTooltip(tooltip);
AnchorPane.setRightAnchor(textField, 80.0);
AnchorPane.setLeftAnchor(textField, 0.0);
textField.focusTraversableProperty().set(focusTraversableProperty().get());

View file

@ -63,7 +63,7 @@ public class AliPayForm extends PaymentMethodForm {
updateFromInputs();
});
addLabelTextField(gridPane, ++gridRow, "Currency:", aliPayAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", aliPayAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
addAccountNameTextFieldWithAutoFillCheckBox();
}
@ -85,7 +85,7 @@ public class AliPayForm extends PaymentMethodForm {
addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(aliPayAccount.getPaymentMethod().getId()));
TextField field = addLabelTextField(gridPane, ++gridRow, "Account nr.:", aliPayAccount.getAccountNr()).second;
field.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Currency:", aliPayAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", aliPayAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
}

View file

@ -49,7 +49,7 @@ public class BlockChainForm extends PaymentMethodForm {
private ComboBox<TradeCurrency> currencyComboBox;
public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) {
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Address:", ((BlockChainAccountContractData) paymentAccountContractData).getAddress());
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Cryptocurrency address:", ((BlockChainAccountContractData) paymentAccountContractData).getAddress());
if (paymentAccountContractData instanceof BlockChainAccountContractData &&
((BlockChainAccountContractData) paymentAccountContractData).getPaymentId() != null)
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Payment ID:", ((BlockChainAccountContractData) paymentAccountContractData).getPaymentId());
@ -70,7 +70,7 @@ public class BlockChainForm extends PaymentMethodForm {
addTradeCurrencyComboBox();
currencyComboBox.setPrefWidth(250);
addressInputTextField = addLabelInputTextField(gridPane, ++gridRow, "Receiving altcoin address:").second;
addressInputTextField = addLabelInputTextField(gridPane, ++gridRow, "Cryptocurrency address:").second;
addressInputTextField.setValidator(altCoinAddressValidator);
addressInputTextField.textProperty().addListener((ov, oldValue, newValue) -> {
@ -98,9 +98,9 @@ public class BlockChainForm extends PaymentMethodForm {
gridRowFrom = gridRow;
addLabelTextField(gridPane, gridRow, "Account name:", blockChainAccount.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(blockChainAccount.getPaymentMethod().getId()));
TextField field = addLabelTextField(gridPane, ++gridRow, "Receiving altcoin address:", blockChainAccount.getAddress()).second;
TextField field = addLabelTextField(gridPane, ++gridRow, "Cryptocurrency address:", blockChainAccount.getAddress()).second;
field.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Crypto currency:", blockChainAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Crypto currency:", blockChainAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
}
@ -114,13 +114,13 @@ public class BlockChainForm extends PaymentMethodForm {
@Override
protected void addTradeCurrencyComboBox() {
currencyComboBox = addLabelComboBox(gridPane, ++gridRow, "Crypto currency:").second;
currencyComboBox.setPromptText("Select crypto currency");
currencyComboBox.setPromptText("Select cryptocurrency");
currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getSortedCryptoCurrencies()));
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 20));
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override

View file

@ -65,7 +65,7 @@ public abstract class PaymentMethodForm {
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override
@ -111,7 +111,7 @@ public abstract class PaymentMethodForm {
displayText = hours / 24 + " days";
addLabelTextField(gridPane, gridRow, "Trade period/end date:", displayText + " / " + dateFromBlocks);
addLabelTextField(gridPane, gridRow, "Max. allowed trade period / date:", displayText + " / " + dateFromBlocks);
}
protected void addAllowedPeriod() {
@ -126,7 +126,7 @@ public abstract class PaymentMethodForm {
displayText += " (Max. permitted period until the trade has to be completed)";
addLabelTextField(gridPane, ++gridRow, "Allowed trade period:", displayText);
addLabelTextField(gridPane, ++gridRow, "Max. allowed trade period:", displayText);
}
abstract protected void autoFillNameTextField();

View file

@ -64,7 +64,7 @@ public class PerfectMoneyForm extends PaymentMethodForm {
updateFromInputs();
});
addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
addAccountNameTextFieldWithAutoFillCheckBox();
}
@ -87,7 +87,7 @@ public class PerfectMoneyForm extends PaymentMethodForm {
addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(perfectMoneyAccount.getPaymentMethod().getId()));
TextField field = addLabelTextField(gridPane, ++gridRow, "Account nr.:", perfectMoneyAccount.getAccountNr()).second;
field.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
}

View file

@ -59,7 +59,7 @@ public class SepaForm extends PaymentMethodForm {
public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) {
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Account holder name:", ((SepaAccountContractData) paymentAccountContractData).getHolderName());
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Country of bank:", CountryUtil.getNameByCode(paymentAccountContractData.getCountryCode()));
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Country of bank:", CountryUtil.getNameAndCode(paymentAccountContractData.getCountryCode()));
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "IBAN:", ((SepaAccountContractData) paymentAccountContractData).getIban());
addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "BIC/SWIFT:", ((SepaAccountContractData) paymentAccountContractData).getBic());
return gridRow;
@ -107,7 +107,7 @@ public class SepaForm extends PaymentMethodForm {
countryComboBox.setConverter(new StringConverter<Country>() {
@Override
public String toString(Country country) {
return country.code + " (" + country.name + ")";
return country.name + " (" + country.code + ")";
}
@Override
@ -120,7 +120,7 @@ public class SepaForm extends PaymentMethodForm {
sepaAccount.setCountry(selectedItem);
TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(selectedItem.code);
sepaAccount.setSingleTradeCurrency(currency);
currencyTextField.setText("Currency: " + currency.getCodeAndName());
currencyTextField.setText("Currency: " + currency.getNameAndCode());
updateCountriesSelection(true, euroCountryCheckBoxes);
updateCountriesSelection(true, nonEuroCountryCheckBoxes);
updateFromInputs();
@ -138,7 +138,7 @@ public class SepaForm extends PaymentMethodForm {
sepaAccount.setCountry(country);
TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(country.code);
sepaAccount.setSingleTradeCurrency(currency);
currencyTextField.setText("Currency: " + currency.getCodeAndName());
currencyTextField.setText("Currency: " + currency.getNameAndCode());
}
updateFromInputs();
@ -256,7 +256,7 @@ public class SepaForm extends PaymentMethodForm {
TextField bicField = addLabelTextField(gridPane, ++gridRow, "BIC/SWIFT:", sepaAccount.getBic()).second;
bicField.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Location of Bank:", sepaAccount.getCountry().name);
addLabelTextField(gridPane, ++gridRow, "Currency:", sepaAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", sepaAccount.getSingleTradeCurrency().getNameAndCode());
String countries;
Tooltip tooltip = null;
if (CountryUtil.containsAllSepaEuroCountries(sepaAccount.getAcceptedCountryCodes())) {

View file

@ -72,7 +72,7 @@ public class SwishForm extends PaymentMethodForm {
updateFromInputs();
});
addLabelTextField(gridPane, ++gridRow, "Currency:", swishAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", swishAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
addAccountNameTextFieldWithAutoFillCheckBox();
}
@ -95,7 +95,7 @@ public class SwishForm extends PaymentMethodForm {
addLabelTextField(gridPane, ++gridRow, "Account holder name:", swishAccount.getHolderName());
TextField field = addLabelTextField(gridPane, ++gridRow, "Mobile nr.:", swishAccount.getMobileNr()).second;
field.setMouseTransparent(false);
addLabelTextField(gridPane, ++gridRow, "Currency:", swishAccount.getSingleTradeCurrency().getCodeAndName());
addLabelTextField(gridPane, ++gridRow, "Currency:", swishAccount.getSingleTradeCurrency().getNameAndCode());
addAllowedPeriod();
}

View file

@ -35,9 +35,9 @@ import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.tasks.buyer.*;
import io.bitsquare.trade.protocol.trade.tasks.offerer.*;
import io.bitsquare.trade.protocol.trade.tasks.seller.*;
import io.bitsquare.trade.protocol.trade.tasks.shared.BroadcastAfterLockTime;
import io.bitsquare.trade.protocol.trade.tasks.shared.CommitPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.shared.InitWaitPeriodForOpenDispute;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.protocol.trade.tasks.taker.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -99,7 +99,7 @@ public class DebugView extends InitializableView {
SignAndFinalizePayoutTx.class,
CommitPayoutTx.class,
SendPayoutTxFinalizedMessage.class,
SetupPayoutTxLockTimeReachedListener.class,
BroadcastAfterLockTime.class,
Boolean.class, /* used as seperator*/
@ -125,7 +125,7 @@ public class DebugView extends InitializableView {
ProcessPayoutTxFinalizedMessage.class,
CommitPayoutTx.class,
SetupPayoutTxLockTimeReachedListener.class,
BroadcastAfterLockTime.class,
Boolean.class /* used as seperator*/
)
);
@ -152,7 +152,7 @@ public class DebugView extends InitializableView {
SignAndFinalizePayoutTx.class,
CommitPayoutTx.class,
SendPayoutTxFinalizedMessage.class,
SetupPayoutTxLockTimeReachedListener.class,
BroadcastAfterLockTime.class,
Boolean.class, /* used as seperator*/
@ -177,7 +177,7 @@ public class DebugView extends InitializableView {
ProcessPayoutTxFinalizedMessage.class,
CommitPayoutTx.class,
SetupPayoutTxLockTimeReachedListener.class,
BroadcastAfterLockTime.class,
Boolean.class /* used as seperator*/
)
);

View file

@ -86,7 +86,7 @@ public class MarketsChartsView extends ActivatableViewAndModel<VBox, MarketsChar
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override

View file

@ -289,7 +289,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
model.onPaymentAccountSelected(paymentAccount);
} else {
currencyTextField.setText(paymentAccount.getSingleTradeCurrency().getCodeAndName());
currencyTextField.setText(paymentAccount.getSingleTradeCurrency().getNameAndCode());
model.onPaymentAccountSelected(paymentAccount);
model.onCurrencySelected(paymentAccount.getSingleTradeCurrency());
}
@ -588,7 +588,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override
@ -673,7 +673,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
balanceTextField = balanceTuple.second;
balanceTextField.setVisible(false);
Tuple3<Button, ProgressIndicator, Label> placeOfferTuple = addButtonWithStatus(gridPane, ++gridRow,
Tuple3<Button, ProgressIndicator, Label> placeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow,
BSResources.get("createOffer.fundsBox.placeOffer"));
placeOfferButton = placeOfferTuple.first;
placeOfferButton.setVisible(false);

View file

@ -89,7 +89,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override
@ -160,6 +160,8 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
currencyComboBox.setOnAction(e -> model.onSetTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()));
paymentMethodComboBox.setOnAction(e -> model.onSetPaymentMethod(paymentMethodComboBox.getSelectionModel().getSelectedItem()));
createOfferButton.setOnAction(e -> onCreateOffer());
priceColumn.textProperty().bind(createStringBinding(
() -> "Price in " + model.tradeCurrencyCode.get() + "/BTC", model.tradeCurrencyCode));
volumeColumn.textProperty().bind(createStringBinding(
() -> "Amount in " + model.tradeCurrencyCode.get() + " (Min.)", model.tradeCurrencyCode));
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());

View file

@ -317,7 +317,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
paymentMethodLabel.setManaged(!showComboBox);
if (!showComboBox)
paymentMethodTextField.setText(BSResources.get(model.getPaymentMethod().getId()));
currencyTextField.setText(model.getTradeCurrency().getCodeAndName());
currencyTextField.setText(model.getTradeCurrency().getNameAndCode());
buyLabel.setText(model.getDirectionLabel());
amountDescriptionLabel.setText(model.getAmountDescription());
amountRangeTextField.setText(model.getAmountRange());
@ -553,7 +553,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
balanceTextField = balanceTuple.second;
balanceTextField.setVisible(false);
Tuple3<Button, ProgressIndicator, Label> takeOfferTuple = addButtonWithStatus(gridPane, ++gridRow, BSResources.get("takeOffer.fundsBox.takeOffer"));
Tuple3<Button, ProgressIndicator, Label> takeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, BSResources.get("takeOffer.fundsBox.takeOffer"));
takeOfferButton = takeOfferTuple.first;
takeOfferButton.setVisible(false);
takeOfferButton.setOnAction(e -> onTakeOffer());

View file

@ -87,7 +87,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
tradeCurrencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getCodeAndName();
return tradeCurrency.getNameAndCode();
}
@Override

View file

@ -148,7 +148,8 @@ public class OfferDetailsPopup extends Popup {
addLabelTextField(gridPane, ++rowIndex, "Payment method:", BSResources.get(offer.getPaymentMethod().getId()));
rows = 3;
if (offer.getPaymentMethodCountryCode() != null)
String paymentMethodCountryCode = offer.getPaymentMethodCountryCode();
if (paymentMethodCountryCode != null)
rows++;
if (offer.getOfferFeePaymentTxID() != null)
rows++;
@ -161,8 +162,9 @@ public class OfferDetailsPopup extends Popup {
addLabelTextField(gridPane, rowIndex, "Offer ID:", offer.getId(), Layout.FIRST_ROW_AND_GROUP_DISTANCE);
addLabelTextField(gridPane, ++rowIndex, "Creation date:", formatter.formatDateTime(offer.getDate()));
if (offer.getPaymentMethodCountryCode() != null)
addLabelTextField(gridPane, ++rowIndex, "Offerers country of bank:", offer.getPaymentMethodCountryCode());
if (paymentMethodCountryCode != null)
addLabelTextField(gridPane, ++rowIndex, "Offerers country of bank:",
CountryUtil.getNameAndCode(paymentMethodCountryCode));
if (offer.getAcceptedCountryCodes() != null) {
String countries;
Tooltip tooltip = null;

View file

@ -129,6 +129,7 @@ public class FormBuilder {
return label;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Label + TextField
///////////////////////////////////////////////////////////////////////////////////////////
@ -672,9 +673,9 @@ public class FormBuilder {
// Button + ProgressIndicator + Label
///////////////////////////////////////////////////////////////////////////////////////////
public static Tuple3<Button, ProgressIndicator, Label> addButtonWithStatus(GridPane gridPane,
int rowIndex,
String buttonTitle) {
public static Tuple3<Button, ProgressIndicator, Label> addButtonWithStatusAfterGroup(GridPane gridPane,
int rowIndex,
String buttonTitle) {
return addButtonWithStatus(gridPane, rowIndex, buttonTitle, 15);
}

View file

@ -20,6 +20,7 @@ public class HttpClient implements Serializable {
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
if (connection.getResponseCode() == 200) {
return convertInputStreamToString(connection.getInputStream());

View file

@ -16,10 +16,7 @@ import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.crypto.PrefixedSealedAndSignedMessage;
import io.bitsquare.p2p.messaging.*;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.Broadcaster;
import io.bitsquare.p2p.peers.PeerExchangeManager;
import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.RequestDataManager;
import io.bitsquare.p2p.peers.*;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.P2PDataStorage;
@ -81,6 +78,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
private Subscription networkReadySubscription;
private boolean isBootstrapped;
private ChangeListener<Number> numOfBroadcastsChangeListener;
private MaintenanceManager maintenanceManager;
///////////////////////////////////////////////////////////////////////////////////////////
@ -128,6 +126,9 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
peerExchangeManager = new PeerExchangeManager(networkNode, peerManager, seedNodeAddresses);
maintenanceManager = new MaintenanceManager(networkNode, peerManager, seedNodeAddresses);
// We need to have both the initial data delivered and the hidden service published
networkReadyBinding = EasyBind.combine(hiddenServicePublished, preliminaryDataReceived,
(hiddenServicePublished, preliminaryDataReceived)
@ -170,6 +171,9 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
if (peerExchangeManager != null)
peerExchangeManager.shutDown();
if (maintenanceManager != null)
maintenanceManager.shutDown();
if (networkNode != null)
networkNode.shutDown(() -> {
shutDownResultHandlers.stream().forEach(Runnable::run);
@ -222,6 +226,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
hiddenServicePublished.set(true);
p2pServiceListeners.stream().forEach(SetupListener::onHiddenServicePublished);
maintenanceManager.start();
}
@Override

View file

@ -62,7 +62,7 @@ public class Connection implements MessageListener {
///////////////////////////////////////////////////////////////////////////////////////////
private final Socket socket;
private final MessageListener messageListener;
// private final MessageListener messageListener;
private final ConnectionListener connectionListener;
private final String portInfo;
private final String uid = UUID.randomUUID().toString();
@ -85,6 +85,8 @@ public class Connection implements MessageListener {
private PeerType peerType;
private final ObjectProperty<NodeAddress> nodeAddressProperty = new SimpleObjectProperty<>();
private List<Long> messageTimeStamps = new ArrayList<>();
private final CopyOnWriteArraySet<MessageListener> messageListeners = new CopyOnWriteArraySet<>();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -93,9 +95,11 @@ public class Connection implements MessageListener {
Connection(Socket socket, MessageListener messageListener, ConnectionListener connectionListener,
@Nullable NodeAddress peersNodeAddress) {
this.socket = socket;
this.messageListener = messageListener;
//this.messageListener = messageListener;
this.connectionListener = connectionListener;
addMessageListener(messageListener);
sharedModel = new SharedModel(this, socket);
if (socket.getLocalPort() == 0)
@ -143,7 +147,6 @@ public class Connection implements MessageListener {
// API
///////////////////////////////////////////////////////////////////////////////////////////
// Called form various threads
public void sendMessage(Message message) {
if (!stopped) {
@ -190,6 +193,19 @@ public class Connection implements MessageListener {
}
}
public void addMessageListener(MessageListener messageListener) {
boolean isNewEntry = messageListeners.add(messageListener);
if (!isNewEntry)
log.warn("Try to add a messageListener which was already added.");
}
public void removeMessageListener(MessageListener messageListener) {
boolean contained = messageListeners.remove(messageListener);
if (!contained)
log.debug("Try to remove a messageListener which was never added.\n\t" +
"That might happen because of async behaviour of CopyOnWriteArraySet");
}
@SuppressWarnings("unused")
public void reportIllegalRequest(RuleViolation ruleViolation) {
sharedModel.reportInvalidRequest(ruleViolation);
@ -224,11 +240,11 @@ public class Connection implements MessageListener {
// MessageListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
// Only get non - CloseConnectionMessage messages
// Only receive non - CloseConnectionMessage messages
@Override
public void onMessage(Message message, Connection connection) {
// connection is null as we get called from InputHandler, which does not hold a reference to Connection
UserThread.execute(() -> messageListener.onMessage(message, this));
checkArgument(connection.equals(this));
UserThread.execute(() -> messageListeners.stream().forEach(e -> e.onMessage(message, connection)));
}

View file

@ -0,0 +1,122 @@
package io.bitsquare.p2p.peers;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.messages.peers.GetPeersRequest;
import io.bitsquare.p2p.peers.messages.peers.GetPeersResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
public class GetPeersRequestHandler {
private static final Logger log = LoggerFactory.getLogger(GetPeersRequestHandler.class);
private static final long TIME_OUT_SEC = 20;
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Listener listener;
private Timer timeoutTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public GetPeersRequestHandler(NetworkNode networkNode, PeerManager peerManager, Listener listener) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.listener = listener;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void process(GetPeersRequest getPeersRequest, final Connection connection) {
Log.traceCall("getPeersRequest=" + getPeersRequest + "\n\tconnection=" + connection + "\n\tthis=" + this);
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
"The peers address must have been already set at the moment");
GetPeersResponse getPeersResponse = new GetPeersResponse(getPeersRequest.nonce,
peerManager.getConnectedPeersNonSeedNodes(connection.getPeersNodeAddressOptional().get()));
SettableFuture<Connection> future = networkNode.sendMessage(connection,
getPeersResponse);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("GetPeersResponse sent successfully");
cleanup();
listener.onComplete();
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getPeersRequest to " + connection +
" failed. That is expected if the peer is offline. getPeersRequest=" + getPeersRequest + "." +
"Exception: " + throwable.getMessage();
log.info(errorMessage);
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_FAILURE, connection);
}
});
checkArgument(timeoutTimer == null, "onGetPeersRequest must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getPeersResponse:" + getPeersResponse + " on connection:" + connection;
log.info(errorMessage + " / PeerExchangeHandshake=" +
GetPeersRequestHandler.this);
log.info("timeoutTimer called. this=" + this);
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
},
TIME_OUT_SEC, TimeUnit.SECONDS);
peerManager.addToReportedPeers(getPeersRequest.reportedPeers, connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void handleFault(String errorMessage, CloseConnectionReason sendMsgFailure, Connection connection) {
// TODO retry
cleanup();
peerManager.shutDownConnection(connection, sendMsgFailure);
listener.onFault(errorMessage, connection);
}
private void cleanup() {
if (timeoutTimer != null) {
timeoutTimer.cancel();
timeoutTimer = null;
}
}
}

View file

@ -117,11 +117,8 @@ public class MaintenanceHandshake implements MessageListener {
Log.traceCall("getPeersRequest=" + getPeersRequest + "\n\tconnection=" + connection + "\n\tthis=" + this);
HashSet<ReportedPeer> reportedPeers = getPeersRequest.reportedPeers;
/* StringBuilder result = new StringBuilder("Received peers:");
reportedPeers.stream().forEach(e -> result.append("\n\t").append(e));
log.trace(result.toString());*/
log.trace("reportedPeers.size=" + reportedPeers.size());
peerManager.printReportedPeers(reportedPeers);
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
"The peers address must have been already set at the moment");

View file

@ -7,7 +7,6 @@ import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.messages.peers.GetPeersRequest;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -18,16 +17,17 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class MaintenanceManager implements MessageListener, ConnectionListener {
private static final Logger log = LoggerFactory.getLogger(MaintenanceManager.class);
private static final int MAINTENANCE_DELAY_SEC = 5 * 60;
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Set<NodeAddress> seedNodeAddresses;
private final ScheduledThreadPoolExecutor executor;
private final Map<NodeAddress, PeerExchangeHandshake> peerExchangeHandshakeMap = new HashMap<>();
private ScheduledThreadPoolExecutor executor;
private final Map<NodeAddress, PeerExchangeHandler> peerExchangeHandshakeMap = new HashMap<>();
private Timer connectToMorePeersTimer, maintainConnectionsTimer;
private boolean shutDownInProgress;
@ -42,7 +42,6 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
checkArgument(!seedNodeAddresses.isEmpty(), "seedNodeAddresses must not be empty");
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
executor = Utilities.getScheduledThreadPoolExecutor("PeerExchangeManager", 1, 10, 5);
networkNode.addMessageListener(this);
}
@ -53,8 +52,10 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
networkNode.removeMessageListener(this);
stopConnectToMorePeersTimer();
stopMaintainConnectionsTimer();
peerExchangeHandshakeMap.values().stream().forEach(PeerExchangeHandshake::closeHandshake);
MoreExecutors.shutdownAndAwaitTermination(executor, 500, TimeUnit.MILLISECONDS);
peerExchangeHandshakeMap.values().stream().forEach(PeerExchangeHandler::cleanup);
if (executor != null)
MoreExecutors.shutdownAndAwaitTermination(executor, 100, TimeUnit.MILLISECONDS);
}
@ -62,19 +63,15 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void requestReportedPeersFromSeedNodes(NodeAddress nodeAddress) {
checkNotNull(networkNode.getNodeAddress(), "My node address must not be null at requestReportedPeers");
ArrayList<NodeAddress> remainingNodeAddresses = new ArrayList<>(seedNodeAddresses);
remainingNodeAddresses.remove(nodeAddress);
Collections.shuffle(remainingNodeAddresses);
requestReportedPeers(nodeAddress, remainingNodeAddresses);
int delay = new Random().nextInt(60) + 60 * 3; // 3-4 min
executor.scheduleAtFixedRate(() -> UserThread.execute(this::maintainConnections),
delay, delay, TimeUnit.SECONDS);
public void start() {
if (executor == null) {
executor = Utilities.getScheduledThreadPoolExecutor("MaintenanceManager", 1, 2, 5);
int delay = new Random().nextInt(120) + MAINTENANCE_DELAY_SEC; // add 1-2 min. randomness
executor.scheduleAtFixedRate(() -> UserThread.execute(this::maintainConnections),
delay, delay, TimeUnit.SECONDS);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@ -85,13 +82,13 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
@Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
// We use a timer to throttle if we get a series of disconnects
/* // We use a timer to throttle if we get a series of disconnects
// The more connections we have the more relaxed we are with a checkConnections
stopMaintainConnectionsTimer();
int size = networkNode.getAllConnections().size();
int delay = 10 + 2 * size * size; // 12 sec - 210 sec (3.5 min)
maintainConnectionsTimer = UserThread.runAfter(this::maintainConnections,
delay, TimeUnit.SECONDS);
delay, TimeUnit.SECONDS);*/
}
@Override
@ -105,7 +102,7 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersRequest) {
/* if (message instanceof GetPeersRequest) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
PeerExchangeHandshake peerExchangeHandshake = new PeerExchangeHandshake(networkNode,
peerManager,
@ -123,7 +120,7 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
}
});
peerExchangeHandshake.onGetPeersRequest((GetPeersRequest) message, connection);
}
}*/
}
@ -134,9 +131,9 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
private void requestReportedPeers(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) {
Log.traceCall("nodeAddress=" + nodeAddress);
if (!peerExchangeHandshakeMap.containsKey(nodeAddress)) {
PeerExchangeHandshake peerExchangeHandshake = new PeerExchangeHandshake(networkNode,
PeerExchangeHandler peerExchangeHandler = new PeerExchangeHandler(networkNode,
peerManager,
new PeerExchangeHandshake.Listener() {
new PeerExchangeHandler.Listener() {
@Override
public void onComplete() {
log.trace("PeerExchangeHandshake of outbound connection complete. nodeAddress={}", nodeAddress);
@ -167,8 +164,8 @@ public class MaintenanceManager implements MessageListener, ConnectionListener {
}
}
});
peerExchangeHandshakeMap.put(nodeAddress, peerExchangeHandshake);
peerExchangeHandshake.requestConnectedPeers(nodeAddress);
peerExchangeHandshakeMap.put(nodeAddress, peerExchangeHandler);
peerExchangeHandler.requestConnectedPeers(nodeAddress);
} else {
//TODO check when that happens
log.warn("We have started already a peerExchangeHandshake. " +

View file

@ -0,0 +1,158 @@
package io.bitsquare.p2p.peers;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.messages.peers.GetPeersRequest;
import io.bitsquare.p2p.peers.messages.peers.GetPeersResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Random;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class PeerExchangeHandler implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(PeerExchangeHandler.class);
private static final long TIME_OUT_SEC = 20;
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, @Nullable Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Listener listener;
private final long nonce = new Random().nextLong();
private Timer timeoutTimer;
public Connection connection;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public PeerExchangeHandler(NetworkNode networkNode, PeerManager peerManager, Listener listener) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.listener = listener;
//networkNode.addMessageListener(this);
}
public void cleanup() {
if (connection != null)
connection.removeMessageListener(this);
if (timeoutTimer != null) {
timeoutTimer.cancel();
timeoutTimer = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void requestConnectedPeers(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this);
checkNotNull(networkNode.getNodeAddress(), "PeerExchangeHandshake.requestReportedPeers: My node address must " +
"not be null at requestReportedPeers");
GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, peerManager.getConnectedPeersNonSeedNodes(nodeAddress));
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getPeersRequest);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
PeerExchangeHandler.this.connection = connection;
connection.addMessageListener(PeerExchangeHandler.this);
log.trace("Send " + getPeersRequest + " to " + nodeAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getPeersRequest to " + nodeAddress +
" failed. That is expected if the peer is offline.\n\tgetPeersRequest=" + getPeersRequest +
".\n\tException=" + throwable.getMessage();
log.info(errorMessage);
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_FAILURE, nodeAddress);
}
});
checkArgument(timeoutTimer == null, "requestReportedPeers must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getPeersRequest:" + getPeersRequest + " for nodeAddress:" + nodeAddress;
log.info(errorMessage + " / PeerExchangeHandshake=" +
PeerExchangeHandler.this);
log.info("timeoutTimer called on " + this);
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, nodeAddress);
},
TIME_OUT_SEC, TimeUnit.SECONDS);
}
///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersResponse) {
GetPeersResponse getPeersResponse = (GetPeersResponse) message;
// Check if the response is for our request
if (getPeersResponse.requestNonce == nonce) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
Log.traceCall("this=" + this);
peerManager.addToReportedPeers(getPeersResponse.reportedPeers, connection);
cleanup();
listener.onComplete();
} else {
log.trace("Nonce not matching. That message is not intended for us.\n\t" +
"We drop that message. nonce={} / requestNonce={}",
nonce, getPeersResponse.requestNonce);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void handleFault(String errorMessage, CloseConnectionReason sendMsgFailure, NodeAddress nodeAddress) {
// TODO retry
cleanup();
if (connection == null)
peerManager.shutDownConnection(nodeAddress, sendMsgFailure);
else
peerManager.shutDownConnection(connection, sendMsgFailure);
listener.onFault(errorMessage, connection);
}
}

View file

@ -1,219 +0,0 @@
package io.bitsquare.p2p.peers;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.messages.peers.GetPeersRequest;
import io.bitsquare.p2p.peers.messages.peers.GetPeersResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Random;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class PeerExchangeHandshake implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(PeerExchangeHandshake.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, @Nullable Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Listener listener;
private final long nonce = new Random().nextLong();
private Timer timeoutTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public PeerExchangeHandshake(NetworkNode networkNode, PeerManager peerManager, Listener listener) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.listener = listener;
networkNode.addMessageListener(this);
}
public void closeHandshake() {
networkNode.removeMessageListener(this);
stopTimeoutTimer();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void requestConnectedPeers(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this);
checkNotNull(networkNode.getNodeAddress(), "PeerExchangeHandshake.requestReportedPeers: My node address must " +
"not be null at requestReportedPeers");
GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, getConnectedPeers(nodeAddress));
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getPeersRequest);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("Send " + getPeersRequest + " to " + nodeAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getPeersRequest to " + nodeAddress +
" failed. That is expected if the peer is offline.\n\tgetPeersRequest=" + getPeersRequest +
".\n\tException=" + throwable.getMessage();
log.info(errorMessage);
peerManager.shutDownConnection(nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
closeHandshake();
listener.onFault(errorMessage, null);
}
});
checkArgument(timeoutTimer == null, "requestReportedPeers must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getPeersRequest:" + getPeersRequest + " for nodeAddress:" + nodeAddress;
log.info(errorMessage + " / PeerExchangeHandshake=" +
PeerExchangeHandshake.this);
log.info("timeoutTimer called on " + this);
peerManager.shutDownConnection(nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
closeHandshake();
listener.onFault(errorMessage, null);
},
20, TimeUnit.SECONDS);
}
public void onGetPeersRequest(GetPeersRequest getPeersRequest, final Connection connection) {
Log.traceCall("getPeersRequest=" + getPeersRequest + "\n\tconnection=" + connection + "\n\tthis=" + this);
HashSet<ReportedPeer> reportedPeers = getPeersRequest.reportedPeers;
/* StringBuilder result = new StringBuilder("Received peers:");
reportedPeers.stream().forEach(e -> result.append("\n\t").append(e));
log.trace(result.toString());*/
log.trace("reportedPeers.size=" + reportedPeers.size());
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
"The peers address must have been already set at the moment");
GetPeersResponse getPeersResponse = new GetPeersResponse(getPeersRequest.nonce,
getConnectedPeers(connection.getPeersNodeAddressOptional().get()));
SettableFuture<Connection> future = networkNode.sendMessage(connection,
getPeersResponse);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("GetPeersResponse sent successfully");
closeHandshake();
listener.onComplete();
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getPeersRequest to " + connection +
" failed. That is expected if the peer is offline. getPeersRequest=" + getPeersRequest + "." +
"Exception: " + throwable.getMessage();
log.info(errorMessage);
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE);
closeHandshake();
listener.onFault(errorMessage, connection);
}
});
checkArgument(timeoutTimer == null, "onGetPeersRequest must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getPeersResponse:" + getPeersResponse + " on connection:" + connection;
log.info(errorMessage + " / PeerExchangeHandshake=" +
PeerExchangeHandshake.this);
log.info("timeoutTimer called. this=" + this);
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_TIMEOUT);
closeHandshake();
listener.onFault(errorMessage, connection);
},
20, TimeUnit.SECONDS);
peerManager.addToReportedPeers(reportedPeers, connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersResponse) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
Log.traceCall("this=" + this);
GetPeersResponse getPeersResponse = (GetPeersResponse) message;
if (getPeersResponse.requestNonce == nonce) {
stopTimeoutTimer();
HashSet<ReportedPeer> reportedPeers = getPeersResponse.reportedPeers;
StringBuilder result = new StringBuilder("Received peers:");
reportedPeers.stream().forEach(e -> result.append("\n\t").append(e));
log.trace(result.toString());
peerManager.addToReportedPeers(reportedPeers, connection);
closeHandshake();
listener.onComplete();
} else {
log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled handshake " +
"(timeout causes connection close but peer might have sent a msg before connection " +
"was closed).\n\tWe drop that message. nonce={} / requestNonce={}",
nonce, getPeersResponse.requestNonce);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private HashSet<ReportedPeer> getConnectedPeers(NodeAddress receiverNodeAddress) {
return new HashSet<>(peerManager.getConnectedPeers().stream()
.filter(e -> !peerManager.isSeedNode(e) &&
!e.nodeAddress.equals(receiverNodeAddress)
)
.collect(Collectors.toSet()));
}
private void stopTimeoutTimer() {
if (timeoutTimer != null) {
timeoutTimer.cancel();
timeoutTimer = null;
}
}
}

View file

@ -1,16 +1,20 @@
package io.bitsquare.p2p.peers;
import com.google.common.util.concurrent.MoreExecutors;
import io.bitsquare.app.Log;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.messages.peers.GetPeersRequest;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@ -19,13 +23,17 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class PeerExchangeManager implements MessageListener, ConnectionListener {
private static final Logger log = LoggerFactory.getLogger(PeerExchangeManager.class);
private static final long RETRY_DELAY_SEC = 60;
private static final long MAINTENANCE_DELAY_SEC = 5;
private final NetworkNode networkNode;
private final PeerManager peerManager;
private final Set<NodeAddress> seedNodeAddresses;
private final Map<NodeAddress, PeerExchangeHandshake> peerExchangeHandshakeMap = new HashMap<>();
private final Map<NodeAddress, PeerExchangeHandler> peerExchangeHandlerMap = new HashMap<>();
private Timer connectToMorePeersTimer;
private boolean shutDownInProgress;
private ScheduledThreadPoolExecutor executor;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -38,15 +46,19 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
networkNode.addMessageListener(this);
networkNode.addConnectionListener(this);
}
public void shutDown() {
Log.traceCall();
shutDownInProgress = true;
networkNode.removeMessageListener(this);
networkNode.removeConnectionListener(this);
stopConnectToMorePeersTimer();
peerExchangeHandshakeMap.values().stream().forEach(PeerExchangeHandshake::closeHandshake);
peerExchangeHandlerMap.values().stream().forEach(PeerExchangeHandler::cleanup);
if (executor != null)
MoreExecutors.shutdownAndAwaitTermination(executor, 100, TimeUnit.MILLISECONDS);
}
@ -60,8 +72,13 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
remainingNodeAddresses.remove(nodeAddress);
Collections.shuffle(remainingNodeAddresses);
requestReportedPeers(nodeAddress, remainingNodeAddresses);
}
if (executor == null) {
executor = Utilities.getScheduledThreadPoolExecutor("PeerExchangeManager", 1, 2, 5);
executor.scheduleAtFixedRate(() -> UserThread.execute(this::requestAgain),
MAINTENANCE_DELAY_SEC, MAINTENANCE_DELAY_SEC, TimeUnit.SECONDS);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation
@ -73,6 +90,12 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
@Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
if (connectToMorePeersTimer == null)
connectToMorePeersTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from onDisconnect code path");
stopConnectToMorePeersTimer();
requestWithAvailablePeers();
}, RETRY_DELAY_SEC);
}
@Override
@ -88,41 +111,41 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersRequest) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
PeerExchangeHandshake peerExchangeHandshake = new PeerExchangeHandshake(networkNode,
GetPeersRequestHandler getPeersRequestHandler = new GetPeersRequestHandler(networkNode,
peerManager,
new PeerExchangeHandshake.Listener() {
new GetPeersRequestHandler.Listener() {
@Override
public void onComplete() {
log.trace("PeerExchangeHandshake of inbound connection complete.\n\tConnection={}", connection);
}
@Override
public void onFault(String errorMessage, @Nullable Connection connection) {
public void onFault(String errorMessage, Connection connection) {
log.trace("PeerExchangeHandshake of outbound connection failed.\n\terrorMessage={}\n\t" +
"connection={}", errorMessage, connection);
peerManager.handleConnectionFault(connection);
}
});
peerExchangeHandshake.onGetPeersRequest((GetPeersRequest) message, connection);
getPeersRequestHandler.process((GetPeersRequest) message, connection);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
// Request
///////////////////////////////////////////////////////////////////////////////////////////
private void requestReportedPeers(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) {
Log.traceCall("nodeAddress=" + nodeAddress);
if (!peerExchangeHandshakeMap.containsKey(nodeAddress)) {
PeerExchangeHandshake peerExchangeHandshake = new PeerExchangeHandshake(networkNode,
if (!peerExchangeHandlerMap.containsKey(nodeAddress)) {
PeerExchangeHandler peerExchangeHandler = new PeerExchangeHandler(networkNode,
peerManager,
new PeerExchangeHandshake.Listener() {
new PeerExchangeHandler.Listener() {
@Override
public void onComplete() {
log.trace("PeerExchangeHandshake of outbound connection complete. nodeAddress={}", nodeAddress);
peerExchangeHandshakeMap.remove(nodeAddress);
connectToMorePeers();
peerExchangeHandlerMap.remove(nodeAddress);
requestWithAvailablePeers();
}
@Override
@ -130,26 +153,36 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
log.trace("PeerExchangeHandshake of outbound connection failed.\n\terrorMessage={}\n\t" +
"nodeAddress={}", errorMessage, nodeAddress);
peerExchangeHandshakeMap.remove(nodeAddress);
peerExchangeHandlerMap.remove(nodeAddress);
peerManager.handleConnectionFault(nodeAddress, connection);
if (!shutDownInProgress) {
if (!remainingNodeAddresses.isEmpty()) {
log.info("There are remaining nodes available for requesting peers. " +
"We will try getReportedPeers again.");
requestReportedPeersFromRandomPeer(remainingNodeAddresses);
if (!peerManager.hasSufficientConnections()) {
log.info("There are remaining nodes available for requesting peers. " +
"We will try getReportedPeers again.");
NodeAddress nextCandidate = remainingNodeAddresses.get(new Random().nextInt(remainingNodeAddresses.size()));
remainingNodeAddresses.remove(nextCandidate);
requestReportedPeers(nextCandidate, remainingNodeAddresses);
} else {
// That path will rarely be reached
log.info("We have already sufficient connections.");
}
} else {
log.info("There is no remaining node available for requesting peers. " +
"That is expected if no other node is online.\n\t" +
"We will try again after a random pause.");
"We will try again after a pause.");
if (connectToMorePeersTimer == null)
connectToMorePeersTimer = UserThread.runAfterRandomDelay(
PeerExchangeManager.this::connectToMorePeers, 20, 30);
connectToMorePeersTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from requestReportedPeers code path");
stopConnectToMorePeersTimer();
requestWithAvailablePeers();
}, RETRY_DELAY_SEC);
}
}
}
});
peerExchangeHandshakeMap.put(nodeAddress, peerExchangeHandshake);
peerExchangeHandshake.requestConnectedPeers(nodeAddress);
peerExchangeHandlerMap.put(nodeAddress, peerExchangeHandler);
peerExchangeHandler.requestConnectedPeers(nodeAddress);
} else {
//TODO check when that happens
log.warn("We have started already a peerExchangeHandshake. " +
@ -158,59 +191,86 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
}
}
private void connectToMorePeers() {
private void requestWithAvailablePeers() {
Log.traceCall();
stopConnectToMorePeersTimer();
if (!peerManager.hasSufficientConnections()) {
// We create a new list of not connected candidates
// 1. reported sorted by most recent lastActivityDate
// 2. persisted sorted by most recent lastActivityDate
// 3. seenNodes
List<NodeAddress> list = new ArrayList<>(getFilteredAndSortedList(peerManager.getReportedPeers(), new ArrayList<>()));
list.addAll(getFilteredAndSortedList(peerManager.getPersistedPeers(), list));
ArrayList<NodeAddress> seedNodeAddresses = new ArrayList<>(this.seedNodeAddresses);
Collections.shuffle(seedNodeAddresses);
list.addAll(seedNodeAddresses.stream()
.filter(e -> !list.contains(e) &&
!peerManager.isSelf(e) &&
!peerManager.isConfirmed(e))
.collect(Collectors.toSet()));
log.info("Sorted and filtered list: list.size()=" + list.size());
log.trace("Sorted and filtered list: list=" + list);
// 1. reported shuffled peers
// 2. persisted shuffled peers
// 3. Add as last shuffled seedNodes (least priority)
List<NodeAddress> list = getFilteredNonSeedNodeList(getNodeAddresses(peerManager.getReportedPeers()), new ArrayList<>());
Collections.shuffle(list);
List<NodeAddress> filteredPersistedPeers = getFilteredNonSeedNodeList(getNodeAddresses(peerManager.getPersistedPeers()), list);
Collections.shuffle(filteredPersistedPeers);
list.addAll(filteredPersistedPeers);
List<NodeAddress> filteredSeedNodeAddresses = getFilteredList(new ArrayList<>(seedNodeAddresses), list);
Collections.shuffle(filteredSeedNodeAddresses);
list.addAll(filteredSeedNodeAddresses);
log.info("Number of peers in list for connectToMorePeers: {}", list.size());
log.trace("Filtered connectToMorePeers list: list=" + list);
if (!list.isEmpty()) {
// Dont shuffle as we want the seed nodes at the last entries
NodeAddress nextCandidate = list.get(0);
list.remove(nextCandidate);
requestReportedPeers(nextCandidate, list);
} else {
log.info("No more peers are available for requestReportedPeers.");
log.info("No more peers are available for requestReportedPeers. We will try again after a pause.");
if (connectToMorePeersTimer == null)
connectToMorePeersTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from requestWithAvailablePeers code path");
stopConnectToMorePeersTimer();
requestWithAvailablePeers();
}, RETRY_DELAY_SEC);
}
} else {
log.info("We have already sufficient connections.");
}
}
// sorted by most recent lastActivityDate
private List<NodeAddress> getFilteredAndSortedList(Set<ReportedPeer> set, List<NodeAddress> list) {
return set.stream()
.filter(e -> !list.contains(e.nodeAddress) &&
!peerManager.isSeedNode(e) &&
!peerManager.isSelf(e) &&
!peerManager.isConfirmed(e))
.collect(Collectors.toList())
.stream()
.filter(e -> e.lastActivityDate != null)
.sorted((o1, o2) -> o2.lastActivityDate.compareTo(o1.lastActivityDate))
///////////////////////////////////////////////////////////////////////////////////////////
// Maintenance
///////////////////////////////////////////////////////////////////////////////////////////
private void requestAgain() {
checkNotNull(networkNode.getNodeAddress(), "My node address must not be null at sendUpdateRequest");
Set<NodeAddress> candidates = new HashSet<>(getNodeAddresses(peerManager.getReportedPeers()));
candidates.addAll(getNodeAddresses(peerManager.getPersistedPeers()));
candidates.addAll(seedNodeAddresses);
candidates.remove(networkNode.getNodeAddress());
ArrayList<NodeAddress> list = new ArrayList<>(candidates);
Collections.shuffle(list);
NodeAddress candidate = list.remove(0);
requestReportedPeers(candidate, list);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
private List<NodeAddress> getNodeAddresses(Collection<ReportedPeer> collection) {
return collection.stream()
.map(e -> e.nodeAddress)
.collect(Collectors.toList());
}
private void requestReportedPeersFromRandomPeer(List<NodeAddress> remainingNodeAddresses) {
NodeAddress nextCandidate = remainingNodeAddresses.get(new Random().nextInt(remainingNodeAddresses.size()));
remainingNodeAddresses.remove(nextCandidate);
requestReportedPeers(nextCandidate, remainingNodeAddresses);
private List<NodeAddress> getFilteredList(Collection<NodeAddress> collection, List<NodeAddress> list) {
return collection.stream()
.filter(e -> !list.contains(e) &&
!peerManager.isSelf(e) &&
!peerManager.isConfirmed(e))
.collect(Collectors.toList());
}
private List<NodeAddress> getFilteredNonSeedNodeList(Collection<NodeAddress> collection, List<NodeAddress> list) {
return getFilteredList(collection, list).stream()
.filter(e -> !peerManager.isSeedNode(e))
.collect(Collectors.toList());
}
private void stopConnectToMorePeersTimer() {

View file

@ -15,7 +15,6 @@ import javax.annotation.Nullable;
import java.io.File;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
@ -32,6 +31,7 @@ public class PeerManager implements ConnectionListener, MessageListener {
private static int MAX_CONNECTIONS_EXTENDED_1;
private static int MAX_CONNECTIONS_EXTENDED_2;
private static int MAX_CONNECTIONS_EXTENDED_3;
private boolean printReportedPeersDetails = true;
public static void setMaxConnections(int maxConnections) {
MAX_CONNECTIONS = maxConnections;
@ -271,87 +271,48 @@ public class PeerManager implements ConnectionListener, MessageListener {
}
public void addToReportedPeers(HashSet<ReportedPeer> reportedPeersToAdd, Connection connection) {
// we disconnect misbehaving nodes trying to send too many peers
// reported peers include the connected peers which is normally max. 10 but we give some headroom
// for safety
if (reportedPeersToAdd.size() > (MAX_REPORTED_PEERS + PeerManager.MIN_CONNECTIONS * 3)) {
// Will trigger a shutdown after 2nd time sending too much
connection.reportIllegalRequest(RuleViolation.TOO_MANY_REPORTED_PEERS_SENT);
} else {
// In case we have one of the peers already we adjust the lastActivityDate by adjusting the date to the mid
// of the lastActivityDate of our already stored peer and the reported one
Map<ReportedPeer, ReportedPeer> reportedPeersMap = reportedPeers.stream()
.collect(Collectors.toMap(e -> e, Function.identity()));
HashSet<ReportedPeer> adjustedReportedPeers = new HashSet<>();
reportedPeersToAdd.stream()
.filter(e -> e.nodeAddress != null &&
!e.nodeAddress.equals(networkNode.getNodeAddress()) &&
!getConnectedPeers().contains(e))
.forEach(e -> {
if (reportedPeersMap.containsKey(e)) {
if (e.lastActivityDate != null && reportedPeersMap.get(e).lastActivityDate != null) {
long adjustedTime = (e.lastActivityDate.getTime() +
reportedPeersMap.get(e).lastActivityDate.getTime()) / 2;
adjustedReportedPeers.add(new ReportedPeer(e.nodeAddress,
new Date(adjustedTime)));
} else if (e.lastActivityDate == null) {
adjustedReportedPeers.add(reportedPeersMap.get(e));
} else if (reportedPeersMap.get(e).lastActivityDate == null) {
adjustedReportedPeers.add(e);
}
} else {
adjustedReportedPeers.add(e);
}
});
reportedPeers.addAll(adjustedReportedPeers);
printReportedPeers(reportedPeersToAdd);
// We check if the reported msg is not violating our rules
if (reportedPeersToAdd.size() <= (MAX_REPORTED_PEERS + PeerManager.MAX_CONNECTIONS_EXTENDED_3 + 10)) {
reportedPeers.addAll(reportedPeersToAdd);
purgeReportedPeersIfExceeds();
persistedPeers.addAll(reportedPeersToAdd);
persistedPeers.addAll(new HashSet<>(getConnectedPeers()));
// We remove if we exceeds MAX_PERSISTED_PEERS limit
int toRemove = persistedPeers.size() - MAX_PERSISTED_PEERS;
if (toRemove > 0) {
int toRemove1 = toRemove / 2;
if (toRemove1 > 0) {
// we remove the first half randomly to avoid attack vectors with lastActivityDate
List<ReportedPeer> list = new ArrayList<>(persistedPeers);
for (int i = 0; i < toRemove1; i++) {
persistedPeers.remove(list.get(i));
}
int toRemove2 = toRemove - toRemove1;
if (toRemove2 > 0) {
// now we remove second half with a list sorted by oldest lastActivityDate
list = new ArrayList<>(persistedPeers);
list = list.stream().filter(e -> e.lastActivityDate != null).collect(Collectors.toList());
list.sort((o1, o2) -> o1.lastActivityDate.compareTo(o2.lastActivityDate));
for (int i = 0; i < toRemove2; i++) {
persistedPeers.remove(list.get(i));
}
}
}
}
purgePersistedPeersIfExceeds();
if (dbStorage != null)
dbStorage.queueUpForSave(persistedPeers, 2000);
}
printReportedPeers();
printReportedPeers();
} else {
// If a node is trying to send too many peers we treat it as rule violation.
// Reported peers include the connected peers. We use the max value and give some extra headroom.
// Will trigger a shutdown after 2nd time sending too much
connection.reportIllegalRequest(RuleViolation.TOO_MANY_REPORTED_PEERS_SENT);
}
}
private void printReportedPeers() {
if (!reportedPeers.isEmpty()) {
StringBuilder result = new StringBuilder("\n\n------------------------------------------------------------\n" +
"Reported peers:");
reportedPeers.stream().forEach(e -> result.append("\n").append(e));
result.append("\n------------------------------------------------------------\n");
//log.trace(result.toString());
log.info("Number of reported peers: {}", reportedPeers.size());
if (printReportedPeersDetails) {
StringBuilder result = new StringBuilder("\n\n------------------------------------------------------------\n" +
"Collected reported peers:");
reportedPeers.stream().forEach(e -> result.append("\n").append(e));
result.append("\n------------------------------------------------------------\n");
log.info(result.toString());
}
log.info("Number of collected reported peers: {}", reportedPeers.size());
}
}
public void printReportedPeers(HashSet<ReportedPeer> reportedPeers) {
if (printReportedPeersDetails) {
StringBuilder result = new StringBuilder("We received now reportedPeers:");
reportedPeers.stream().forEach(e -> result.append("\n\t").append(e));
log.info(result.toString());
}
log.info("Number of new arrived reported peers: {}", reportedPeers.size());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Persisted peers
@ -455,6 +416,7 @@ public class PeerManager implements ConnectionListener, MessageListener {
return isConfirmed(reportedPeer.nodeAddress);
}
// Checks if that connection has the peers node address
public boolean isConfirmed(NodeAddress nodeAddress) {
return networkNode.getNodeAddressesOfConfirmedConnections().contains(nodeAddress);
}
@ -480,22 +442,41 @@ public class PeerManager implements ConnectionListener, MessageListener {
private void purgeReportedPeersIfExceeds() {
Log.traceCall();
int size = getReportedPeers().size();
if (size > MAX_REPORTED_PEERS) {
log.trace("We have more then {} reported peers. size={}. " +
"We remove random peers from the reported peers list.", MAX_REPORTED_PEERS, size);
int diff = size - MAX_REPORTED_PEERS;
int limit = MAX_REPORTED_PEERS - MAX_CONNECTIONS_EXTENDED_3;
if (size > limit) {
log.trace("We have already {} reported peers which exceeds our limit of {}." +
"We remove random peers from the reported peers list.", size, limit);
int diff = size - limit;
List<ReportedPeer> list = new ArrayList<>(getReportedPeers());
// we dont use sorting by lastActivityDate to avoid attack vectors and keep it more random
for (int i = 0; i < diff; i++) {
ReportedPeer toRemove = getAndRemoveRandomReportedPeer(list);
removeReportedPeer(toRemove);
removePersistedPeer(toRemove);
}
} else {
log.trace("No need to purge reported peers.\n\tWe don't have more then {} reported peers yet.", MAX_REPORTED_PEERS);
}
}
private void purgePersistedPeersIfExceeds() {
Log.traceCall();
int size = getPersistedPeers().size();
int limit = MAX_REPORTED_PEERS - MAX_CONNECTIONS_EXTENDED_3;
if (size > limit) {
log.trace("We have already {} persisted peers which exceeds our limit of {}." +
"We remove random peers from the persisted peers list.", size, limit);
int diff = size - limit;
List<ReportedPeer> list = new ArrayList<>(getReportedPeers());
// we dont use sorting by lastActivityDate to avoid attack vectors and keep it more random
for (int i = 0; i < diff; i++) {
ReportedPeer toRemove = getAndRemoveRandomReportedPeer(list);
removePersistedPeer(toRemove);
}
} else {
log.trace("No need to purge persisted peers.\n\tWe don't have more then {} persisted peers yet.", MAX_REPORTED_PEERS);
}
}
private ReportedPeer getAndRemoveRandomReportedPeer(List<ReportedPeer> list) {
checkArgument(!list.isEmpty(), "List must not be empty");
return list.remove(new Random().nextInt(list.size()));
@ -509,6 +490,18 @@ public class PeerManager implements ConnectionListener, MessageListener {
.collect(Collectors.toSet());
}
public HashSet<ReportedPeer> getConnectedPeersNonSeedNodes() {
return new HashSet<>(getConnectedPeers().stream()
.filter(e -> !isSeedNode(e))
.collect(Collectors.toSet()));
}
public HashSet<ReportedPeer> getConnectedPeersNonSeedNodes(NodeAddress excludedNodeAddress) {
return new HashSet<>(getConnectedPeersNonSeedNodes().stream()
.filter(e -> !e.nodeAddress.equals(excludedNodeAddress))
.collect(Collectors.toSet()));
}
private void stopCheckMaxConnectionsTimer() {
if (checkMaxConnectionsTimer != null) {
checkMaxConnectionsTimer.cancel();

View file

@ -22,6 +22,9 @@ import static com.google.common.base.Preconditions.checkArgument;
public class RequestDataManager implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(RequestDataManager.class);
private static final long RETRY_DELAY_SEC = 10;
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
@ -193,7 +196,7 @@ public class RequestDataManager implements MessageListener {
// try again after a pause
stopRequestDataTimer();
requestDataTimer = UserThread.runAfterRandomDelay(() -> {
requestDataTimer = UserThread.runAfter(() -> {
log.trace("requestDataAfterDelayTimer called");
// We want to keep it sorted but avoid duplicates
// We don't filter out already established connections for seed nodes as it might be that
@ -208,7 +211,7 @@ public class RequestDataManager implements MessageListener {
list.remove(nextCandidate);
requestData(nextCandidate, list);
},
10, 15, TimeUnit.SECONDS);
RETRY_DELAY_SEC, TimeUnit.SECONDS);
}
requestDataHandshakeMap.remove(nodeAddress);

View file

@ -32,14 +32,12 @@ public class SeedNodesRepository {
new NodeAddress("izs5oz7i5ta7c2ir.onion:8000"),*/
// v0.3.5
new NodeAddress("que4ysbd2qazkb7d.onion:8000"),
new NodeAddress("h2crs2j5huhclkc6.onion:8000"),
new NodeAddress("7a3sj4j6yw5oukai.onion:8000"),
new NodeAddress("hulvbm5xjn7b7ku4.onion:8000"),
new NodeAddress("3efgjjbdvhbvck3x.onion:8000"),
new NodeAddress("3unfcshgwipxhxfm.onion:8000"),
// testnet
new NodeAddress("znmy44wcstn2rkva.onion:8001"),
/* new NodeAddress("zvn7umikgxml6x6h.onion:8001"),
new NodeAddress("wnfxmrmsyeeos2dy.onion:8001"),*/
// regtest
// For development you need to change that to your local onion addresses

View file

@ -15,7 +15,10 @@ public class ProtectedData implements Serializable {
private static final Logger log = LoggerFactory.getLogger(P2PDataStorage.class);
public final ExpirableMessage expirableMessage;
//TODO check if that field make sense as it is in expirableMessage.getTTL()
transient public long ttl;
public final PublicKey ownerPubKey;
public final int sequenceNumber;
public final byte[] signature;