diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
index 9b1ce934186..46e43c1bca0 100644
--- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
+++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java
@@ -242,6 +242,7 @@ protected void onSetupCompleted() {
return message;
});
+ chainHeight.set(chain.getBestChainHeight());
chain.addNewBestBlockListener(block -> {
connectedPeers.set(peerGroup.getConnectedPeers());
chainHeight.set(block.getHeight());
@@ -522,6 +523,16 @@ public boolean isDownloadComplete() {
return downloadPercentageProperty().get() == 1d;
}
+ public boolean isChainHeightSyncedWithinTolerance() {
+ int peersChainHeight = PeerGroup.getMostCommonChainHeight(connectedPeers.get());
+ int bestChainHeight = walletConfig.chain().getBestChainHeight();
+ if (peersChainHeight - bestChainHeight <= 3) {
+ return true;
+ }
+ log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), peersChainHeight);
+ return false;
+ }
+
public Set
getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
return addressEntryList.getAddressEntriesAsListImmutable().stream()
.filter(addressEntry -> addressEntry.getContext() == context)
diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
index 251aac97172..b5ac6118bdb 100644
--- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java
+++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java
@@ -558,6 +558,10 @@ public int getBestChainHeight() {
return isWalletReady() && chain != null ? chain.getBestChainHeight() : 0;
}
+ public boolean isChainHeightSyncedWithinTolerance() {
+ return walletsSetup.isChainHeightSyncedWithinTolerance();
+ }
+
public Transaction getClonedTransaction(Transaction tx) {
return new Transaction(params, tx.bitcoinSerialize());
}
diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
index c06b6d11fd2..7db70114563 100644
--- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java
@@ -580,6 +580,14 @@ private void handleOfferAvailabilityRequest(OfferAvailabilityRequest request, No
return;
}
+ // Don't allow trade start if BitcoinJ is not fully synced (bisq issue #4764)
+ if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
+ errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
+ log.info(errorMessage);
+ sendAckMessage(request, peer, false, errorMessage);
+ return;
+ }
+
if (stopped) {
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
log.debug(errorMessage);
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index 6d011b5e76c..ff22131e93e 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -1275,6 +1275,7 @@ settings.net.creationDateColumn=Established
settings.net.connectionTypeColumn=In/Out
settings.net.sentDataLabel=Sent data statistics
settings.net.receivedDataLabel=Received data statistics
+settings.net.chainHeightLabel=Latest BTC block height
settings.net.roundTripTimeColumn=Roundtrip
settings.net.sentBytesColumn=Sent
settings.net.receivedBytesColumn=Received
@@ -1289,6 +1290,7 @@ settings.net.needRestart=You need to restart the application to apply that chang
settings.net.notKnownYet=Not known yet...
settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec
settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec
+settings.net.chainHeight=Bisq: {0} | Peers: {1}
settings.net.ips=[IP address:port | host name:port | onion address:port] (comma separated). Port can be omitted if default is used (8333).
settings.net.seedNode=Seed node
settings.net.directPeer=Peer (direct)
@@ -2781,6 +2783,8 @@ popup.warning.noMediatorsAvailable=There are no mediators available.
popup.warning.notFullyConnected=You need to wait until you are fully connected to the network.\nThat might take up to about 2 minutes at startup.
popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Bitcoin network.
popup.warning.downloadNotComplete=You need to wait until the download of missing Bitcoin blocks is complete.
+popup.warning.chainNotSynced=The Bisq wallet blockchain height is not synced correctly. If you recently started the application, please wait until one Bitcoin block has been published.\n\n\
+ You can check the blockchain height in Settings/Network Info. If more than one block passes and this problem persists it may be stalled, in which case you should do an SPV resync. [HYPERLINK:https://bisq.wiki/Resyncing_SPV_file]
popup.warning.removeOffer=Are you sure you want to remove that offer?\nThe maker fee of {0} will be lost if you remove that offer.
popup.warning.tooLargePercentageValue=You cannot set a percentage of 100% or larger.
popup.warning.examplePercentageValue=Please enter a percentage number like \"5.4\" for 5.4%
diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java
index 1df383ea071..a6cd541f81e 100644
--- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java
+++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java
@@ -26,6 +26,7 @@
import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService;
+import bisq.core.btc.setup.WalletsSetup;
import bisq.core.filter.FilterManager;
import bisq.core.locale.BankUtil;
import bisq.core.locale.CountryUtil;
@@ -99,6 +100,7 @@ class OfferBookViewModel extends ActivatableViewModel {
private final User user;
private final OfferBook offerBook;
final Preferences preferences;
+ private final WalletsSetup walletsSetup;
private final P2PService p2PService;
final PriceFeedService priceFeedService;
private final ClosedTradableManager closedTradableManager;
@@ -142,6 +144,7 @@ public OfferBookViewModel(User user,
OpenOfferManager openOfferManager,
OfferBook offerBook,
Preferences preferences,
+ WalletsSetup walletsSetup,
P2PService p2PService,
PriceFeedService priceFeedService,
ClosedTradableManager closedTradableManager,
@@ -156,6 +159,7 @@ public OfferBookViewModel(User user,
this.user = user;
this.offerBook = offerBook;
this.preferences = preferences;
+ this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
this.closedTradableManager = closedTradableManager;
@@ -532,6 +536,7 @@ boolean hasPaymentAccountForCurrency() {
boolean canCreateOrTakeOffer() {
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
+ GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) &&
GUIUtil.isBootstrappedOrShowPopup(p2PService);
}
diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.fxml b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.fxml
index 5b966f09736..eae359dec22 100644
--- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.fxml
+++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.fxml
@@ -94,7 +94,7 @@
-
+
@@ -159,7 +159,10 @@
-
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
index dd1d53be109..d328a91d25d 100644
--- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
@@ -45,6 +45,8 @@
import bisq.common.ClockWatcher;
import bisq.common.UserThread;
+import org.bitcoinj.core.PeerGroup;
+
import javax.inject.Inject;
import javafx.fxml.FXML;
@@ -86,7 +88,7 @@ public class NetworkSettingsView extends ActivatableView {
@FXML
InputTextField btcNodesInputTextField;
@FXML
- TextField onionAddress, sentDataTextField, receivedDataTextField;
+ TextField onionAddress, sentDataTextField, receivedDataTextField, chainHeightTextField;
@FXML
Label p2PPeersLabel, bitcoinPeersLabel;
@FXML
@@ -180,6 +182,7 @@ public void initialize() {
connectionTypeColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.connectionTypeColumn")));
sentDataTextField.setPromptText(Res.get("settings.net.sentDataLabel"));
receivedDataTextField.setPromptText(Res.get("settings.net.receivedDataLabel"));
+ chainHeightTextField.setPromptText(Res.get("settings.net.chainHeightLabel"));
roundTripTimeColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.roundTripTimeColumn")));
sentBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.sentBytesColumn")));
receivedBytesColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.receivedBytesColumn")));
@@ -487,6 +490,9 @@ private void updateBitcoinPeersTable() {
bitcoinNetworkListItems.setAll(walletsSetup.getPeerGroup().getConnectedPeers().stream()
.map(BitcoinNetworkListItem::new)
.collect(Collectors.toList()));
+ chainHeightTextField.textProperty().setValue(Res.get("settings.net.chainHeight",
+ walletsSetup.chainHeightProperty().get(),
+ PeerGroup.getMostCommonChainHeight(walletsSetup.connectedPeersProperty().get())));
}
}
diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java
index e90559cd1d0..2ecb9ee2423 100644
--- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java
+++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java
@@ -756,6 +756,15 @@ public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, Wa
return true;
}
+ public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(WalletsSetup walletsSetup) {
+ if (!walletsSetup.isChainHeightSyncedWithinTolerance()) {
+ new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
+ return false;
+ }
+
+ return true;
+ }
+
public static boolean canCreateOrTakeOfferOrShowPopup(User user, Navigation navigation) {
if (!user.hasAcceptedRefundAgents()) {
new Popup().warning(Res.get("popup.warning.noArbitratorsAvailable")).show();
diff --git a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java
index 3b76f7e2742..9cd74e457fb 100644
--- a/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java
+++ b/desktop/src/test/java/bisq/desktop/main/offer/offerbook/OfferBookViewModelTest.java
@@ -229,7 +229,7 @@ public void testMaxCharactersForAmountWithNoOffes() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
assertEquals(0, model.maxPlacesForAmount.intValue());
}
@@ -243,7 +243,7 @@ public void testMaxCharactersForAmount() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -261,7 +261,7 @@ public void testMaxCharactersForAmountRange() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -280,7 +280,7 @@ public void testMaxCharactersForVolumeWithNoOffes() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
assertEquals(0, model.maxPlacesForVolume.intValue());
}
@@ -294,7 +294,7 @@ public void testMaxCharactersForVolume() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -312,7 +312,7 @@ public void testMaxCharactersForVolumeRange() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -331,7 +331,7 @@ public void testMaxCharactersForPriceWithNoOffers() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
assertEquals(0, model.maxPlacesForPrice.intValue());
}
@@ -345,7 +345,7 @@ public void testMaxCharactersForPrice() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -363,7 +363,7 @@ public void testMaxCharactersForPriceDistanceWithNoOffers() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
- final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, null, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
assertEquals(0, model.maxPlacesForMarketPriceMargin.intValue());
}
@@ -391,7 +391,7 @@ public void testMaxCharactersForPriceDistance() {
item4.getOffer().setPriceFeedService(priceFeedService);
offerBookListItems.addAll(item1, item2);
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, priceFeedService,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, priceFeedService,
null, null, null, null, coinFormatter, new BsqFormatter());
model.activate();
@@ -412,7 +412,7 @@ public void testGetPrice() {
when(offerBook.getOfferBookListItems()).thenReturn(offerBookListItems);
when(priceFeedService.getMarketPrice(anyString())).thenReturn(new MarketPrice("USD", 12684.0450, Instant.now().getEpochSecond(), true));
- final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null,
+ final OfferBookViewModel model = new OfferBookViewModel(null, openOfferManager, offerBook, empty, null, null, null,
null, null, null, null, coinFormatter, new BsqFormatter());
final OfferBookListItem item = make(btcBuyItem.but(