From 4020b85f660ff9840c019ff20632700227c30175 Mon Sep 17 00:00:00 2001 From: Niyi Dada Date: Sat, 19 Oct 2019 02:41:37 +0100 Subject: [PATCH] Added interface and corresponding implementation to handle display of exceptions on failed vote reveal transaction - fixes issue 3408. Allow "do not show again". --- .../votereveal/VoteRevealService.java | 56 +++++++++++-------- .../resources/i18n/displayStrings.properties | 3 + .../java/bisq/desktop/main/dao/DaoView.java | 10 ++++ 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java index e7b1706011f..f2e4c7d63fe 100644 --- a/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java +++ b/core/src/main/java/bisq/core/dao/governance/votereveal/VoteRevealService.java @@ -69,21 +69,17 @@ @Slf4j public class VoteRevealService implements DaoStateListener, DaoSetupService { - public interface VoteRevealTxPublishedListener { - void onVoteRevealTxPublished(String txId); - } - private final DaoStateService daoStateService; private final BlindVoteListService blindVoteListService; private final PeriodService periodService; private final MyVoteListService myVoteListService; private final BsqWalletService bsqWalletService; private final BtcWalletService btcWalletService; - private final WalletsManager walletsManager; - + private final WalletsManager walletsManager; @Getter private final ObservableList voteRevealExceptions = FXCollections.observableArrayList(); private final List voteRevealTxPublishedListeners = new ArrayList<>(); + private final List voteRevealTxFailedListeners = new ArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -106,7 +102,6 @@ public VoteRevealService(DaoStateService daoStateService, this.walletsManager = walletsManager; } - /////////////////////////////////////////////////////////////////////////////////////////// // DaoSetupService /////////////////////////////////////////////////////////////////////////////////////////// @@ -125,11 +120,6 @@ public void addListeners() { public void start() { } - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - private byte[] getHashOfBlindVoteList() { List blindVotes = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService); byte[] hashOfBlindVoteList = VoteRevealConsensus.getHashOfBlindVoteList(blindVotes); @@ -144,25 +134,24 @@ public void addVoteRevealTxPublishedListener(VoteRevealTxPublishedListener voteR /////////////////////////////////////////////////////////////////////////////////////////// - // DaoStateListener + // API /////////////////////////////////////////////////////////////////////////////////////////// + public void addVoteRevealTxFailedListener(VoteRevealTxFailedListener voteRevealTxFailedListener) { + voteRevealTxFailedListeners.add(voteRevealTxFailedListener); + } + @Override public void onParseBlockCompleteAfterBatchProcessing(Block block) { maybeRevealVotes(block.getHeight()); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - // Creation of vote reveal tx is done without user activity! // We create automatically the vote reveal tx when we are in the reveal phase of the current cycle when // the blind vote was created in case we have not done it already. - // The voter needs to be at least once online in the reveal phase when he has a blind vote created, + // The voter need to be at least once online in the reveal phase when he has a blind vote created, // otherwise his vote becomes invalid. - // In case the user misses the vote reveal phase an (invalid) vote reveal tx will be created the next time the user is + // In case the user miss the vote reveal phase an (invalid) vote reveal tx will be created the next time the user is // online. That tx only serves the purpose to unlock the stake from the blind vote but it will be ignored for voting. // A blind vote which did not get revealed might still be part of the majority hash calculation as we cannot know // which blind votes might be revealed until the phase is over at the moment when we publish the vote reveal tx. @@ -198,7 +187,7 @@ private void maybeRevealVotes(int chainHeight) { // BSQ because the blind vote tx is already in the snapshot and does not get parsed // again. It would require a reset of the snapshot and parse all blocks again. // As this is an exceptional case we prefer to have a simple solution instead and just - // publish the vote reveal tx but are aware that it is invalid. + // publish the vote reveal tx but are aware that is is invalid. log.warn("We missed the vote reveal phase but publish now the tx to unlock our locked " + "BSQ from the blind vote tx. BlindVoteTxId={}, blockHeight={}", blindVoteTxId, chainHeight); @@ -212,6 +201,11 @@ private void maybeRevealVotes(int chainHeight) { }); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoStateListener + /////////////////////////////////////////////////////////////////////////////////////////// + private void revealVote(MyVote myVote, boolean isInVoteRevealPhase) { try { // We collect all valid blind vote items we received via the p2p network. @@ -238,7 +232,7 @@ private void revealVote(MyVote myVote, boolean isInVoteRevealPhase) { publishTx(voteRevealTx); // We don't want to wait for a successful broadcast to avoid issues if the broadcast succeeds delayed or at - // next startup but the tx was actually broadcast. + // next startup but the tx was actually broadcasted. myVoteListService.applyRevealTxId(myVote, voteRevealTx.getHashAsString()); } catch (IOException | WalletException | TransactionVerificationException | InsufficientMoneyException e) { @@ -247,13 +241,21 @@ private void revealVote(MyVote myVote, boolean isInVoteRevealPhase) { } catch (VoteRevealException e) { voteRevealExceptions.add(e); } + + //Display vote reveal exceptions + voteRevealExceptions.forEach(e -> voteRevealTxFailedListeners.forEach(l -> l.onVoteRevealTxFailed(e))); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + private void publishTx(Transaction voteRevealTx) { walletsManager.publishAndCommitBsqTx(voteRevealTx, TxType.VOTE_REVEAL, new TxBroadcaster.Callback() { @Override public void onSuccess(Transaction transaction) { - log.info("voteRevealTx successfully broadcast."); + log.info("voteRevealTx successfully broadcasted."); voteRevealTxPublishedListeners.forEach(l -> l.onVoteRevealTxPublished(transaction.getHashAsString())); } @@ -272,4 +274,12 @@ private Transaction getVoteRevealTx(TxOutput stakeTxOutput, byte[] opReturnData) Transaction txWithBtcFee = btcWalletService.completePreparedVoteRevealTx(preparedTx, opReturnData); return bsqWalletService.signTx(txWithBtcFee); } + + public interface VoteRevealTxPublishedListener { + void onVoteRevealTxPublished(String txId); + } + + public interface VoteRevealTxFailedListener { + void onVoteRevealTxFailed(VoteRevealException exception); + } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 0f3f1278df3..85474374b11 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1619,6 +1619,9 @@ dao.cycle.phaseDurationWithoutBlocks=Block {0} - {1} (≈{2} - ≈{3}) dao.voteReveal.txPublished.headLine=Vote reveal transaction published dao.voteReveal.txPublished=Your vote reveal transaction with transaction ID {0} was successfully published.\n\n\ This happens automatically by the software if you have participated in the DAO voting. +dao.voteReveal.txFailed.headLine=Vote reveal transaction failed +dao.voteReveal.txFailed=Your vote reveal transaction with transaction ID {0} failed.\n\n\ + Reason was: {1}. dao.results.cycles.header=Cycles dao.results.cycles.table.header.cycle=Cycle 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 7d34574c6ed..60c8b662955 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/DaoView.java @@ -78,6 +78,16 @@ private DaoView(CachingViewLoader viewLoader, VoteRevealService voteRevealServic .feedback(Res.get("dao.voteReveal.txPublished", txId)) .show(); }); + + voteRevealService.addVoteRevealTxFailedListener((exception) -> { + String key = "showVoteRevealFailedWarnPopupOnStartup" + exception.getBlindVoteTxId(); + if (preferences.showAgain(key)) { + new Popup().headLine(Res.get("dao.voteReveal.txFailed.headLine")) + .dontShowAgainId(key) + .error(Res.get("dao.voteReveal.txFailed", exception.getBlindVoteTxId(), exception.getLocalizedMessage())) + .show(); + } + }); } @Override