From 76a41a19490062fdcbfbfb7445021757d60cdda0 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 22 Jan 2019 18:31:13 +0100 Subject: [PATCH 1/5] Fix incorrect exception handling --- .../voteresult/VoteResultConsensus.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java index a69b8d98e91..18c1c082927 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultConsensus.java @@ -71,24 +71,26 @@ public static byte[] getMajorityHash(List hashW throws VoteResultException.ConsensusException, VoteResultException.ValidationException { try { checkArgument(!hashWithStakeList.isEmpty(), "hashWithStakeList must not be empty"); - hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed() - .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash()))); - - // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if - // the majority is a super majority with > 80%. - if (hashWithStakeList.size() > 1) { - long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum(); - long stakeOfFirst = hashWithStakeList.get(0).getStake(); - if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) { - throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " + - "total stake of all data views. We consider the voting cycle as invalid if the " + - "winning data view does not reach a super majority."); - } - } - return hashWithStakeList.get(0).getHash(); } catch (Throwable t) { throw new VoteResultException.ValidationException(t); } + + hashWithStakeList.sort(Comparator.comparingLong(VoteResultService.HashWithStake::getStake).reversed() + .thenComparing(hashWithStake -> Utilities.encodeToHex(hashWithStake.getHash()))); + + // If there are conflicting data views (multiple hashes) we only consider the voting round as valid if + // the majority is a super majority with > 80%. + if (hashWithStakeList.size() > 1) { + long stakeOfAll = hashWithStakeList.stream().mapToLong(VoteResultService.HashWithStake::getStake).sum(); + long stakeOfFirst = hashWithStakeList.get(0).getStake(); + if ((double) stakeOfFirst / (double) stakeOfAll < 0.8) { + log.warn("hashWithStakeList " + hashWithStakeList); + throw new VoteResultException.ConsensusException("The winning data view has less then 80% of the " + + "total stake of all data views. We consider the voting cycle as invalid if the " + + "winning data view does not reach a super majority."); + } + } + return hashWithStakeList.get(0).getHash(); } // Key is stored after version and type bytes and list of Blind votes. It has 16 bytes From ce77412903b136737c6d576e4e50361c07922221 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 29 Jan 2019 11:50:52 +0100 Subject: [PATCH 2/5] Use warn and error log level if appropriate --- .../java/bisq/core/app/BisqHeadlessApp.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index 3bb23f40f2b..f7ece906fb7 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -78,26 +78,26 @@ protected void setupHandlers() { log.info("onDisplayTacHandler: We accept the tacs automatically in headless mode"); acceptedHandler.run(); }); - bisqSetup.setCryptoSetupFailedHandler(msg -> log.info("onCryptoSetupFailedHandler: msg={}", msg)); + bisqSetup.setCryptoSetupFailedHandler(msg -> log.error("onCryptoSetupFailedHandler: msg={}", msg)); bisqSetup.setDisplayTorNetworkSettingsHandler(show -> log.info("onDisplayTorNetworkSettingsHandler: show={}", show)); - bisqSetup.setSpvFileCorruptedHandler(msg -> log.info("onSpvFileCorruptedHandler: msg={}", msg)); - bisqSetup.setChainFileLockedExceptionHandler(msg -> log.info("onChainFileLockedExceptionHandler: msg={}", msg)); + bisqSetup.setSpvFileCorruptedHandler(msg -> log.error("onSpvFileCorruptedHandler: msg={}", msg)); + bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg)); bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg)); bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler")); bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> log.info("onRequestWalletPasswordHandler")); bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler")); bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert)); bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification)); - bisqSetup.setDaoErrorMessageHandler(errorMessage -> log.info("onDaoErrorMessageHandler. errorMessage={}", errorMessage)); - bisqSetup.setDaoWarnMessageHandler(warnMessage -> log.info("onDaoWarnMessageHandler. warnMessage={}", warnMessage)); + bisqSetup.setDaoErrorMessageHandler(errorMessage -> log.error("onDaoErrorMessageHandler. errorMessage={}", errorMessage)); + bisqSetup.setDaoWarnMessageHandler(warnMessage -> log.warn("onDaoWarnMessageHandler. warnMessage={}", warnMessage)); bisqSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler")); bisqSetup.setDisplayLocalhostHandler(key -> log.info("onDisplayLocalhostHandler")); - bisqSetup.setWrongOSArchitectureHandler(msg -> log.info("onWrongOSArchitectureHandler. msg={}", msg)); - bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.info("voteResultException={}", voteResultException)); + bisqSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg)); + bisqSetup.setVoteResultExceptionHandler(voteResultException -> log.warn("voteResultException={}", voteResultException)); //TODO move to bisqSetup - corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.info("getCorruptedDatabaseFiles. files={}", files)); - tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.info("onTakeOfferRequestErrorMessageHandler")); + corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files)); + tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler")); } public void stop() { From e42c9d4f762aa1e8e35dcfa0646de1a6110ef045 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 29 Jan 2019 11:51:08 +0100 Subject: [PATCH 3/5] Don't show popup at vote result exception --- desktop/src/main/java/bisq/desktop/main/MainViewModel.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 92a568c2b71..0ebec9697e5 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -283,7 +283,9 @@ private void setupHandlers() { .show(); }); bisqSetup.setVoteResultExceptionHandler(voteResultException -> { - new Popup<>().error(voteResultException.toString()).show(); + log.warn(voteResultException.toString()); + + //new Popup<>().error(voteResultException.toString()).show(); }); bisqSetup.setChainFileLockedExceptionHandler(msg -> { From 6bf14ee881a0cf129ea30cc3e63c42a3a8993a5b Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 29 Jan 2019 11:51:29 +0100 Subject: [PATCH 4/5] Use warn log level instead of error at vote result exceptions --- .../dao/governance/voteresult/VoteResultService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java index 28f185fd78d..5b526e5fe2d 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java @@ -212,12 +212,12 @@ private void maybeCalculateVoteResult(int chainHeight) { } } catch (VoteResultException.ValidationException e) { - log.error(e.toString()); + log.warn(e.toString()); e.printStackTrace(); voteResultExceptions.add(e); } catch (VoteResultException.ConsensusException e) { - log.error(e.toString()); - log.error("decryptedBallotsWithMeritsSet " + decryptedBallotsWithMeritsSet); + log.warn(e.toString()); + log.warn("decryptedBallotsWithMeritsSet " + decryptedBallotsWithMeritsSet); e.printStackTrace(); //TODO notify application of that case (e.g. add error handler) @@ -300,7 +300,7 @@ private Set getDecryptedBallotsWithMeritsSet(int cha voteResultExceptions.add(missingBallotException); return null; } catch (VoteResultException.DecryptionException decryptionException) { - log.error("Could not decrypt data: " + decryptionException.toString()); + log.warn("Could not decrypt data: " + decryptionException.toString()); voteResultExceptions.add(decryptionException); return null; } @@ -316,7 +316,7 @@ private Set getDecryptedBallotsWithMeritsSet(int cha return null; } } catch (VoteResultException.ValidationException e) { - log.error("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString()); + log.warn("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString()); voteResultExceptions.add(e); return null; } catch (Throwable e) { From 75943052d1dcc2a397405eab812fbccd9022891a Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 29 Jan 2019 13:21:36 +0100 Subject: [PATCH 5/5] Show vote result exception in UI - Show vote result exception in Vote result View when selecting the cycle where the vote result exception happened --- .../voteresult/MissingDataRequestService.java | 19 ++--------- .../voteresult/VoteResultException.java | 29 +++++++++------- .../voteresult/VoteResultService.java | 21 +++++++----- .../resources/i18n/displayStrings.properties | 1 + .../dao/governance/result/VoteResultView.java | 34 +++++++++++++++---- 5 files changed, 59 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java index d0b47df820a..69c20433d73 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/MissingDataRequestService.java @@ -22,18 +22,9 @@ import javax.inject.Inject; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; - -import lombok.Getter; - public class MissingDataRequestService implements DaoSetupService { private final RepublishGovernanceDataHandler republishGovernanceDataHandler; - @Getter - private final ObservableList voteResultExceptions = FXCollections.observableArrayList(); - @Inject public MissingDataRequestService(RepublishGovernanceDataHandler republishGovernanceDataHandler) { this.republishGovernanceDataHandler = republishGovernanceDataHandler; @@ -46,12 +37,6 @@ public MissingDataRequestService(RepublishGovernanceDataHandler republishGoverna @Override public void addListeners() { - voteResultExceptions.addListener((ListChangeListener) c -> { - c.next(); - if (c.wasAdded()) { - republishGovernanceDataHandler.sendRepublishRequest(); - } - }); } @Override @@ -63,7 +48,7 @@ public void start() { // API /////////////////////////////////////////////////////////////////////////////////////////// - public void addVoteResultException(VoteResultException voteResultException) { - this.voteResultExceptions.add(voteResultException); + public void sendRepublishRequest() { + republishGovernanceDataHandler.sendRepublishRequest(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java index 7e2814b3dee..754f8f420c7 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultException.java @@ -18,34 +18,37 @@ package bisq.core.dao.governance.voteresult; import bisq.core.dao.state.model.governance.Ballot; +import bisq.core.dao.state.model.governance.Cycle; import java.util.List; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.Value; public class VoteResultException extends Exception { + @Getter + private final Cycle cycle; - VoteResultException(Throwable cause) { + VoteResultException(Cycle cycle, Throwable cause) { super(cause); - } - - private VoteResultException(String message) { - super(message); - } - - private VoteResultException(String message, Throwable cause) { - super(message, cause); + this.cycle = cycle; } @Override public String toString() { return "VoteResultException{" + + "\n cycle=" + cycle + "\n} " + super.toString(); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Static sub classes + /////////////////////////////////////////////////////////////////////////////////////////// + @EqualsAndHashCode(callSuper = true) - public static class ConsensusException extends VoteResultException { + public static class ConsensusException extends Exception { ConsensusException(String message) { super(message); @@ -59,7 +62,7 @@ public String toString() { } @EqualsAndHashCode(callSuper = true) - public static class ValidationException extends VoteResultException { + public static class ValidationException extends Exception { ValidationException(Throwable cause) { super("Validation of vote result failed.", cause); @@ -74,7 +77,7 @@ public String toString() { } @EqualsAndHashCode(callSuper = true) - public static abstract class MissingDataException extends VoteResultException { + public static abstract class MissingDataException extends Exception { private MissingDataException(String message) { super(message); } @@ -114,7 +117,7 @@ public static class MissingBallotException extends MissingDataException { @EqualsAndHashCode(callSuper = true) @Value - public static class DecryptionException extends VoteResultException { + public static class DecryptionException extends Exception { public DecryptionException(Throwable cause) { super(cause); } diff --git a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java index 5b526e5fe2d..3229ae515a1 100644 --- a/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java +++ b/core/src/main/java/bisq/core/dao/governance/voteresult/VoteResultService.java @@ -40,6 +40,7 @@ import bisq.core.dao.state.model.governance.BallotList; import bisq.core.dao.state.model.governance.ChangeParamProposal; import bisq.core.dao.state.model.governance.ConfiscateBondProposal; +import bisq.core.dao.state.model.governance.Cycle; import bisq.core.dao.state.model.governance.DaoPhase; import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits; import bisq.core.dao.state.model.governance.EvaluatedProposal; @@ -170,6 +171,7 @@ public void onParseBlockChainComplete() { private void maybeCalculateVoteResult(int chainHeight) { if (isInVoteResultPhase(chainHeight)) { + Cycle currentCycle = periodService.getCurrentCycle(); long startTs = System.currentTimeMillis(); Set decryptedBallotsWithMeritsSet = getDecryptedBallotsWithMeritsSet(chainHeight); decryptedBallotsWithMeritsSet.stream() @@ -214,7 +216,7 @@ private void maybeCalculateVoteResult(int chainHeight) { } catch (VoteResultException.ValidationException e) { log.warn(e.toString()); e.printStackTrace(); - voteResultExceptions.add(e); + voteResultExceptions.add(new VoteResultException(currentCycle, e)); } catch (VoteResultException.ConsensusException e) { log.warn(e.toString()); log.warn("decryptedBallotsWithMeritsSet " + decryptedBallotsWithMeritsSet); @@ -224,7 +226,7 @@ private void maybeCalculateVoteResult(int chainHeight) { // The vote cycle is invalid as conflicting data views of the blind vote data exist and the winner // did not reach super majority of 80%. - voteResultExceptions.add(e); + voteResultExceptions.add(new VoteResultException(currentCycle, e)); } } else { log.info("There have not been any votes in that cycle. chainHeight={}", chainHeight); @@ -261,6 +263,7 @@ private Set getDecryptedBallotsWithMeritsSet(int cha return null; } + Cycle currentCycle = periodService.getCurrentCycle(); try { // TODO maybe verify version in opReturn @@ -296,12 +299,12 @@ private Set getDecryptedBallotsWithMeritsSet(int cha return new DecryptedBallotsWithMerits(hashOfBlindVoteList, blindVoteTxId, voteRevealTxId, blindVoteStake, ballotList, meritList); } catch (VoteResultException.MissingBallotException missingBallotException) { log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString()); - missingDataRequestService.addVoteResultException(missingBallotException); - voteResultExceptions.add(missingBallotException); + missingDataRequestService.sendRepublishRequest(); + voteResultExceptions.add(new VoteResultException(currentCycle, missingBallotException)); return null; } catch (VoteResultException.DecryptionException decryptionException) { log.warn("Could not decrypt data: " + decryptionException.toString()); - voteResultExceptions.add(decryptionException); + voteResultExceptions.add(new VoteResultException(currentCycle, decryptionException)); return null; } } else { @@ -311,17 +314,17 @@ private Set getDecryptedBallotsWithMeritsSet(int cha "recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId); VoteResultException.MissingBlindVoteDataException voteResultException = new VoteResultException.MissingBlindVoteDataException(blindVoteTxId); - missingDataRequestService.addVoteResultException(voteResultException); - voteResultExceptions.add(voteResultException); + missingDataRequestService.sendRepublishRequest(); + voteResultExceptions.add(new VoteResultException(currentCycle, voteResultException)); return null; } } catch (VoteResultException.ValidationException e) { log.warn("Could not create DecryptedBallotsWithMerits because of voteResultValidationException: " + e.toString()); - voteResultExceptions.add(e); + voteResultExceptions.add(new VoteResultException(currentCycle, e)); return null; } catch (Throwable e) { log.error("Could not create DecryptedBallotsWithMerits because of an unknown exception: " + e.toString()); - voteResultExceptions.add(new VoteResultException(e)); + voteResultExceptions.add(new VoteResultException(currentCycle, e)); return null; } }) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4c3c27d9dcc..7582a58283d 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1221,6 +1221,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 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 562b329e961..752f828a631 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 @@ -25,18 +25,20 @@ import bisq.desktop.components.TableGroupHeadline; import bisq.desktop.main.dao.governance.PhasesView; import bisq.desktop.main.dao.governance.ProposalDisplay; +import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; -import bisq.core.btc.wallet.BsqWalletService; import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.period.CycleService; import bisq.core.dao.governance.proposal.ProposalService; +import bisq.core.dao.governance.voteresult.VoteResultException; import bisq.core.dao.governance.voteresult.VoteResultService; import bisq.core.dao.state.DaoStateListener; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.model.blockchain.Block; import bisq.core.dao.state.model.governance.Ballot; +import bisq.core.dao.state.model.governance.Cycle; import bisq.core.dao.state.model.governance.DecryptedBallotsWithMerits; import bisq.core.dao.state.model.governance.EvaluatedProposal; import bisq.core.dao.state.model.governance.Proposal; @@ -56,6 +58,7 @@ import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; +import javafx.scene.control.TextArea; import javafx.scene.control.Tooltip; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; @@ -89,7 +92,6 @@ public class VoteResultView extends ActivatableView implements D private final CycleService cycleService; private final VoteResultService voteResultService; private final ProposalService proposalService; - private final BsqWalletService bsqWalletService; private final Preferences preferences; private final BsqFormatter bsqFormatter; @@ -124,7 +126,6 @@ public VoteResultView(DaoFacade daoFacade, CycleService cycleService, VoteResultService voteResultService, ProposalService proposalService, - BsqWalletService bsqWalletService, Preferences preferences, BsqFormatter bsqFormatter) { this.daoFacade = daoFacade; @@ -133,7 +134,6 @@ public VoteResultView(DaoFacade daoFacade, this.cycleService = cycleService; this.voteResultService = voteResultService; this.proposalService = proposalService; - this.bsqWalletService = bsqWalletService; this.preferences = preferences; this.bsqFormatter = bsqFormatter; } @@ -146,7 +146,6 @@ public void initialize() { createCyclesTable(); } - @Override protected void activate() { super.activate(); @@ -207,6 +206,7 @@ private void onResultsListItemSelected(CycleListItem item) { if (item != null) { resultsOfCycle = item.getResultsOfCycle(); + maybeShowVoteResultErrors(item.getResultsOfCycle().getCycle()); createProposalsTable(); selectedProposalSubscription = EasyBind.subscribe(proposalsTableView.getSelectionModel().selectedItemProperty(), @@ -214,10 +214,32 @@ private void onResultsListItemSelected(CycleListItem item) { } } + private void maybeShowVoteResultErrors(Cycle cycle) { + List exceptions = voteResultService.getVoteResultExceptions().stream() + .filter(voteResultException -> cycle.equals(voteResultException.getCycle())) + .collect(Collectors.toList()); + if (!exceptions.isEmpty()) { + TextArea textArea = FormBuilder.addTextArea(root, ++gridRow, ""); + GridPane.setMargin(textArea, new Insets(Layout.GROUP_DISTANCE, -15, 0, -10)); + textArea.setPrefHeight(100); + + StringBuilder sb = new StringBuilder(Res.getWithCol("dao.results.exceptions") + "\n"); + exceptions.forEach(exception -> { + if (exception.getCause() != null) + sb.append(exception.getCause().getMessage()); + else + sb.append(exception.getMessage()); + sb.append("\n"); + }); + + textArea.setText(sb.toString()); + } + } + private void onSelectProposalResultListItem(ProposalListItem item) { selectedProposalListItem = item; - GUIUtil.removeChildrenFromGridPaneRows(root, 3, gridRow); + GUIUtil.removeChildrenFromGridPaneRows(root, 4, gridRow); gridRow = 2;