daoStateHashList = proto.getDaoStateHashList().isEmpty() ?
+ new LinkedList<>() :
+ new LinkedList<>(proto.getDaoStateHashList().stream()
+ .map(DaoStateHash::fromProto)
+ .collect(Collectors.toList()));
+ return new DaoStateStore(DaoState.fromProto(proto.getBsqState()), daoStateHashList);
}
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/DaoState.java b/core/src/main/java/bisq/core/dao/state/model/DaoState.java
index 9e2e406d983..bbe330034ea 100644
--- a/core/src/main/java/bisq/core/dao/state/model/DaoState.java
+++ b/core/src/main/java/bisq/core/dao/state/model/DaoState.java
@@ -36,10 +36,10 @@
import javax.inject.Inject;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
import java.util.stream.Collectors;
import lombok.Getter;
@@ -51,6 +51,9 @@
* Holds both blockchain data as well as data derived from the governance process (voting).
*
* One BSQ block with empty txs adds 152 bytes which results in about 8 MB/year
+ *
+ * For supporting the hashChain we need to ensure deterministic sorting behaviour of all collections so we use a
+ * TreeMap which is sorted by the key.
*/
@Slf4j
public class DaoState implements PersistablePayload {
@@ -77,17 +80,17 @@ public static DaoState getClone(DaoState daoState) {
// These maps represent mutual data which can get changed at parsing a transaction
@Getter
- private final Map unspentTxOutputMap;
+ private final TreeMap unspentTxOutputMap;
@Getter
- private final Map nonBsqTxOutputMap;
+ private final TreeMap nonBsqTxOutputMap;
@Getter
- private final Map spentInfoMap;
+ private final TreeMap spentInfoMap;
// These maps are related to state change triggered by voting
@Getter
private final List confiscatedLockupTxList;
@Getter
- private final Map issuanceMap; // key is txId
+ private final TreeMap issuanceMap; // key is txId
@Getter
private final List paramChangeList;
@@ -109,11 +112,11 @@ public DaoState() {
this(0,
new LinkedList<>(),
new LinkedList<>(),
- new HashMap<>(),
- new HashMap<>(),
- new HashMap<>(),
+ new TreeMap<>(),
+ new TreeMap<>(),
+ new TreeMap<>(),
new ArrayList<>(),
- new HashMap<>(),
+ new TreeMap<>(),
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>()
@@ -128,11 +131,11 @@ public DaoState() {
private DaoState(int chainHeight,
LinkedList blocks,
LinkedList cycles,
- Map unspentTxOutputMap,
- Map nonBsqTxOutputMap,
- Map spentInfoMap,
+ TreeMap unspentTxOutputMap,
+ TreeMap nonBsqTxOutputMap,
+ TreeMap spentInfoMap,
List confiscatedLockupTxList,
- Map issuanceMap,
+ TreeMap issuanceMap,
List paramChangeList,
List evaluatedProposalList,
List decryptedBallotsWithMeritsList) {
@@ -182,15 +185,15 @@ public static DaoState fromProto(PB.BsqState proto) {
.collect(Collectors.toCollection(LinkedList::new));
LinkedList cycles = proto.getCyclesList().stream()
.map(Cycle::fromProto).collect(Collectors.toCollection(LinkedList::new));
- Map unspentTxOutputMap = proto.getUnspentTxOutputMapMap().entrySet().stream()
- .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
- Map nonBsqTxOutputMap = proto.getNonBsqTxOutputMapMap().entrySet().stream()
- .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
- Map spentInfoMap = proto.getSpentInfoMapMap().entrySet().stream()
- .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue())));
+ TreeMap unspentTxOutputMap = new TreeMap<>(proto.getUnspentTxOutputMapMap().entrySet().stream()
+ .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue()))));
+ TreeMap nonBsqTxOutputMap = new TreeMap<>(proto.getNonBsqTxOutputMapMap().entrySet().stream()
+ .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue()))));
+ TreeMap spentInfoMap = new TreeMap<>(proto.getSpentInfoMapMap().entrySet().stream()
+ .collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> SpentInfo.fromProto(e.getValue()))));
List confiscatedLockupTxList = new ArrayList<>(proto.getConfiscatedLockupTxListList());
- Map issuanceMap = proto.getIssuanceMapMap().entrySet().stream()
- .collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue())));
+ TreeMap issuanceMap = new TreeMap<>(proto.getIssuanceMapMap().entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue()))));
List paramChangeList = proto.getParamChangeListList().stream()
.map(ParamChange::fromProto).collect(Collectors.toCollection(ArrayList::new));
List evaluatedProposalList = proto.getEvaluatedProposalListList().stream()
@@ -218,4 +221,21 @@ public static DaoState fromProto(PB.BsqState proto) {
public void setChainHeight(int chainHeight) {
this.chainHeight = chainHeight;
}
+
+ @Override
+ public String toString() {
+ return "DaoState{" +
+ "\n chainHeight=" + chainHeight +
+ ",\n blocks=" + blocks +
+ ",\n cycles=" + cycles +
+ ",\n unspentTxOutputMap=" + unspentTxOutputMap +
+ ",\n nonBsqTxOutputMap=" + nonBsqTxOutputMap +
+ ",\n spentInfoMap=" + spentInfoMap +
+ ",\n confiscatedLockupTxList=" + confiscatedLockupTxList +
+ ",\n issuanceMap=" + issuanceMap +
+ ",\n paramChangeList=" + paramChangeList +
+ ",\n evaluatedProposalList=" + evaluatedProposalList +
+ ",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList +
+ "\n}";
+ }
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/BaseTxOutput.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/BaseTxOutput.java
index e8b5015ccf9..62f5b20c706 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/BaseTxOutput.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/BaseTxOutput.java
@@ -45,9 +45,10 @@ public abstract class BaseTxOutput implements ImmutableDaoStateModel {
protected final long value;
protected final String txId;
- // Only set if dumpBlockchainData is true
+ // Before v0.9.6 it was only set if dumpBlockchainData was set to true but we changed that with 0.9.6
+ // so that is is always set. We still need to support it because of backward compatibility.
@Nullable
- protected final PubKeyScript pubKeyScript;
+ protected final PubKeyScript pubKeyScript; // Has about 50 bytes, total size of TxOutput is about 300 bytes.
@Nullable
protected final String address;
@Nullable
@@ -69,7 +70,6 @@ public BaseTxOutput(int index,
this.address = address;
this.opReturnData = opReturnData;
this.blockHeight = blockHeight;
-
}
diff --git a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputKey.java b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputKey.java
index 11ebec2a896..282d69d2783 100644
--- a/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputKey.java
+++ b/core/src/main/java/bisq/core/dao/state/model/blockchain/TxOutputKey.java
@@ -21,6 +21,8 @@
import lombok.Value;
+import org.jetbrains.annotations.NotNull;
+
import javax.annotation.concurrent.Immutable;
/**
@@ -29,7 +31,7 @@
*/
@Immutable
@Value
-public final class TxOutputKey implements ImmutableDaoStateModel {
+public final class TxOutputKey implements ImmutableDaoStateModel, Comparable {
private final String txId;
private final int index;
@@ -47,4 +49,9 @@ public static TxOutputKey getKeyFromString(String keyAsString) {
final String[] tokens = keyAsString.split(":");
return new TxOutputKey(tokens[0], Integer.valueOf(tokens[1]));
}
+
+ @Override
+ public int compareTo(@NotNull Object o) {
+ return toString().compareTo(o.toString());
+ }
}
diff --git a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
index 9a119c57c14..85a02942bc3 100644
--- a/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
+++ b/core/src/main/java/bisq/core/proto/network/CoreNetworkProtoResolver.java
@@ -28,6 +28,15 @@
import bisq.core.arbitration.messages.PeerPublishedDisputePayoutTxMessage;
import bisq.core.dao.governance.blindvote.network.messages.RepublishGovernanceDataRequest;
import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload;
+import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest;
+import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesResponse;
+import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
+import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesResponse;
+import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
+import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesResponse;
+import bisq.core.dao.monitoring.network.messages.NewBlindVoteStateHashMessage;
+import bisq.core.dao.monitoring.network.messages.NewDaoStateHashMessage;
+import bisq.core.dao.monitoring.network.messages.NewProposalStateHashMessage;
import bisq.core.dao.node.messages.GetBlocksRequest;
import bisq.core.dao.node.messages.GetBlocksResponse;
import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
@@ -153,7 +162,6 @@ public NetworkEnvelope fromProto(PB.NetworkEnvelope proto) throws ProtobufferExc
return GetBlocksResponse.fromProto(proto.getGetBlocksResponse(), messageVersion);
case NEW_BLOCK_BROADCAST_MESSAGE:
return NewBlockBroadcastMessage.fromProto(proto.getNewBlockBroadcastMessage(), messageVersion);
-
case ADD_PERSISTABLE_NETWORK_PAYLOAD_MESSAGE:
return AddPersistableNetworkPayloadMessage.fromProto(proto.getAddPersistableNetworkPayloadMessage(), this, messageVersion);
case ACK_MESSAGE:
@@ -161,6 +169,27 @@ public NetworkEnvelope fromProto(PB.NetworkEnvelope proto) throws ProtobufferExc
case REPUBLISH_GOVERNANCE_DATA_REQUEST:
return RepublishGovernanceDataRequest.fromProto(proto.getRepublishGovernanceDataRequest(), messageVersion);
+ case NEW_DAO_STATE_HASH_MESSAGE:
+ return NewDaoStateHashMessage.fromProto(proto.getNewDaoStateHashMessage(), messageVersion);
+ case GET_DAO_STATE_HASHES_REQUEST:
+ return GetDaoStateHashesRequest.fromProto(proto.getGetDaoStateHashesRequest(), messageVersion);
+ case GET_DAO_STATE_HASHES_RESPONSE:
+ return GetDaoStateHashesResponse.fromProto(proto.getGetDaoStateHashesResponse(), messageVersion);
+
+ case NEW_PROPOSAL_STATE_HASH_MESSAGE:
+ return NewProposalStateHashMessage.fromProto(proto.getNewProposalStateHashMessage(), messageVersion);
+ case GET_PROPOSAL_STATE_HASHES_REQUEST:
+ return GetProposalStateHashesRequest.fromProto(proto.getGetProposalStateHashesRequest(), messageVersion);
+ case GET_PROPOSAL_STATE_HASHES_RESPONSE:
+ return GetProposalStateHashesResponse.fromProto(proto.getGetProposalStateHashesResponse(), messageVersion);
+
+ case NEW_BLIND_VOTE_STATE_HASH_MESSAGE:
+ return NewBlindVoteStateHashMessage.fromProto(proto.getNewBlindVoteStateHashMessage(), messageVersion);
+ case GET_BLIND_VOTE_STATE_HASHES_REQUEST:
+ return GetBlindVoteStateHashesRequest.fromProto(proto.getGetBlindVoteStateHashesRequest(), messageVersion);
+ case GET_BLIND_VOTE_STATE_HASHES_RESPONSE:
+ return GetBlindVoteStateHashesResponse.fromProto(proto.getGetBlindVoteStateHashesResponse(), messageVersion);
+
default:
throw new ProtobufferException("Unknown proto message case (PB.NetworkEnvelope). messageCase=" +
proto.getMessageCase() + "; proto raw data=" + proto.toString());
diff --git a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
index 1bf5ce2c90f..a36b129f3c8 100644
--- a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
+++ b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java
@@ -39,6 +39,7 @@ public static void setSupportedCapabilities(BisqEnvironment bisqEnvironment) {
supportedCapabilities.add(Capability.PROPOSAL);
supportedCapabilities.add(Capability.BLIND_VOTE);
supportedCapabilities.add(Capability.BSQ_BLOCK);
+ supportedCapabilities.add(Capability.DAO_STATE);
String isFullDaoNode = bisqEnvironment.getProperty(DaoOptionKeys.FULL_DAO_NODE, String.class, "");
if (isFullDaoNode != null && !isFullDaoNode.isEmpty())
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index a69439dc803..c9afe3f29b9 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -1260,6 +1260,7 @@ dao.tab.bsqWallet=BSQ wallet
dao.tab.proposals=Governance
dao.tab.bonding=Bonding
dao.tab.proofOfBurn=Asset listing fee/Proof of burn
+dao.tab.monitor=Network monitor
dao.tab.news=News
dao.paidWithBsq=paid with BSQ
@@ -1311,6 +1312,7 @@ dao.results.proposals.table.header.result=Vote result
dao.results.proposals.voting.detail.header=Vote results for selected proposal
dao.results.exceptions=Vote result exception(s)
+
# suppress inspection "UnusedProperty"
dao.param.UNDEFINED=Undefined
@@ -1742,7 +1744,6 @@ dao.wallet.dashboard.burntTx=No. of all fee payments transactions
dao.wallet.dashboard.price=Latest BSQ/BTC trade price (in Bisq)
dao.wallet.dashboard.marketCap=Market capitalisation (based on trade price)
-
dao.wallet.receive.fundYourWallet=Your BSQ receive address
dao.wallet.receive.bsqAddress=BSQ wallet address (Fresh unused address)
@@ -1857,6 +1858,51 @@ dao.news.DAOOnTestnet.fourthSection.title=4. Explore a BSQ Block Explorer
dao.news.DAOOnTestnet.fourthSection.content=Since BSQ is just bitcoin, you can see BSQ transactions on our bitcoin block explorer.
dao.news.DAOOnTestnet.readMoreLink=Read the full documentation
+dao.monitor.daoState=DAO state
+dao.monitor.proposals=Proposals state
+dao.monitor.blindVotes=Blind votes state
+
+dao.monitor.table.peers=Peers
+dao.monitor.table.conflicts=Conflicts
+dao.monitor.state=Status
+dao.monitor.requestAlHashes=Request all hashes
+dao.monitor.resync=Resync DAO state
+dao.monitor.table.header.cycleBlockHeight=Cycle / block height
+dao.monitor.table.cycleBlockHeight=Cycle {0} / block {1}
+
+dao.monitor.daoState.headline=DAO state
+dao.monitor.daoState.daoStateInSync=Your local DAO state is in consensus with the network
+dao.monitor.daoState.daoStateNotInSync=Your local DAO state is not in consensus with the network. Please resync your \
+ DAO state.
+dao.monitor.daoState.table.headline=Chain of DAO state hashes
+dao.monitor.daoState.table.blockHeight=Block height
+dao.monitor.daoState.table.hash=Hash of DAO state
+dao.monitor.daoState.table.prev=Previous hash
+dao.monitor.daoState.conflictTable.headline=DAO state hashes from peers in conflict
+
+dao.monitor.proposal.headline=Proposals state
+dao.monitor.proposal.daoStateInSync=Your local proposals state is in consensus with the network
+dao.monitor.proposal.daoStateNotInSync=Your local proposals state is not in consensus with the network. Please restart your \
+ application.
+dao.monitor.proposal.table.headline=Chain of proposal state hashes
+dao.monitor.proposal.conflictTable.headline=Proposal state hashes from peers in conflict
+
+dao.monitor.proposal.table.hash=Hash of proposal state
+dao.monitor.proposal.table.prev=Previous hash
+dao.monitor.proposal.table.numProposals=No. proposals
+
+
+dao.monitor.blindVote.headline=Blind votes state
+dao.monitor.blindVote.daoStateInSync=Your local blind votes state is in consensus with the network
+dao.monitor.blindVote.daoStateNotInSync=Your local blind votes state is not in consensus with the network. Please restart your \
+ application.
+dao.monitor.blindVote.table.headline=Chain of blind vote state hashes
+dao.monitor.blindVote.conflictTable.headline=Blind vote state hashes from peers in conflict
+dao.monitor.blindVote.table.hash=Hash of blind vote state
+dao.monitor.blindVote.table.prev=Previous hash
+dao.monitor.blindVote.table.numBlindVotes=No. blind votes
+
+
####################################################################
# Windows
####################################################################
diff --git a/core/src/test/java/bisq/core/dao/state/DaoStateServiceTest.java b/core/src/test/java/bisq/core/dao/state/DaoStateServiceTest.java
index 0d9eff411b8..3809770dc95 100644
--- a/core/src/test/java/bisq/core/dao/state/DaoStateServiceTest.java
+++ b/core/src/test/java/bisq/core/dao/state/DaoStateServiceTest.java
@@ -38,6 +38,7 @@ public void testIsBlockHashKnown() {
);
Block block = new Block(0, 1534800000, "fakeblockhash0", null);
+ stateService.onNewBlockHeight(0);
stateService.onNewBlockWithEmptyTxs(block);
Assert.assertEquals(
"Block has to be genesis block to get added.",
@@ -52,10 +53,13 @@ public void testIsBlockHashKnown() {
);
block = new Block(1, 1534800001, "fakeblockhash1", null);
+ stateService.onNewBlockHeight(1);
stateService.onNewBlockWithEmptyTxs(block);
block = new Block(2, 1534800002, "fakeblockhash2", null);
+ stateService.onNewBlockHeight(2);
stateService.onNewBlockWithEmptyTxs(block);
block = new Block(3, 1534800003, "fakeblockhash3", null);
+ stateService.onNewBlockHeight(3);
stateService.onNewBlockWithEmptyTxs(block);
Assert.assertEquals(
"Block that was never added should still not exist after adding more blocks.",
diff --git a/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java
index 7941e7bb5a2..d2730b7c303 100644
--- a/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java
+++ b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java
@@ -18,6 +18,7 @@
package bisq.core.dao.state;
import bisq.core.dao.governance.period.CycleService;
+import bisq.core.dao.monitoring.DaoStateMonitoringService;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
@@ -33,7 +34,7 @@
import static org.powermock.api.mockito.PowerMockito.mock;
@RunWith(PowerMockRunner.class)
-@PrepareForTest({DaoStateService.class, GenesisTxInfo.class, CycleService.class, DaoStateStorageService.class})
+@PrepareForTest({DaoStateService.class, GenesisTxInfo.class, CycleService.class, DaoStateStorageService.class, DaoStateMonitoringService.class})
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
public class DaoStateSnapshotServiceTest {
@@ -44,7 +45,8 @@ public void setup() {
daoStateSnapshotService = new DaoStateSnapshotService(mock(DaoStateService.class),
mock(GenesisTxInfo.class),
mock(CycleService.class),
- mock(DaoStateStorageService.class));
+ mock(DaoStateStorageService.class),
+ mock(DaoStateMonitoringService.class));
}
@Test
diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css
index b2ff670a49d..8a2d2bd26d4 100644
--- a/desktop/src/main/java/bisq/desktop/bisq.css
+++ b/desktop/src/main/java/bisq/desktop/bisq.css
@@ -2049,6 +2049,14 @@ textfield */
-fx-fill: -fx-accent;
}
+.dao-inSync {
+ -fx-text-fill: -bs-rd-green;
+}
+
+.dao-inConflict {
+ -fx-text-fill: -bs-rd-error-red;
+}
+
/********************************************************************************************************************
* *
* Notifications *
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
index 412645fb567..98239372f88 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java
@@ -28,6 +28,7 @@
import bisq.desktop.main.dao.bonding.BondingView;
import bisq.desktop.main.dao.burnbsq.BurnBsqView;
import bisq.desktop.main.dao.governance.GovernanceView;
+import bisq.desktop.main.dao.monitor.MonitorView;
import bisq.desktop.main.dao.news.NewsView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
import bisq.desktop.main.dao.wallet.dashboard.BsqDashboardView;
@@ -52,7 +53,7 @@
public class DaoView extends ActivatableViewAndModel {
@FXML
- private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab;
+ private Tab bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, daoNewsTab, monitor;
private Navigation.Listener navigationListener;
private ChangeListener tabChangeListener;
@@ -80,23 +81,26 @@ public void initialize() {
proposalsTab = new Tab(Res.get("dao.tab.proposals").toUpperCase());
bondingTab = new Tab(Res.get("dao.tab.bonding").toUpperCase());
burnBsqTab = new Tab(Res.get("dao.tab.proofOfBurn").toUpperCase());
+ monitor = new Tab(Res.get("dao.tab.monitor").toUpperCase());
bsqWalletTab.setClosable(false);
proposalsTab.setClosable(false);
bondingTab.setClosable(false);
burnBsqTab.setClosable(false);
+ monitor.setClosable(false);
if (!DevEnv.isDaoActivated()) {
+ bsqWalletTab.setDisable(true);
proposalsTab.setDisable(true);
bondingTab.setDisable(true);
burnBsqTab.setDisable(true);
- bsqWalletTab.setDisable(true);
+ monitor.setDisable(true);
daoNewsTab = new Tab(Res.get("dao.tab.news").toUpperCase());
root.getTabs().add(daoNewsTab);
} else {
- root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab);
+ root.getTabs().addAll(bsqWalletTab, proposalsTab, bondingTab, burnBsqTab, monitor);
}
navigationListener = viewPath -> {
@@ -121,6 +125,8 @@ public void initialize() {
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
} else if (newValue == burnBsqTab) {
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
+ } else if (newValue == monitor) {
+ navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
}
};
}
@@ -141,6 +147,8 @@ else if (selectedItem == bondingTab)
navigation.navigateTo(MainView.class, DaoView.class, BondingView.class);
else if (selectedItem == burnBsqTab)
navigation.navigateTo(MainView.class, DaoView.class, BurnBsqView.class);
+ else if (selectedItem == monitor)
+ navigation.navigateTo(MainView.class, DaoView.class, MonitorView.class);
}
} else {
loadView(NewsView.class);
@@ -173,6 +181,8 @@ private void loadView(Class extends View> viewClass) {
selectedTab = bondingTab;
} else if (view instanceof BurnBsqView) {
selectedTab = burnBsqTab;
+ } else if (view instanceof MonitorView) {
+ selectedTab = monitor;
} else if (view instanceof NewsView) {
selectedTab = daoNewsTab;
}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondsView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondsView.java
index e263a6a2b34..14a42e934a0 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/bonds/BondsView.java
@@ -107,6 +107,7 @@ protected void activate() {
bondedReputationRepository.getBonds().addListener(bondedReputationListener);
bondedRolesRepository.getBonds().addListener(bondedRolesListener);
updateList();
+ GUIUtil.setFitToRowsForTableView(tableView, 37, 28, 2, 30);
}
@Override
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java
index e189b069e00..0f9225e9327 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/reputation/MyReputationView.java
@@ -189,6 +189,7 @@ protected void activate() {
setNewRandomSalt();
updateList();
+ GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
@Override
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/bonding/roles/RolesView.java b/desktop/src/main/java/bisq/desktop/main/dao/bonding/roles/RolesView.java
index 264979523b7..2340ea7f936 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/bonding/roles/RolesView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/bonding/roles/RolesView.java
@@ -103,6 +103,7 @@ protected void activate() {
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
daoFacade.getBondedRoles().addListener(bondedRoleListChangeListener);
updateList();
+ GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 30);
}
@Override
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java
index 1760b32f4b5..d6fad9d71a4 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/assetfee/AssetFeeView.java
@@ -191,6 +191,7 @@ protected void activate() {
});
updateList();
+ GUIUtil.setFitToRowsForTableView(tableView, 41, 28, 2, 10);
updateButtonState();
feeAmountInputTextField.resetValidation();
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java
index af5b9ab7adb..b4a51fa1681 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/burnbsq/proofofburn/ProofOfBurnView.java
@@ -187,6 +187,8 @@ protected void activate() {
preImageTextField.setValidator(new InputValidator());
updateList();
+ GUIUtil.setFitToRowsForTableView(myItemsTableView, 41, 28, 2, 4);
+ GUIUtil.setFitToRowsForTableView(allTxsTableView, 41, 28, 2, 10);
updateButtonState();
}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/GovernanceView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/GovernanceView.java
index dd275f1ddc4..3c3c4510c47 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/GovernanceView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/GovernanceView.java
@@ -51,7 +51,6 @@
@FxmlView
public class GovernanceView extends ActivatableViewAndModel {
-
private final ViewLoader viewLoader;
private final Navigation navigation;
private final DaoFacade daoFacade;
@@ -102,6 +101,7 @@ public void initialize() {
ProposalsView.class, baseNavPath);
result = new MenuItem(navigation, toggleGroup, Res.get("dao.proposal.menuItem.result"),
VoteResultView.class, baseNavPath);
+
leftVBox.getChildren().addAll(dashboard, make, open, result);
}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java
index 3e8d3615cd0..2a7ad6b7842 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/proposals/ProposalsView.java
@@ -184,8 +184,6 @@ private ProposalsView(DaoFacade daoFacade,
public void initialize() {
super.initialize();
- root.getStyleClass().add("vote-root");
-
gridRow = phasesView.addGroup(root, gridRow);
proposalDisplayGridPane = new GridPane();
@@ -226,6 +224,7 @@ protected void activate() {
bsqWalletService.getUnlockingBondsBalance());
updateListItems();
+ GUIUtil.setFitToRowsForTableView(tableView, 38, 28, 2, 6);
updateViews();
}
@@ -334,8 +333,6 @@ private void updateListItems() {
}
GUIUtil.setFitToRowsForTableView(tableView, 38, 28, 2, 6);
- tableView.layout();
- root.layout();
}
private void createAllFieldsOnProposalDisplay(Proposal proposal, @Nullable Ballot ballot,
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
index 633ec79181a..ccb850a7e35 100644
--- a/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
+++ b/desktop/src/main/java/bisq/desktop/main/dao/governance/result/VoteResultView.java
@@ -149,6 +149,7 @@ public class VoteResultView extends ActivatableView implements D
private ChangeListener selectedVoteResultListItemListener;
private ResultsOfCycle resultsOfCycle;
private ProposalListItem selectedProposalListItem;
+ private TableView votesTableView;
///////////////////////////////////////////////////////////////////////////////////////////
@@ -211,6 +212,13 @@ protected void activate() {
JsonElement cyclesJsonArray = getVotingHistoryJson();
GUIUtil.exportJSON("voteResultsHistory.json", cyclesJsonArray, (Stage) root.getScene().getWindow());
});
+ if (proposalsTableView != null) {
+ GUIUtil.setFitToRowsForTableView(proposalsTableView, 25, 28, 2, 4);
+ }
+ if (votesTableView != null) {
+ GUIUtil.setFitToRowsForTableView(votesTableView, 25, 28, 2, 4);
+ }
+ GUIUtil.setFitToRowsForTableView(cyclesTableView, 25, 28, 2, 4);
}
@Override
@@ -513,7 +521,7 @@ private void createVotesTable() {
GridPane.setColumnSpan(votesTableHeader, 2);
root.getChildren().add(votesTableHeader);
- TableView votesTableView = new TableView<>();
+ votesTableView = new TableView<>();
votesTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
votesTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.fxml
new file mode 100644
index 00000000000..c0377d39c31
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.fxml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.java
new file mode 100644
index 00000000000..93de1642514
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/MonitorView.java
@@ -0,0 +1,131 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor;
+
+import bisq.desktop.Navigation;
+import bisq.desktop.common.view.ActivatableViewAndModel;
+import bisq.desktop.common.view.CachingViewLoader;
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.common.view.View;
+import bisq.desktop.common.view.ViewLoader;
+import bisq.desktop.common.view.ViewPath;
+import bisq.desktop.components.MenuItem;
+import bisq.desktop.main.MainView;
+import bisq.desktop.main.dao.DaoView;
+import bisq.desktop.main.dao.monitor.blindvotes.BlindVoteStateMonitorView;
+import bisq.desktop.main.dao.monitor.daostate.DaoStateMonitorView;
+import bisq.desktop.main.dao.monitor.proposals.ProposalStateMonitorView;
+
+import bisq.core.locale.Res;
+
+import javax.inject.Inject;
+
+import javafx.fxml.FXML;
+
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+
+import java.util.Arrays;
+import java.util.List;
+
+@FxmlView
+public class MonitorView extends ActivatableViewAndModel {
+ private final ViewLoader viewLoader;
+ private final Navigation navigation;
+
+ private MenuItem daoState, proposals, blindVotes;
+ private Navigation.Listener navigationListener;
+
+ @FXML
+ private VBox leftVBox;
+ @FXML
+ private AnchorPane content;
+ private Class extends View> selectedViewClass;
+ private ToggleGroup toggleGroup;
+
+ @Inject
+ private MonitorView(CachingViewLoader viewLoader, Navigation navigation) {
+ this.viewLoader = viewLoader;
+ this.navigation = navigation;
+ }
+
+ @Override
+ public void initialize() {
+ navigationListener = viewPath -> {
+ if (viewPath.size() != 4 || viewPath.indexOf(MonitorView.class) != 2)
+ return;
+
+ selectedViewClass = viewPath.tip();
+ loadView(selectedViewClass);
+ };
+
+ toggleGroup = new ToggleGroup();
+ List> baseNavPath = Arrays.asList(MainView.class, DaoView.class, MonitorView.class);
+ daoState = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.daoState"),
+ DaoStateMonitorView.class, baseNavPath);
+ proposals = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.proposals"),
+ ProposalStateMonitorView.class, baseNavPath);
+ blindVotes = new MenuItem(navigation, toggleGroup, Res.get("dao.monitor.blindVotes"),
+ BlindVoteStateMonitorView.class, baseNavPath);
+
+ leftVBox.getChildren().addAll(daoState, proposals, blindVotes);
+ }
+
+ @Override
+ protected void activate() {
+ proposals.activate();
+ blindVotes.activate();
+ daoState.activate();
+
+ navigation.addListener(navigationListener);
+ ViewPath viewPath = navigation.getCurrentPath();
+ if (viewPath.size() == 3 && viewPath.indexOf(MonitorView.class) == 2 ||
+ viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
+ if (selectedViewClass == null)
+ selectedViewClass = DaoStateMonitorView.class;
+
+ loadView(selectedViewClass);
+
+ } else if (viewPath.size() == 4 && viewPath.indexOf(MonitorView.class) == 2) {
+ selectedViewClass = viewPath.get(3);
+ loadView(selectedViewClass);
+ }
+ }
+
+ @SuppressWarnings("Duplicates")
+ @Override
+ protected void deactivate() {
+ navigation.removeListener(navigationListener);
+
+ proposals.deactivate();
+ blindVotes.deactivate();
+ daoState.deactivate();
+ }
+
+ private void loadView(Class extends View> viewClass) {
+ View view = viewLoader.load(viewClass);
+ content.getChildren().setAll(view.getRoot());
+
+ if (view instanceof DaoStateMonitorView) toggleGroup.selectToggle(daoState);
+ else if (view instanceof ProposalStateMonitorView) toggleGroup.selectToggle(proposals);
+ else if (view instanceof BlindVoteStateMonitorView) toggleGroup.selectToggle(blindVotes);
+ }
+}
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java
new file mode 100644
index 00000000000..7062fa726e7
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateBlockListItem.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor;
+
+import bisq.core.dao.monitoring.model.StateBlock;
+import bisq.core.dao.monitoring.model.StateHash;
+import bisq.core.locale.Res;
+
+import bisq.common.util.Utilities;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Getter
+@EqualsAndHashCode
+public abstract class StateBlockListItem> {
+ protected final StateBlock stateBlock;
+ protected final String height;
+ protected final String hash;
+ protected final String prevHash;
+ protected final String numNetworkMessages;
+ protected final String numMisMatches;
+ protected final boolean isInSync;
+
+ protected StateBlockListItem(StB stateBlock, int cycleIndex) {
+ this.stateBlock = stateBlock;
+ height = Res.get("dao.monitor.table.cycleBlockHeight", cycleIndex + 1, String.valueOf(stateBlock.getHeight()));
+ hash = Utilities.bytesAsHexString(stateBlock.getHash());
+ prevHash = stateBlock.getPrevHash().length > 0 ? Utilities.bytesAsHexString(stateBlock.getPrevHash()) : "-";
+ numNetworkMessages = String.valueOf(stateBlock.getPeersMap().size());
+ int size = stateBlock.getInConflictMap().size();
+ numMisMatches = String.valueOf(size);
+ isInSync = size == 0;
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java
new file mode 100644
index 00000000000..0610f3c8cb9
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateInConflictListItem.java
@@ -0,0 +1,47 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor;
+
+import bisq.core.dao.monitoring.model.StateHash;
+import bisq.core.locale.Res;
+
+import bisq.common.util.Utilities;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Getter
+@EqualsAndHashCode
+public abstract class StateInConflictListItem {
+ private final String peerAddress;
+ private final String height;
+ private final String hash;
+ private final String prevHash;
+ private final T stateHash;
+
+ protected StateInConflictListItem(String peerAddress, T stateHash, int cycleIndex) {
+ this.stateHash = stateHash;
+ this.peerAddress = peerAddress;
+ height = Res.get("dao.monitor.table.cycleBlockHeight", cycleIndex + 1, String.valueOf(stateHash.getHeight()));
+ hash = Utilities.bytesAsHexString(stateHash.getHash());
+ prevHash = stateHash.getPrevHash().length > 0 ?
+ Utilities.bytesAsHexString(stateHash.getPrevHash()) : "-";
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java
new file mode 100644
index 00000000000..a00059bf9c1
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/StateMonitorView.java
@@ -0,0 +1,563 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor;
+
+import bisq.desktop.common.view.ActivatableView;
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.components.AutoTooltipButton;
+import bisq.desktop.components.AutoTooltipLabel;
+import bisq.desktop.components.AutoTooltipTableColumn;
+import bisq.desktop.components.TableGroupHeadline;
+import bisq.desktop.util.FormBuilder;
+import bisq.desktop.util.GUIUtil;
+import bisq.desktop.util.Layout;
+
+import bisq.core.dao.DaoFacade;
+import bisq.core.dao.governance.period.CycleService;
+import bisq.core.dao.governance.period.PeriodService;
+import bisq.core.dao.monitoring.model.StateBlock;
+import bisq.core.dao.monitoring.model.StateHash;
+import bisq.core.dao.state.DaoStateListener;
+import bisq.core.dao.state.DaoStateService;
+import bisq.core.locale.Res;
+
+import javax.inject.Inject;
+
+import de.jensd.fx.fontawesome.AwesomeIcon;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Priority;
+
+import javafx.geometry.Insets;
+
+import org.fxmisc.easybind.EasyBind;
+import org.fxmisc.easybind.Subscription;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ReadOnlyObjectWrapper;
+import javafx.beans.property.SimpleBooleanProperty;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.SortedList;
+
+import javafx.util.Callback;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@FxmlView
+public abstract class StateMonitorView,
+ BLI extends StateBlockListItem,
+ CLI extends StateInConflictListItem>
+ extends ActivatableView implements DaoStateListener {
+ protected final DaoStateService daoStateService;
+ protected final DaoFacade daoFacade;
+ protected final CycleService cycleService;
+ protected final PeriodService periodService;
+
+ protected TextField statusTextField;
+ protected Button resyncButton;
+ protected TableView tableView;
+ protected TableView conflictTableView;
+
+ protected final ObservableList listItems = FXCollections.observableArrayList();
+ private final SortedList sortedList = new SortedList<>(listItems);
+ private final ObservableList conflictListItems = FXCollections.observableArrayList();
+ private final SortedList sortedConflictList = new SortedList<>(conflictListItems);
+
+ protected int gridRow = 0;
+ private Subscription selectedItemSubscription;
+ protected final BooleanProperty isInConflict = new SimpleBooleanProperty();
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, lifecycle
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Inject
+ public StateMonitorView(DaoStateService daoStateService,
+ DaoFacade daoFacade,
+ CycleService cycleService,
+ PeriodService periodService) {
+ this.daoStateService = daoStateService;
+ this.daoFacade = daoFacade;
+ this.cycleService = cycleService;
+ this.periodService = periodService;
+ }
+
+ @Override
+ public void initialize() {
+ createTableView();
+ createDetailsView();
+ }
+
+ @Override
+ protected void activate() {
+ selectedItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectItem);
+
+ sortedList.comparatorProperty().bind(tableView.comparatorProperty());
+ sortedConflictList.comparatorProperty().bind(conflictTableView.comparatorProperty());
+
+ daoStateService.addDaoStateListener(this);
+
+ resyncButton.visibleProperty().bind(isInConflict);
+ resyncButton.managedProperty().bind(isInConflict);
+
+ if (daoStateService.isParseBlockChainComplete()) {
+ onDataUpdate();
+ }
+
+ GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
+ GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
+ }
+
+ @Override
+ protected void deactivate() {
+ selectedItemSubscription.unsubscribe();
+
+ sortedList.comparatorProperty().unbind();
+ sortedConflictList.comparatorProperty().unbind();
+
+ daoStateService.removeDaoStateListener(this);
+
+ resyncButton.visibleProperty().unbind();
+ resyncButton.managedProperty().unbind();
+
+ resyncButton.setOnAction(null);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Abstract
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ protected abstract BLI getStateBlockListItem(StB e);
+
+ protected abstract CLI getStateInConflictListItem(Map.Entry mapEntry);
+
+ protected abstract void requestHashesFromGenesisBlockHeight(String peerAddress);
+
+ protected abstract String getConflictsTableHeader();
+
+ protected abstract String getPeersTableHeader();
+
+ protected abstract String getPrevHashTableHeader();
+
+ protected abstract String getHashTableHeader();
+
+ protected abstract String getBlockHeightTableHeader();
+
+ protected abstract String getRequestHashes();
+
+ protected abstract String getTableHeadLine();
+
+ protected abstract String getConflictTableHeadLine();
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // DaoStateListener
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void onParseBlockChainComplete() {
+ onDataUpdate();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Create table views
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private void createTableView() {
+ TableGroupHeadline headline = new TableGroupHeadline(getTableHeadLine());
+ GridPane.setRowIndex(headline, ++gridRow);
+ GridPane.setMargin(headline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
+ root.getChildren().add(headline);
+
+ tableView = new TableView<>();
+ tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
+ tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ tableView.setPrefHeight(100);
+
+ createColumns();
+ GridPane.setRowIndex(tableView, gridRow);
+ GridPane.setHgrow(tableView, Priority.ALWAYS);
+ GridPane.setMargin(tableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, -25, -10));
+ root.getChildren().add(tableView);
+
+ tableView.setItems(sortedList);
+ }
+
+ private void createDetailsView() {
+ TableGroupHeadline conflictTableHeadline = new TableGroupHeadline(getConflictTableHeadLine());
+ GridPane.setRowIndex(conflictTableHeadline, ++gridRow);
+ GridPane.setMargin(conflictTableHeadline, new Insets(Layout.GROUP_DISTANCE, -10, -10, -10));
+ root.getChildren().add(conflictTableHeadline);
+
+ conflictTableView = new TableView<>();
+ conflictTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
+ conflictTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ conflictTableView.setPrefHeight(100);
+
+ createConflictColumns();
+ GridPane.setRowIndex(conflictTableView, gridRow);
+ GridPane.setHgrow(conflictTableView, Priority.ALWAYS);
+ GridPane.setMargin(conflictTableView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, -10, -25, -10));
+ root.getChildren().add(conflictTableView);
+
+ conflictTableView.setItems(sortedConflictList);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Handler
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ private void onSelectItem(BLI item) {
+ if (item != null) {
+ conflictListItems.setAll(item.getStateBlock().getInConflictMap().entrySet().stream()
+ .map(this::getStateInConflictListItem).collect(Collectors.toList()));
+ GUIUtil.setFitToRowsForTableView(conflictTableView, 38, 28, 2, 4);
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Private
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ protected void onDataUpdate() {
+ GUIUtil.setFitToRowsForTableView(tableView, 25, 28, 2, 5);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // TableColumns
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ protected void createColumns() {
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
+ column.setMinWidth(120);
+ column.getStyleClass().add("first-column");
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getHeight());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateBlock().getHeight()));
+ column.setSortType(TableColumn.SortType.DESCENDING);
+ tableView.getSortOrder().add(column);
+ tableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getHashTableHeader());
+ column.setMinWidth(120);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getHash());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(BLI::getHash));
+ tableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
+ column.setMinWidth(120);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+
+ @Override
+ public TableCell call(TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getPrevHash());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(BLI::getPrevHash));
+ tableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getPeersTableHeader());
+ column.setMinWidth(80);
+ column.setMaxWidth(column.getMinWidth());
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumNetworkMessages());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateBlock().getPeersMap().size()));
+ tableView.getColumns().add(column);
+
+ column = new AutoTooltipTableColumn<>(getConflictsTableHeader());
+ column.setMinWidth(80);
+ column.setMaxWidth(column.getMinWidth());
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumMisMatches());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateBlock().getInConflictMap().size()));
+ tableView.getColumns().add(column);
+
+ column = new AutoTooltipTableColumn<>("");
+ column.setMinWidth(40);
+ column.setMaxWidth(column.getMinWidth());
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final BLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null && !empty) {
+ Label icon;
+ if (!item.getStateBlock().getPeersMap().isEmpty()) {
+ if (item.isInSync()) {
+ icon = FormBuilder.getIcon(AwesomeIcon.OK_CIRCLE);
+ icon.getStyleClass().addAll("icon", "dao-inSync");
+ } else {
+ icon = FormBuilder.getIcon(AwesomeIcon.REMOVE_CIRCLE);
+ icon.getStyleClass().addAll("icon", "dao-inConflict");
+ }
+ setGraphic(icon);
+ } else {
+ setGraphic(null);
+ }
+ } else {
+ setGraphic(null);
+ }
+ }
+ };
+ }
+ });
+ column.setSortable(false);
+ tableView.getColumns().add(column);
+ }
+
+
+ protected void createConflictColumns() {
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(getBlockHeightTableHeader());
+ column.setMinWidth(120);
+ column.getStyleClass().add("first-column");
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final CLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getHeight());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateHash().getHeight()));
+ column.setSortType(TableColumn.SortType.DESCENDING);
+ conflictTableView.getColumns().add(column);
+ conflictTableView.getSortOrder().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getPeersTableHeader());
+ column.setMinWidth(80);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final CLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getPeerAddress());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(CLI::getPeerAddress));
+ conflictTableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getHashTableHeader());
+ column.setMinWidth(150);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final CLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getHash());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(CLI::getHash));
+ conflictTableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>(getPrevHashTableHeader());
+ column.setMinWidth(150);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(final CLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getPrevHash());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(CLI::getPrevHash));
+ conflictTableView.getColumns().add(column);
+
+
+ column = new AutoTooltipTableColumn<>("");
+ column.setMinWidth(100);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ Button button;
+
+ @Override
+ public void updateItem(final CLI item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null && !empty) {
+ if (button == null) {
+ button = new AutoTooltipButton(getRequestHashes());
+ setGraphic(button);
+ }
+ button.setOnAction(e -> requestHashesFromGenesisBlockHeight(item.getPeerAddress()));
+ } else {
+ setGraphic(null);
+ if (button != null) {
+ button.setOnAction(null);
+ button = null;
+ }
+ }
+ }
+ };
+ }
+ });
+ column.setSortable(false);
+ conflictTableView.getColumns().add(column);
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateBlockListItem.java
new file mode 100644
index 00000000000..870ab9407c6
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateBlockListItem.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.blindvotes;
+
+import bisq.desktop.main.dao.monitor.StateBlockListItem;
+
+import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
+import bisq.core.dao.monitoring.model.BlindVoteStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class BlindVoteStateBlockListItem extends StateBlockListItem {
+ private final String numBlindVotes;
+
+ BlindVoteStateBlockListItem(BlindVoteStateBlock stateBlock, int cycleIndex) {
+ super(stateBlock, cycleIndex);
+
+ numBlindVotes = String.valueOf(stateBlock.getNumBlindVotes());
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateInConflictListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateInConflictListItem.java
new file mode 100644
index 00000000000..a620675e8f3
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateInConflictListItem.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.blindvotes;
+
+import bisq.desktop.main.dao.monitor.StateInConflictListItem;
+
+import bisq.core.dao.monitoring.model.BlindVoteStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class BlindVoteStateInConflictListItem extends StateInConflictListItem {
+ private final String numBlindVotes;
+
+ BlindVoteStateInConflictListItem(String peerAddress, BlindVoteStateHash stateHash, int cycleIndex) {
+ super(peerAddress, stateHash, cycleIndex);
+
+ numBlindVotes = String.valueOf(stateHash.getNumBlindVotes());
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.fxml
new file mode 100644
index 00000000000..64a9a02ff9a
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.fxml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java
new file mode 100644
index 00000000000..48b37d4b114
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/blindvotes/BlindVoteStateMonitorView.java
@@ -0,0 +1,257 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.blindvotes;
+
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.components.AutoTooltipTableColumn;
+import bisq.desktop.main.dao.monitor.StateMonitorView;
+import bisq.desktop.main.overlays.popups.Popup;
+import bisq.desktop.util.FormBuilder;
+
+import bisq.core.dao.DaoFacade;
+import bisq.core.dao.governance.period.CycleService;
+import bisq.core.dao.governance.period.PeriodService;
+import bisq.core.dao.monitoring.BlindVoteStateMonitoringService;
+import bisq.core.dao.monitoring.model.BlindVoteStateBlock;
+import bisq.core.dao.monitoring.model.BlindVoteStateHash;
+import bisq.core.dao.state.DaoStateService;
+import bisq.core.locale.Res;
+
+import javax.inject.Inject;
+
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+import javafx.util.Callback;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@FxmlView
+public class BlindVoteStateMonitorView extends StateMonitorView
+ implements BlindVoteStateMonitoringService.Listener {
+ private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, lifecycle
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Inject
+ private BlindVoteStateMonitorView(DaoStateService daoStateService,
+ DaoFacade daoFacade,
+ BlindVoteStateMonitoringService blindVoteStateMonitoringService,
+ CycleService cycleService,
+ PeriodService periodService) {
+ super(daoStateService, daoFacade, cycleService, periodService);
+
+ this.blindVoteStateMonitoringService = blindVoteStateMonitoringService;
+ }
+
+ @Override
+ public void initialize() {
+ FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.blindVote.headline"));
+
+ statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
+ Res.get("dao.monitor.state")).second;
+ resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
+
+ super.initialize();
+ }
+
+ @Override
+ protected void activate() {
+ super.activate();
+ blindVoteStateMonitoringService.addListener(this);
+
+ resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
+ new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
+ .useShutDownButton()
+ .hideCloseButton()
+ .show())
+ );
+ }
+
+ @Override
+ protected void deactivate() {
+ super.deactivate();
+ blindVoteStateMonitoringService.removeListener(this);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // BlindVoteStateMonitoringService.Listener
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void onBlindVoteStateBlockChainChanged() {
+ if (daoStateService.isParseBlockChainComplete()) {
+ onDataUpdate();
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implementation abstract methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected BlindVoteStateBlockListItem getStateBlockListItem(BlindVoteStateBlock daoStateBlock) {
+ int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new BlindVoteStateBlockListItem(daoStateBlock, cycleIndex);
+ }
+
+ @Override
+ protected BlindVoteStateInConflictListItem getStateInConflictListItem(Map.Entry mapEntry) {
+ BlindVoteStateHash blindVoteStateHash = mapEntry.getValue();
+ int cycleIndex = periodService.getCycle(blindVoteStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new BlindVoteStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex);
+ }
+
+ @Override
+ protected String getTableHeadLine() {
+ return Res.get("dao.monitor.blindVote.table.headline");
+ }
+
+ @Override
+ protected String getConflictTableHeadLine() {
+ return Res.get("dao.monitor.blindVote.conflictTable.headline");
+ }
+
+ @Override
+ protected String getConflictsTableHeader() {
+ return Res.get("dao.monitor.table.conflicts");
+ }
+
+ @Override
+ protected String getPeersTableHeader() {
+ return Res.get("dao.monitor.table.peers");
+ }
+
+ @Override
+ protected String getPrevHashTableHeader() {
+ return Res.get("dao.monitor.blindVote.table.prev");
+ }
+
+ @Override
+ protected String getHashTableHeader() {
+ return Res.get("dao.monitor.blindVote.table.hash");
+ }
+
+ @Override
+ protected String getBlockHeightTableHeader() {
+ return Res.get("dao.monitor.table.header.cycleBlockHeight");
+ }
+
+ @Override
+ protected String getRequestHashes() {
+ return Res.get("dao.monitor.requestAlHashes");
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Override
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void onDataUpdate() {
+ isInConflict.set(blindVoteStateMonitoringService.isInConflict());
+
+ if (isInConflict.get()) {
+ statusTextField.setText(Res.get("dao.monitor.blindVote.daoStateNotInSync"));
+ statusTextField.getStyleClass().add("dao-inConflict");
+ } else {
+ statusTextField.setText(Res.get("dao.monitor.blindVote.daoStateInSync"));
+ statusTextField.getStyleClass().remove("dao-inConflict");
+ }
+
+ listItems.setAll(blindVoteStateMonitoringService.getBlindVoteStateBlockChain().stream()
+ .map(this::getStateBlockListItem)
+ .collect(Collectors.toList()));
+
+ super.onDataUpdate();
+ }
+
+ @Override
+ protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
+ blindVoteStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
+ }
+
+ @Override
+ protected void createColumns() {
+ super.createColumns();
+
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
+ column.setMinWidth(110);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(BlindVoteStateBlockListItem item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumBlindVotes());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumBlindVotes()));
+ tableView.getColumns().add(1, column);
+ }
+
+
+ protected void createConflictColumns() {
+ super.createConflictColumns();
+
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.blindVote.table.numBlindVotes"));
+ column.setMinWidth(110);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(BlindVoteStateInConflictListItem item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumBlindVotes());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumBlindVotes()));
+ conflictTableView.getColumns().add(1, column);
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java
new file mode 100644
index 00000000000..d4ec7fdd369
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateBlockListItem.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.daostate;
+
+import bisq.desktop.main.dao.monitor.StateBlockListItem;
+
+import bisq.core.dao.monitoring.model.DaoStateBlock;
+import bisq.core.dao.monitoring.model.DaoStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class DaoStateBlockListItem extends StateBlockListItem {
+ DaoStateBlockListItem(DaoStateBlock stateBlock, int cycleIndex) {
+ super(stateBlock, cycleIndex);
+ }
+}
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateInConflictListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateInConflictListItem.java
new file mode 100644
index 00000000000..1496d97d6b5
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateInConflictListItem.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.daostate;
+
+import bisq.desktop.main.dao.monitor.StateInConflictListItem;
+
+import bisq.core.dao.monitoring.model.DaoStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class DaoStateInConflictListItem extends StateInConflictListItem {
+ DaoStateInConflictListItem(String peerAddress, DaoStateHash stateHash, int cycleIndex) {
+ super(peerAddress, stateHash, cycleIndex);
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.fxml
new file mode 100644
index 00000000000..c68fbf5c5bf
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.fxml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.java
new file mode 100644
index 00000000000..e5b9cb79d1e
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/daostate/DaoStateMonitorView.java
@@ -0,0 +1,188 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.daostate;
+
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.main.dao.monitor.StateMonitorView;
+import bisq.desktop.main.overlays.popups.Popup;
+import bisq.desktop.util.FormBuilder;
+
+import bisq.core.dao.DaoFacade;
+import bisq.core.dao.governance.period.CycleService;
+import bisq.core.dao.governance.period.PeriodService;
+import bisq.core.dao.monitoring.DaoStateMonitoringService;
+import bisq.core.dao.monitoring.model.DaoStateBlock;
+import bisq.core.dao.monitoring.model.DaoStateHash;
+import bisq.core.dao.state.DaoStateService;
+import bisq.core.locale.Res;
+
+import javax.inject.Inject;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@FxmlView
+public class DaoStateMonitorView extends StateMonitorView
+ implements DaoStateMonitoringService.Listener {
+ private final DaoStateMonitoringService daoStateMonitoringService;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, lifecycle
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Inject
+ private DaoStateMonitorView(DaoStateService daoStateService,
+ DaoFacade daoFacade,
+ DaoStateMonitoringService daoStateMonitoringService,
+ CycleService cycleService,
+ PeriodService periodService) {
+ super(daoStateService, daoFacade, cycleService, periodService);
+
+ this.daoStateMonitoringService = daoStateMonitoringService;
+ }
+
+ @Override
+ public void initialize() {
+ FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.daoState.headline"));
+
+ statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
+ Res.get("dao.monitor.state")).second;
+ resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
+
+ super.initialize();
+ }
+
+ @Override
+ protected void activate() {
+ super.activate();
+ daoStateMonitoringService.addListener(this);
+
+ resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
+ new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
+ .useShutDownButton()
+ .hideCloseButton()
+ .show())
+ );
+ }
+
+ @Override
+ protected void deactivate() {
+ super.deactivate();
+ daoStateMonitoringService.removeListener(this);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // DaoStateMonitoringService.Listener
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void onChangeAfterBatchProcessing() {
+ if (daoStateService.isParseBlockChainComplete()) {
+ onDataUpdate();
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implementation abstract methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected DaoStateBlockListItem getStateBlockListItem(DaoStateBlock daoStateBlock) {
+ int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new DaoStateBlockListItem(daoStateBlock, cycleIndex);
+ }
+
+ @Override
+ protected DaoStateInConflictListItem getStateInConflictListItem(Map.Entry mapEntry) {
+ DaoStateHash daoStateHash = mapEntry.getValue();
+ int cycleIndex = periodService.getCycle(daoStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new DaoStateInConflictListItem(mapEntry.getKey(), daoStateHash, cycleIndex);
+ }
+
+ @Override
+ protected String getTableHeadLine() {
+ return Res.get("dao.monitor.daoState.table.headline");
+ }
+
+ @Override
+ protected String getConflictTableHeadLine() {
+ return Res.get("dao.monitor.daoState.conflictTable.headline");
+ }
+
+ @Override
+ protected String getConflictsTableHeader() {
+ return Res.get("dao.monitor.table.conflicts");
+ }
+
+ @Override
+ protected String getPeersTableHeader() {
+ return Res.get("dao.monitor.table.peers");
+ }
+
+ @Override
+ protected String getPrevHashTableHeader() {
+ return Res.get("dao.monitor.daoState.table.prev");
+ }
+
+ @Override
+ protected String getHashTableHeader() {
+ return Res.get("dao.monitor.daoState.table.hash");
+ }
+
+ @Override
+ protected String getBlockHeightTableHeader() {
+ return Res.get("dao.monitor.daoState.table.blockHeight");
+ }
+
+ @Override
+ protected String getRequestHashes() {
+ return Res.get("dao.monitor.requestAlHashes");
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Override
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void onDataUpdate() {
+ isInConflict.set(daoStateMonitoringService.isInConflict());
+
+ if (isInConflict.get()) {
+ statusTextField.setText(Res.get("dao.monitor.daoState.daoStateNotInSync"));
+ statusTextField.getStyleClass().add("dao-inConflict");
+ } else {
+ statusTextField.setText(Res.get("dao.monitor.daoState.daoStateInSync"));
+ statusTextField.getStyleClass().remove("dao-inConflict");
+ }
+
+ listItems.setAll(daoStateMonitoringService.getDaoStateBlockChain().stream()
+ .map(this::getStateBlockListItem)
+ .collect(Collectors.toList()));
+
+ super.onDataUpdate();
+ }
+
+ @Override
+ protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
+ daoStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateBlockListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateBlockListItem.java
new file mode 100644
index 00000000000..abb47480da9
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateBlockListItem.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.proposals;
+
+import bisq.desktop.main.dao.monitor.StateBlockListItem;
+
+import bisq.core.dao.monitoring.model.ProposalStateBlock;
+import bisq.core.dao.monitoring.model.ProposalStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class ProposalStateBlockListItem extends StateBlockListItem {
+ private final String numProposals;
+
+ ProposalStateBlockListItem(ProposalStateBlock stateBlock, int cycleIndex) {
+ super(stateBlock, cycleIndex);
+
+ numProposals = String.valueOf(stateBlock.getNumProposals());
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateInConflictListItem.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateInConflictListItem.java
new file mode 100644
index 00000000000..af006fa6aab
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateInConflictListItem.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.proposals;
+
+import bisq.desktop.main.dao.monitor.StateInConflictListItem;
+
+import bisq.core.dao.monitoring.model.ProposalStateHash;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Value
+@EqualsAndHashCode(callSuper = true)
+class ProposalStateInConflictListItem extends StateInConflictListItem {
+ private final String numProposals;
+
+ ProposalStateInConflictListItem(String peerAddress, ProposalStateHash stateHash, int cycleIndex) {
+ super(peerAddress, stateHash, cycleIndex);
+
+ numProposals = String.valueOf(stateHash.getNumProposals());
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.fxml b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.fxml
new file mode 100644
index 00000000000..37124f77f82
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.fxml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java
new file mode 100644
index 00000000000..2f0156fe2d8
--- /dev/null
+++ b/desktop/src/main/java/bisq/desktop/main/dao/monitor/proposals/ProposalStateMonitorView.java
@@ -0,0 +1,257 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.main.dao.monitor.proposals;
+
+import bisq.desktop.common.view.FxmlView;
+import bisq.desktop.components.AutoTooltipTableColumn;
+import bisq.desktop.main.dao.monitor.StateMonitorView;
+import bisq.desktop.main.overlays.popups.Popup;
+import bisq.desktop.util.FormBuilder;
+
+import bisq.core.dao.DaoFacade;
+import bisq.core.dao.governance.period.CycleService;
+import bisq.core.dao.governance.period.PeriodService;
+import bisq.core.dao.monitoring.ProposalStateMonitoringService;
+import bisq.core.dao.monitoring.model.ProposalStateBlock;
+import bisq.core.dao.monitoring.model.ProposalStateHash;
+import bisq.core.dao.state.DaoStateService;
+import bisq.core.locale.Res;
+
+import javax.inject.Inject;
+
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+import javafx.util.Callback;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@FxmlView
+public class ProposalStateMonitorView extends StateMonitorView
+ implements ProposalStateMonitoringService.Listener {
+ private final ProposalStateMonitoringService proposalStateMonitoringService;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Constructor, lifecycle
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+
+ @Inject
+ private ProposalStateMonitorView(DaoStateService daoStateService,
+ DaoFacade daoFacade,
+ ProposalStateMonitoringService proposalStateMonitoringService,
+ CycleService cycleService,
+ PeriodService periodService) {
+ super(daoStateService, daoFacade, cycleService, periodService);
+
+ this.proposalStateMonitoringService = proposalStateMonitoringService;
+ }
+
+ @Override
+ public void initialize() {
+ FormBuilder.addTitledGroupBg(root, gridRow, 3, Res.get("dao.monitor.proposal.headline"));
+
+ statusTextField = FormBuilder.addTopLabelTextField(root, ++gridRow,
+ Res.get("dao.monitor.state")).second;
+ resyncButton = FormBuilder.addButton(root, ++gridRow, Res.get("dao.monitor.resync"), 10);
+
+ super.initialize();
+ }
+
+ @Override
+ protected void activate() {
+ super.activate();
+ proposalStateMonitoringService.addListener(this);
+
+ resyncButton.setOnAction(e -> daoFacade.resyncDao(() ->
+ new Popup<>().attention(Res.get("setting.preferences.dao.resync.popup"))
+ .useShutDownButton()
+ .hideCloseButton()
+ .show())
+ );
+ }
+
+ @Override
+ protected void deactivate() {
+ super.deactivate();
+ proposalStateMonitoringService.removeListener(this);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // ProposalStateMonitoringService.Listener
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void onProposalStateBlockChainChanged() {
+ if (daoStateService.isParseBlockChainComplete()) {
+ onDataUpdate();
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Implementation abstract methods
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected ProposalStateBlockListItem getStateBlockListItem(ProposalStateBlock daoStateBlock) {
+ int cycleIndex = periodService.getCycle(daoStateBlock.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new ProposalStateBlockListItem(daoStateBlock, cycleIndex);
+ }
+
+ @Override
+ protected ProposalStateInConflictListItem getStateInConflictListItem(Map.Entry mapEntry) {
+ ProposalStateHash proposalStateHash = mapEntry.getValue();
+ int cycleIndex = periodService.getCycle(proposalStateHash.getHeight()).map(cycleService::getCycleIndex).orElse(0);
+ return new ProposalStateInConflictListItem(mapEntry.getKey(), mapEntry.getValue(), cycleIndex);
+ }
+
+ @Override
+ protected String getTableHeadLine() {
+ return Res.get("dao.monitor.proposal.table.headline");
+ }
+
+ @Override
+ protected String getConflictTableHeadLine() {
+ return Res.get("dao.monitor.proposal.conflictTable.headline");
+ }
+
+ @Override
+ protected String getConflictsTableHeader() {
+ return Res.get("dao.monitor.table.conflicts");
+ }
+
+ @Override
+ protected String getPeersTableHeader() {
+ return Res.get("dao.monitor.table.peers");
+ }
+
+ @Override
+ protected String getPrevHashTableHeader() {
+ return Res.get("dao.monitor.proposal.table.prev");
+ }
+
+ @Override
+ protected String getHashTableHeader() {
+ return Res.get("dao.monitor.proposal.table.hash");
+ }
+
+ @Override
+ protected String getBlockHeightTableHeader() {
+ return Res.get("dao.monitor.table.header.cycleBlockHeight");
+ }
+
+ @Override
+ protected String getRequestHashes() {
+ return Res.get("dao.monitor.requestAlHashes");
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Override
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void onDataUpdate() {
+ isInConflict.set(proposalStateMonitoringService.isInConflict());
+
+ if (isInConflict.get()) {
+ statusTextField.setText(Res.get("dao.monitor.proposal.daoStateNotInSync"));
+ statusTextField.getStyleClass().add("dao-inConflict");
+ } else {
+ statusTextField.setText(Res.get("dao.monitor.proposal.daoStateInSync"));
+ statusTextField.getStyleClass().remove("dao-inConflict");
+ }
+
+ listItems.setAll(proposalStateMonitoringService.getProposalStateBlockChain().stream()
+ .map(this::getStateBlockListItem)
+ .collect(Collectors.toList()));
+
+ super.onDataUpdate();
+ }
+
+ @Override
+ protected void requestHashesFromGenesisBlockHeight(String peerAddress) {
+ proposalStateMonitoringService.requestHashesFromGenesisBlockHeight(peerAddress);
+ }
+
+ @Override
+ protected void createColumns() {
+ super.createColumns();
+
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
+ column.setMinWidth(110);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(ProposalStateBlockListItem item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumProposals());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateBlock().getMyStateHash().getNumProposals()));
+ tableView.getColumns().add(1, column);
+ }
+
+
+ protected void createConflictColumns() {
+ super.createConflictColumns();
+
+ TableColumn column;
+
+ column = new AutoTooltipTableColumn<>(Res.get("dao.monitor.proposal.table.numProposals"));
+ column.setMinWidth(110);
+ column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue()));
+ column.setCellFactory(
+ new Callback<>() {
+ @Override
+ public TableCell call(
+ TableColumn column) {
+ return new TableCell<>() {
+ @Override
+ public void updateItem(ProposalStateInConflictListItem item, boolean empty) {
+ super.updateItem(item, empty);
+ if (item != null)
+ setText(item.getNumProposals());
+ else
+ setText("");
+ }
+ };
+ }
+ });
+ column.setComparator(Comparator.comparing(e -> e.getStateHash().getNumProposals()));
+ conflictTableView.getColumns().add(1, column);
+ }
+}
diff --git a/p2p/src/main/java/bisq/network/p2p/network/Connection.java b/p2p/src/main/java/bisq/network/p2p/network/Connection.java
index 6ea6467512d..ddb327875f7 100644
--- a/p2p/src/main/java/bisq/network/p2p/network/Connection.java
+++ b/p2p/src/main/java/bisq/network/p2p/network/Connection.java
@@ -297,7 +297,9 @@ public boolean noCapabilityRequiredOrCapabilityIsSupported(Proto msg) {
}
if (!result)
- log.info("We did not send the message because the peer does not support our required capabilities. message={}, peers supportedCapabilities={}", msg, capabilities);
+ log.info("We did not send the message because the peer does not support our required capabilities. " +
+ "message={}, peer={}, peers supportedCapabilities={}",
+ msg, peersNodeAddressOptional, capabilities);
return result;
}
diff --git a/p2p/src/main/resources/BlindVoteStore_BTC_DAO_TESTNET b/p2p/src/main/resources/BlindVoteStore_BTC_DAO_TESTNET
index 9f04c091681..e8136c63054 100644
Binary files a/p2p/src/main/resources/BlindVoteStore_BTC_DAO_TESTNET and b/p2p/src/main/resources/BlindVoteStore_BTC_DAO_TESTNET differ
diff --git a/p2p/src/main/resources/DaoStateStore2_BTC_DAO_TESTNET b/p2p/src/main/resources/DaoStateStore2_BTC_DAO_TESTNET
new file mode 100644
index 00000000000..909f2dbc7e8
Binary files /dev/null and b/p2p/src/main/resources/DaoStateStore2_BTC_DAO_TESTNET differ
diff --git a/p2p/src/main/resources/DaoStateStore_BTC_DAO_TESTNET b/p2p/src/main/resources/DaoStateStore_BTC_DAO_TESTNET
deleted file mode 100644
index 7a1bc0eaee2..00000000000
Binary files a/p2p/src/main/resources/DaoStateStore_BTC_DAO_TESTNET and /dev/null differ
diff --git a/p2p/src/main/resources/ProposalStore_BTC_DAO_TESTNET b/p2p/src/main/resources/ProposalStore_BTC_DAO_TESTNET
index 67f9fd5759b..f80d555d880 100644
Binary files a/p2p/src/main/resources/ProposalStore_BTC_DAO_TESTNET and b/p2p/src/main/resources/ProposalStore_BTC_DAO_TESTNET differ