Merge pull request #7092 from stejbac/speed-up-vote-result-view-load

Speed up Vote Result display, cycle list item selection & JSON export
This commit is contained in:
Alejandro García 2024-05-09 18:57:53 +00:00 committed by GitHub
commit 0b85e0615d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 63 additions and 122 deletions

View File

@ -48,7 +48,6 @@ import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput; import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.TransactionOutput;
@ -60,14 +59,12 @@ import org.bitcoinj.wallet.SendRequest;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -95,6 +92,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
private final DaoStateService daoStateService; private final DaoStateService daoStateService;
private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService; private final UnconfirmedBsqChangeOutputListService unconfirmedBsqChangeOutputListService;
private final List<Transaction> walletTransactions = new ArrayList<>(); private final List<Transaction> walletTransactions = new ArrayList<>();
private Map<String, Transaction> walletTransactionsById;
private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<BsqBalanceListener> bsqBalanceListeners = new CopyOnWriteArraySet<>();
private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>(); private final List<WalletTransactionsChangeListener> walletTransactionsChangeListeners = new ArrayList<>();
private boolean updateBsqWalletTransactionsPending; private boolean updateBsqWalletTransactionsPending;
@ -233,7 +231,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
private void updateBsqBalance() { private void updateBsqBalance() {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
unverifiedBalance = Coin.valueOf( unverifiedBalance = Coin.valueOf(
getTransactions(false).stream() walletTransactions.stream()
.filter(tx -> tx.getConfidence().getConfidenceType() == PENDING) .filter(tx -> tx.getConfidence().getConfidenceType() == PENDING)
.mapToLong(tx -> { .mapToLong(tx -> {
// Sum up outputs into BSQ wallet and subtract the inputs using lockup or unlocking // Sum up outputs into BSQ wallet and subtract the inputs using lockup or unlocking
@ -270,7 +268,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
.sum() .sum()
); );
Set<String> confirmedTxIdSet = getTransactions(false).stream() Set<String> confirmedTxIdSet = walletTransactions.stream()
.filter(tx -> tx.getConfidence().getConfidenceType() == BUILDING) .filter(tx -> tx.getConfidence().getConfidenceType() == BUILDING)
.map(Transaction::getTxId) .map(Transaction::getTxId)
.map(Sha256Hash::toString) .map(Sha256Hash::toString)
@ -343,13 +341,15 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// BSQ TransactionOutputs and Transactions // BSQ TransactionOutputs and Transactions
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// not thread safe - call only from user thread
public List<Transaction> getClonedWalletTransactions() { public List<Transaction> getClonedWalletTransactions() {
return new ArrayList<>(walletTransactions); return new ArrayList<>(walletTransactions);
} }
// not thread safe - call only from user thread
public Stream<Transaction> getPendingWalletTransactionsStream() { public Stream<Transaction> getPendingWalletTransactionsStream() {
return walletTransactions.stream() return walletTransactions.stream()
.filter(transaction -> transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING); .filter(transaction -> transaction.getConfidence().getConfidenceType() == PENDING);
} }
private void updateBsqWalletTransactions() { private void updateBsqWalletTransactions() {
@ -363,6 +363,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
UserThread.runAfter(() -> { UserThread.runAfter(() -> {
walletTransactions.clear(); walletTransactions.clear();
walletTransactions.addAll(getTransactions(false)); walletTransactions.addAll(getTransactions(false));
walletTransactionsById = null;
walletTransactionsChangeListeners.forEach(WalletTransactionsChangeListener::onWalletTransactionsChange); walletTransactionsChangeListeners.forEach(WalletTransactionsChangeListener::onWalletTransactionsChange);
updateBsqBalance(); updateBsqBalance();
updateBsqWalletTransactionsPending = false; updateBsqWalletTransactionsPending = false;
@ -371,37 +372,6 @@ public class BsqWalletService extends WalletService implements DaoStateListener
} }
} }
private Set<Transaction> getBsqWalletTransactions() {
return getTransactions(false).stream()
.filter(transaction -> transaction.getConfidence().getConfidenceType() == PENDING ||
daoStateService.containsTx(transaction.getTxId().toString()))
.collect(Collectors.toSet());
}
public Set<Transaction> getUnverifiedBsqTransactions() {
Set<Transaction> bsqWalletTransactions = getBsqWalletTransactions();
Set<Transaction> walletTxs = new HashSet<>(getTransactions(false));
checkArgument(walletTxs.size() >= bsqWalletTransactions.size(),
"We cannot have more txsWithOutputsFoundInBsqTxo than walletTxs");
if (walletTxs.size() == bsqWalletTransactions.size()) {
// As expected
return new HashSet<>();
} else {
Map<String, Transaction> map = walletTxs.stream()
.collect(Collectors.toMap(t -> t.getTxId().toString(), Function.identity()));
Set<String> walletTxIds = walletTxs.stream()
.map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet());
Set<String> bsqTxIds = bsqWalletTransactions.stream()
.map(Transaction::getTxId).map(Sha256Hash::toString).collect(Collectors.toSet());
walletTxIds.stream()
.filter(bsqTxIds::contains)
.forEach(map::remove);
return new HashSet<>(map.values());
}
}
@Override @Override
public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException { public Coin getValueSentFromMeForTransaction(Transaction transaction) throws ScriptException {
Coin result = Coin.ZERO; Coin result = Coin.ZERO;
@ -414,7 +384,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
// We grab the parent tx of the connected output // We grab the parent tx of the connected output
final Transaction parentTransaction = connectedOutput.getParentTransaction(); final Transaction parentTransaction = connectedOutput.getParentTransaction();
final boolean isConfirmed = parentTransaction != null && final boolean isConfirmed = parentTransaction != null &&
parentTransaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING; parentTransaction.getConfidence().getConfidenceType() == BUILDING;
if (connectedOutput.isMineOrWatched(wallet)) { if (connectedOutput.isMineOrWatched(wallet)) {
if (isConfirmed) { if (isConfirmed) {
// We lookup if we have a BSQ tx matching the parent tx // We lookup if we have a BSQ tx matching the parent tx
@ -455,7 +425,7 @@ public class BsqWalletService extends WalletService implements DaoStateListener
for (int i = 0; i < transaction.getOutputs().size(); i++) { for (int i = 0; i < transaction.getOutputs().size(); i++) {
TransactionOutput output = transaction.getOutputs().get(i); TransactionOutput output = transaction.getOutputs().get(i);
final boolean isConfirmed = output.getParentTransaction() != null && final boolean isConfirmed = output.getParentTransaction() != null &&
output.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING; output.getParentTransaction().getConfidence().getConfidenceType() == BUILDING;
if (output.isMineOrWatched(wallet)) { if (output.isMineOrWatched(wallet)) {
if (isConfirmed) { if (isConfirmed) {
if (txOptional.isPresent()) { if (txOptional.isPresent()) {
@ -484,8 +454,13 @@ public class BsqWalletService extends WalletService implements DaoStateListener
return result; return result;
} }
// not thread safe - call only from user thread
public Optional<Transaction> isWalletTransaction(String txId) { public Optional<Transaction> isWalletTransaction(String txId) {
return walletTransactions.stream().filter(e -> e.getTxId().toString().equals(txId)).findAny(); if (walletTransactionsById == null) {
walletTransactionsById = walletTransactions.stream()
.collect(Collectors.toMap(tx -> tx.getTxId().toString(), tx -> tx));
}
return Optional.ofNullable(walletTransactionsById.get(txId));
} }

View File

@ -72,7 +72,6 @@ import org.bitcoinj.wallet.listeners.WalletReorganizeEventListener;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multiset; import com.google.common.collect.Multiset;
@ -88,7 +87,6 @@ import org.bouncycastle.crypto.params.KeyParameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -107,6 +105,9 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING;
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.DEAD;
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.PENDING;
/** /**
* Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality. * Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality.
@ -124,7 +125,6 @@ public abstract class WalletService {
private final WalletChangeEventListener cacheInvalidationListener; private final WalletChangeEventListener cacheInvalidationListener;
private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>(); private final AtomicReference<Multiset<Address>> txOutputAddressCache = new AtomicReference<>();
private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>(); private final AtomicReference<SetMultimap<Address, Transaction>> addressToMatchingTxSetCache = new AtomicReference<>();
private final AtomicReference<Map<Sha256Hash, Transaction>> txByIdCache = new AtomicReference<>();
@Getter @Getter
protected Wallet wallet; protected Wallet wallet;
@Getter @Getter
@ -150,7 +150,6 @@ public abstract class WalletService {
cacheInvalidationListener = wallet -> { cacheInvalidationListener = wallet -> {
txOutputAddressCache.set(null); txOutputAddressCache.set(null);
addressToMatchingTxSetCache.set(null); addressToMatchingTxSetCache.set(null);
txByIdCache.set(null);
}; };
} }
@ -337,8 +336,9 @@ public abstract class WalletService {
txIn = partialTx.getInput(index); txIn = partialTx.getInput(index);
if (txIn.getConnectedOutput() != null) { if (txIn.getConnectedOutput() != null) {
// If we don't have a sig we don't do the check to avoid error reports of failed sig checks // If we don't have a sig we don't do the check to avoid error reports of failed sig checks
final List<ScriptChunk> chunks = txIn.getConnectedOutput().getScriptPubKey().getChunks(); List<ScriptChunk> chunks = txIn.getConnectedOutput().getScriptPubKey().getChunks();
if (!chunks.isEmpty() && chunks.get(0).data != null && chunks.get(0).data.length > 0) { byte[] pushData;
if (!chunks.isEmpty() && (pushData = chunks.get(0).data) != null && pushData.length > 0) {
try { try {
// We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when // We assume if it's already signed, it's hopefully got a SIGHASH type that will not invalidate when
// we sign missing pieces (to check this would require either assuming any signatures are signing // we sign missing pieces (to check this would require either assuming any signatures are signing
@ -460,9 +460,8 @@ public abstract class WalletService {
transactionConfidenceList.addAll(transactions.stream() transactionConfidenceList.addAll(transactions.stream()
.map(tx -> getTransactionConfidence(tx, address)) .map(tx -> getTransactionConfidence(tx, address))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(con -> con.getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING || .filter(con -> con.getConfidenceType() == PENDING ||
(con.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING && (con.getConfidenceType() == BUILDING && con.getAppearedAtChainHeight() > targetHeight))
con.getAppearedAtChainHeight() > targetHeight))
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
return getMostRecentConfidence(transactionConfidenceList); return getMostRecentConfidence(transactionConfidenceList);
@ -486,23 +485,16 @@ public abstract class WalletService {
@Nullable @Nullable
public TransactionConfidence getConfidenceForTxId(@Nullable String txId) { public TransactionConfidence getConfidenceForTxId(@Nullable String txId) {
if (wallet != null && txId != null && !txId.isEmpty()) { if (wallet != null && txId != null && !txId.isEmpty()) {
Transaction tx = getTxByIdMap().get(Sha256Hash.wrap(txId)); Sha256Hash hash = Sha256Hash.wrap(txId);
if (tx != null) { Transaction tx = getTransaction(hash);
return tx.getConfidence(); TransactionConfidence confidence;
if (tx != null && (confidence = tx.getConfidence()).getConfidenceType() != DEAD) {
return confidence;
} }
} }
return null; return null;
} }
private Map<Sha256Hash, Transaction> getTxByIdMap() {
return txByIdCache.updateAndGet(map -> map != null ? map : computeTxByIdMap());
}
private Map<Sha256Hash, Transaction> computeTxByIdMap() {
return wallet.getTransactions(false).stream()
.collect(ImmutableMap.toImmutableMap(Transaction::getTxId, tx -> tx));
}
@Nullable @Nullable
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) { private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream() List<TransactionConfidence> transactionConfidenceList = getOutputsWithConnectedOutputs(tx).stream()
@ -538,11 +530,9 @@ public abstract class WalletService {
TransactionConfidence transactionConfidence = null; TransactionConfidence transactionConfidence = null;
for (TransactionConfidence confidence : transactionConfidenceList) { for (TransactionConfidence confidence : transactionConfidenceList) {
if (confidence != null) { if (confidence != null) {
if (transactionConfidence == null || if (transactionConfidence == null || confidence.getConfidenceType() == PENDING ||
confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING) || (confidence.getConfidenceType() == BUILDING &&
(confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) && transactionConfidence.getConfidenceType() == BUILDING &&
transactionConfidence.getConfidenceType().equals(
TransactionConfidence.ConfidenceType.BUILDING) &&
confidence.getDepthInBlocks() < transactionConfidence.getDepthInBlocks())) { confidence.getDepthInBlocks() < transactionConfidence.getDepthInBlocks())) {
transactionConfidence = confidence; transactionConfidence = confidence;
} }
@ -632,7 +622,7 @@ public abstract class WalletService {
for (TransactionOutput transactionOutput : proposedTransaction.getOutputs()) { for (TransactionOutput transactionOutput : proposedTransaction.getOutputs()) {
if (transactionOutput.getValue().isLessThan(Restrictions.getMinNonDustOutput())) { if (transactionOutput.getValue().isLessThan(Restrictions.getMinNonDustOutput())) {
dust = dust.add(transactionOutput.getValue()); dust = dust.add(transactionOutput.getValue());
log.info("Dust TXO = {}", transactionOutput.toString()); log.info("Dust TXO = {}", transactionOutput);
} }
} }
return dust; return dust;
@ -662,13 +652,13 @@ public abstract class WalletService {
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() { Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() {
@Override @Override
public void onSuccess(Transaction result) { public void onSuccess(Transaction result) {
log.info("emptyBtcWallet onSuccess Transaction=" + result); log.info("emptyBtcWallet onSuccess Transaction={}", result);
resultHandler.handleResult(); resultHandler.handleResult();
} }
@Override @Override
public void onFailure(@NotNull Throwable t) { public void onFailure(@NotNull Throwable t) {
log.error("emptyBtcWallet onFailure " + t.toString()); log.error("emptyBtcWallet onFailure " + t);
errorMessageHandler.handleErrorMessage(t.getMessage()); errorMessageHandler.handleErrorMessage(t.getMessage());
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -688,7 +678,7 @@ public abstract class WalletService {
} }
public int getBestChainHeight() { public int getBestChainHeight() {
final BlockChain chain = walletsSetup.getChain(); BlockChain chain = walletsSetup.getChain();
return isWalletReady() && chain != null ? chain.getBestChainHeight() : 0; return isWalletReady() && chain != null ? chain.getBestChainHeight() : 0;
} }
@ -714,13 +704,13 @@ public abstract class WalletService {
} }
public void addNewBestBlockListener(NewBestBlockListener listener) { public void addNewBestBlockListener(NewBestBlockListener listener) {
final BlockChain chain = walletsSetup.getChain(); BlockChain chain = walletsSetup.getChain();
if (isWalletReady() && chain != null) if (isWalletReady() && chain != null)
chain.addNewBestBlockListener(listener); chain.addNewBestBlockListener(listener);
} }
public void removeNewBestBlockListener(NewBestBlockListener listener) { public void removeNewBestBlockListener(NewBestBlockListener listener) {
final BlockChain chain = walletsSetup.getChain(); BlockChain chain = walletsSetup.getChain();
if (isWalletReady() && chain != null) if (isWalletReady() && chain != null)
chain.removeNewBestBlockListener(listener); chain.removeNewBestBlockListener(listener);
} }
@ -865,7 +855,7 @@ public abstract class WalletService {
/** /**
* @param serializedTransaction The serialized transaction to be added to the wallet * @param serializedTransaction The serialized transaction to be added to the wallet
* @return The transaction we added to the wallet, which is different as the one we passed as argument! * @return The transaction we added to the wallet, which is different as the one we passed as argument!
* @throws VerificationException * @throws VerificationException if the transaction could not be parsed or fails sanity checks
*/ */
public static Transaction maybeAddTxToWallet(byte[] serializedTransaction, public static Transaction maybeAddTxToWallet(byte[] serializedTransaction,
Wallet wallet, Wallet wallet,

View File

@ -40,7 +40,6 @@ import org.bitcoinj.core.Coin;
import de.jensd.fx.fontawesome.AwesomeIcon; import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TableRow;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
@ -51,18 +50,14 @@ import lombok.Getter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class ProposalListItem { public class ProposalListItem {
@Getter @Getter
private EvaluatedProposal evaluatedProposal; private final EvaluatedProposal evaluatedProposal;
@Getter @Getter
private final Proposal proposal; private final Proposal proposal;
private final Vote vote; private final Vote vote;
private final boolean isMyBallotIncluded; private final boolean isMyBallotIncluded;
private final BsqFormatter bsqFormatter; private final BsqFormatter bsqFormatter;
private TableRow tableRow;
ProposalListItem(EvaluatedProposal evaluatedProposal, Ballot ballot, boolean isMyBallotIncluded, ProposalListItem(EvaluatedProposal evaluatedProposal, Ballot ballot, boolean isMyBallotIncluded,
BsqFormatter bsqFormatter) { BsqFormatter bsqFormatter) {
this.evaluatedProposal = evaluatedProposal; this.evaluatedProposal = evaluatedProposal;
@ -104,17 +99,6 @@ public class ProposalListItem {
return myVoteIcon; return myVoteIcon;
} }
public void setTableRow(TableRow tableRow) {
this.tableRow = tableRow;
}
public void resetTableRow() {
if (tableRow != null) {
tableRow.setStyle(null);
tableRow.requestLayout();
}
}
public String getProposalOwnerName() { public String getProposalOwnerName() {
return evaluatedProposal.getProposal().getName(); return evaluatedProposal.getProposal().getName();
} }

View File

@ -148,6 +148,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
private ProposalListItem selectedProposalListItem; private ProposalListItem selectedProposalListItem;
private boolean isVoteIncludedInResult; private boolean isVoteIncludedInResult;
private final Set<Cycle> cyclesAdded = new HashSet<>(); private final Set<Cycle> cyclesAdded = new HashSet<>();
private Map<String, Ballot> ballotByProposalTxIdMap;
private boolean hasCalculatedResult = false; private boolean hasCalculatedResult = false;
@ -205,7 +206,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
if (daoStateService.isParseBlockChainComplete()) { if (daoStateService.isParseBlockChainComplete()) {
checkForResultPhase(daoStateService.getChainHeight()); checkForResultPhase(daoStateService.getChainHeight());
fillCycleList(); fillCycleListAndBallotMap();
} }
exportButton.setOnAction(event -> { exportButton.setOnAction(event -> {
@ -243,7 +244,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
@Override @Override
public void onParseBlockCompleteAfterBatchProcessing(Block block) { public void onParseBlockCompleteAfterBatchProcessing(Block block) {
checkForResultPhase(daoStateService.getChainHeight()); checkForResultPhase(daoStateService.getChainHeight());
fillCycleList(); fillCycleListAndBallotMap();
} }
private void checkForResultPhase(int chainHeight) { private void checkForResultPhase(int chainHeight) {
@ -283,16 +284,9 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
resultsOfCycle = item.getResultsOfCycle(); resultsOfCycle = item.getResultsOfCycle();
// Check if my vote is included in result // Check if my vote is included in result
isVoteIncludedInResult = false; isVoteIncludedInResult = resultsOfCycle.getDecryptedVotesForCycle().stream()
resultsOfCycle.getEvaluatedProposals().forEach(evProposal -> resultsOfCycle.getDecryptedVotesForCycle() .anyMatch(decryptedBallotsWithMerits -> bsqWalletService.isWalletTransaction(decryptedBallotsWithMerits
.forEach(decryptedBallotsWithMerits -> { .getVoteRevealTxId()).isPresent());
// Iterate through all included votes to see if any of those are ours
if (!isVoteIncludedInResult) {
isVoteIncludedInResult = bsqWalletService.isWalletTransaction(decryptedBallotsWithMerits
.getVoteRevealTxId()).isPresent();
}
}));
maybeShowVoteResultErrors(item.getResultsOfCycle().getCycle()); maybeShowVoteResultErrors(item.getResultsOfCycle().getCycle());
createProposalsTable(); createProposalsTable();
@ -359,11 +353,8 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
if (selectedProposalListItem != null) { if (selectedProposalListItem != null) {
EvaluatedProposal evaluatedProposal = selectedProposalListItem.getEvaluatedProposal(); EvaluatedProposal evaluatedProposal = selectedProposalListItem.getEvaluatedProposal();
Optional<Ballot> optionalBallot = daoFacade.getAllValidBallots().stream() Ballot ballot = ballotByProposalTxIdMap.get(evaluatedProposal.getProposalTxId());
.filter(ballot -> ballot.getTxId().equals(evaluatedProposal.getProposalTxId()))
.findAny();
Ballot ballot = optionalBallot.orElse(null);
voteListItemList.clear(); voteListItemList.clear();
resultsOfCycle.getEvaluatedProposals().stream() resultsOfCycle.getEvaluatedProposals().stream()
.filter(evProposal -> evProposal.getProposal().equals(selectedProposalListItem.getEvaluatedProposal().getProposal())) .filter(evProposal -> evProposal.getProposal().equals(selectedProposalListItem.getEvaluatedProposal().getProposal()))
@ -389,16 +380,19 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
// Fill lists: Cycle // Fill lists: Cycle
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void fillCycleList() { private void fillCycleListAndBallotMap() {
// At data creation we delay a bit so that the UI has a chance to display the placeholder. // At data creation we delay a bit so that the UI has a chance to display the placeholder.
if (cyclesAdded.isEmpty()) { if (cyclesAdded.isEmpty()) {
UserThread.runAfter(this::doFillCycleList, 50, TimeUnit.MILLISECONDS); UserThread.runAfter(this::doFillCycleListAndBallotMap, 50, TimeUnit.MILLISECONDS);
} else { } else {
doFillCycleList(); doFillCycleListAndBallotMap();
} }
} }
private void doFillCycleList() { private void doFillCycleListAndBallotMap() {
ballotByProposalTxIdMap = daoFacade.getAllValidBallots().stream()
.collect(Collectors.toMap(Ballot::getTxId, ballot -> ballot));
// Creating our data structure is a bit expensive so we ensure to only create the CycleListItems once. // Creating our data structure is a bit expensive so we ensure to only create the CycleListItems once.
daoStateService.getCycles().stream() daoStateService.getCycles().stream()
.filter(cycle -> !cyclesAdded.contains(cycle)) .filter(cycle -> !cyclesAdded.contains(cycle))
@ -412,7 +406,7 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
.collect(Collectors.toList()); .collect(Collectors.toList());
AtomicLong stakeAndMerit = new AtomicLong(); AtomicLong stakeAndMerit = new AtomicLong();
List<DecryptedBallotsWithMerits> decryptedVotesForCycle = daoStateService.getDecryptedBallotsWithMeritsList().stream() List<DecryptedBallotsWithMerits> decryptedVotesForCycle = daoStateService.getDecryptedBallotsWithMeritsList().parallelStream()
.filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getBlindVoteTxId())) .filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getBlindVoteTxId()))
.filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getVoteRevealTxId())) .filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getVoteRevealTxId()))
.peek(decryptedBallotsWithMerits -> stakeAndMerit.getAndAdd(decryptedBallotsWithMerits.getStake() + decryptedBallotsWithMerits.getMerit(daoStateService))) .peek(decryptedBallotsWithMerits -> stakeAndMerit.getAndAdd(decryptedBallotsWithMerits.getStake() + decryptedBallotsWithMerits.getMerit(daoStateService)))
@ -464,8 +458,6 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
cyclesTableView.setItems(sortedCycleListItemList); cyclesTableView.setItems(sortedCycleListItemList);
sortedCycleListItemList.comparatorProperty().bind(cyclesTableView.comparatorProperty()); sortedCycleListItemList.comparatorProperty().bind(cyclesTableView.comparatorProperty());
} }
@ -495,11 +487,8 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
proposalsTableView.setItems(sortedProposalList); proposalsTableView.setItems(sortedProposalList);
sortedProposalList.comparatorProperty().bind(proposalsTableView.comparatorProperty()); sortedProposalList.comparatorProperty().bind(proposalsTableView.comparatorProperty());
proposalList.forEach(ProposalListItem::resetTableRow);
proposalList.clear(); proposalList.clear();
Map<String, Ballot> ballotByProposalTxIdMap = daoFacade.getAllValidBallots().stream()
.collect(Collectors.toMap(Ballot::getTxId, ballot -> ballot));
proposalList.setAll(resultsOfCycle.getEvaluatedProposals().stream() proposalList.setAll(resultsOfCycle.getEvaluatedProposals().stream()
.filter(evaluatedProposal -> { .filter(evaluatedProposal -> {
boolean containsKey = ballotByProposalTxIdMap.containsKey(evaluatedProposal.getProposalTxId()); boolean containsKey = ballotByProposalTxIdMap.containsKey(evaluatedProposal.getProposalTxId());
@ -893,6 +882,12 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
cycleJson.addProperty("totalAcceptedVotes", cycleListItem.getResultsOfCycle().getNumAcceptedVotes()); cycleJson.addProperty("totalAcceptedVotes", cycleListItem.getResultsOfCycle().getNumAcceptedVotes());
cycleJson.addProperty("totalRejectedVotes", cycleListItem.getResultsOfCycle().getNumRejectedVotes()); cycleJson.addProperty("totalRejectedVotes", cycleListItem.getResultsOfCycle().getNumRejectedVotes());
List<DecryptedBallotsWithMerits> decryptedVotesForCycle = cycleListItem.getResultsOfCycle().getDecryptedVotesForCycle();
// Make sure the votes are sorted so we can easier compare json files from different users
decryptedVotesForCycle.sort(Comparator.comparing(DecryptedBallotsWithMerits::getBlindVoteTxId));
Map<String, Long> meritStakeMap = decryptedVotesForCycle.stream()
.collect(Collectors.toMap(DecryptedBallotsWithMerits::getBlindVoteTxId, d -> d.getMerit(daoStateService)));
JsonArray proposalsArray = new JsonArray(); JsonArray proposalsArray = new JsonArray();
List<EvaluatedProposal> evaluatedProposals = cycleListItem.getResultsOfCycle().getEvaluatedProposals(); List<EvaluatedProposal> evaluatedProposals = cycleListItem.getResultsOfCycle().getEvaluatedProposals();
evaluatedProposals.sort(Comparator.comparingLong(o -> o.getProposal().getCreationDate())); evaluatedProposals.sort(Comparator.comparingLong(o -> o.getProposal().getCreationDate()));
@ -984,18 +979,15 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements D
evaluatedProposals.stream() evaluatedProposals.stream()
.filter(evaluatedProposal -> evaluatedProposal.getProposal().equals(proposal)) .filter(evaluatedProposal -> evaluatedProposal.getProposal().equals(proposal))
.forEach(evaluatedProposal -> { .forEach(evaluatedProposal -> {
List<DecryptedBallotsWithMerits> decryptedVotesForCycle = cycleListItem.getResultsOfCycle().getDecryptedVotesForCycle();
// Make sure the votes are sorted so we can easier compare json files from different users
decryptedVotesForCycle.sort(Comparator.comparing(DecryptedBallotsWithMerits::getBlindVoteTxId));
decryptedVotesForCycle.forEach(decryptedBallotsWithMerits -> { decryptedVotesForCycle.forEach(decryptedBallotsWithMerits -> {
JsonObject voteJson = new JsonObject(); JsonObject voteJson = new JsonObject();
// Domain data of decryptedBallotsWithMerits // Domain data of decryptedBallotsWithMerits
voteJson.addProperty("hashOfBlindVoteList", Utilities.bytesAsHexString(decryptedBallotsWithMerits.getHashOfBlindVoteList())); voteJson.addProperty("hashOfBlindVoteList", Utilities.bytesAsHexString(decryptedBallotsWithMerits.getHashOfBlindVoteList()));
voteJson.addProperty("blindVoteTxId", decryptedBallotsWithMerits.getBlindVoteTxId()); voteJson.addProperty("blindVoteTxId", decryptedBallotsWithMerits.getBlindVoteTxId());
voteJson.addProperty("voteRevealTxId", decryptedBallotsWithMerits.getVoteRevealTxId()); voteJson.addProperty("voteRevealTxId", decryptedBallotsWithMerits.getVoteRevealTxId());
voteJson.addProperty("stake", decryptedBallotsWithMerits.getStake()); long stake = decryptedBallotsWithMerits.getStake();
voteJson.addProperty("stake", stake);
voteJson.addProperty("voteWeight", decryptedBallotsWithMerits.getMerit(daoStateService)); voteJson.addProperty("voteWeight", stake + meritStakeMap.get(decryptedBallotsWithMerits.getBlindVoteTxId()));
String voteResult = decryptedBallotsWithMerits.getVote(evaluatedProp.getProposalTxId()) String voteResult = decryptedBallotsWithMerits.getVote(evaluatedProp.getProposalTxId())
.map(vote -> vote.isAccepted() ? "Accepted" : "Rejected") .map(vote -> vote.isAccepted() ? "Accepted" : "Rejected")
.orElse("Ignored"); .orElse("Ignored");