From 6798c89cdc47186a53882dd235f68844809a701a Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 27 Aug 2019 22:36:37 +0900 Subject: [PATCH 1/3] Improve TempProposal processing (fixes #3143) --- .../src/main/java/bisq/core/dao/DaoSetup.java | 3 + .../proposal/ProposalListPresentation.java | 78 ++++++++++++++++-- .../governance/proposal/ProposalService.java | 2 +- .../p2p/storage/HashMapChangedListener.java | 8 ++ .../network/p2p/storage/P2PDataStorage.java | 7 +- .../resources/TempProposalStore_BTC_MAINNET | Bin 74533 -> 0 bytes 6 files changed, 89 insertions(+), 9 deletions(-) delete mode 100644 p2p/src/main/resources/TempProposalStore_BTC_MAINNET diff --git a/core/src/main/java/bisq/core/dao/DaoSetup.java b/core/src/main/java/bisq/core/dao/DaoSetup.java index 550b64d867f..b8c13582f77 100644 --- a/core/src/main/java/bisq/core/dao/DaoSetup.java +++ b/core/src/main/java/bisq/core/dao/DaoSetup.java @@ -27,6 +27,7 @@ import bisq.core.dao.governance.bond.role.BondedRolesRepository; import bisq.core.dao.governance.period.CycleService; import bisq.core.dao.governance.proofofburn.ProofOfBurnService; +import bisq.core.dao.governance.proposal.ProposalListPresentation; import bisq.core.dao.governance.proposal.ProposalService; import bisq.core.dao.governance.voteresult.MissingDataRequestService; import bisq.core.dao.governance.voteresult.VoteResultService; @@ -59,6 +60,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, CycleService cycleService, BallotListService ballotListService, ProposalService proposalService, + ProposalListPresentation proposalListPresentation, BlindVoteListService blindVoteListService, MyBlindVoteListService myBlindVoteListService, VoteRevealService voteRevealService, @@ -90,6 +92,7 @@ public DaoSetup(BsqNodeProvider bsqNodeProvider, daoSetupServices.add(cycleService); daoSetupServices.add(ballotListService); daoSetupServices.add(proposalService); + daoSetupServices.add(proposalListPresentation); daoSetupServices.add(blindVoteListService); daoSetupServices.add(myBlindVoteListService); daoSetupServices.add(voteRevealService); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalListPresentation.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalListPresentation.java index 1dcb6cf0b38..9be6942b6c9 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalListPresentation.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalListPresentation.java @@ -18,12 +18,20 @@ package bisq.core.dao.governance.proposal; import bisq.core.btc.wallet.BsqWalletService; +import bisq.core.dao.DaoSetupService; import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload; +import bisq.core.dao.governance.proposal.storage.temp.TempProposalPayload; 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.Proposal; +import bisq.network.p2p.storage.HashMapChangedListener; +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; + +import bisq.common.UserThread; + import org.bitcoinj.core.TransactionConfidence; import com.google.inject.Inject; @@ -47,7 +55,8 @@ * our own proposal that is not critical). Foreign proposals are only shown if confirmed and fully validated. */ @Slf4j -public class ProposalListPresentation implements DaoStateListener, MyProposalListService.Listener { +public class ProposalListPresentation implements DaoStateListener, HashMapChangedListener, + MyProposalListService.Listener, DaoSetupService { private final ProposalService proposalService; private final DaoStateService daoStateService; private final MyProposalListService myProposalListService; @@ -56,6 +65,8 @@ public class ProposalListPresentation implements DaoStateListener, MyProposalLis private final ObservableList allProposals = FXCollections.observableArrayList(); @Getter private final FilteredList activeOrMyUnconfirmedProposals = new FilteredList<>(allProposals); + private final ListChangeListener proposalListChangeListener; + private boolean tempProposalsChanged; /////////////////////////////////////////////////////////////////////////////////////////// @@ -65,6 +76,7 @@ public class ProposalListPresentation implements DaoStateListener, MyProposalLis @Inject public ProposalListPresentation(ProposalService proposalService, DaoStateService daoStateService, + P2PDataStorage p2PDataStorage, MyProposalListService myProposalListService, BsqWalletService bsqWalletService, ProposalValidatorProvider validatorProvider) { @@ -75,13 +87,30 @@ public ProposalListPresentation(ProposalService proposalService, this.validatorProvider = validatorProvider; daoStateService.addDaoStateListener(this); + p2PDataStorage.addHashMapChangedListener(this); myProposalListService.addListener(this); - proposalService.getTempProposals().addListener((ListChangeListener) c -> { - updateLists(); - }); - proposalService.getProposalPayloads().addListener((ListChangeListener) c -> { - updateLists(); + proposalListChangeListener = c -> updateLists(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // DaoSetupService + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void addListeners() { + } + + @Override + public void start() { + // We must set the listeners initially and not on onParseBlockChainComplete as activeOrMyUnconfirmedProposals + // is used in voteResults which can be called earlier during sync. + // To avoid unneeded upDateLists calls we delay one render frame so that once the proposalService is complete we + // register out listeners. + UserThread.execute(() -> { + proposalService.getTempProposals().addListener(proposalListChangeListener); + proposalService.getProposalPayloads().addListener((ListChangeListener) c -> updateLists()); }); } @@ -96,6 +125,43 @@ public void onParseBlockCompleteAfterBatchProcessing(Block block) { } + /////////////////////////////////////////////////////////////////////////////////////////// + // HashMapChangedListener + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onAdded(ProtectedStorageEntry entry) { + if (entry.getProtectedStoragePayload() instanceof TempProposalPayload) { + tempProposalsChanged = true; + } + } + + @Override + public void onRemoved(ProtectedStorageEntry entry) { + if (entry.getProtectedStoragePayload() instanceof TempProposalPayload) { + tempProposalsChanged = true; + } + } + + @Override + public void onBatchRemoveExpiredDataStarted() { + // We temporary remove the listener when batch processing starts to avoid that we rebuild our lists at each + // remove call. After batch processing at onBatchRemoveExpiredDataCompleted we add again our listener and call + // the updateLists method. + proposalService.getTempProposals().removeListener(proposalListChangeListener); + } + + @Override + public void onBatchRemoveExpiredDataCompleted() { + proposalService.getTempProposals().addListener(proposalListChangeListener); + // We only call updateLists if tempProposals have changed. updateLists() is an expensive call and takes 200 ms. + if (tempProposalsChanged) { + updateLists(); + tempProposalsChanged = false; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// // MyProposalListService.Listener /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java index 616fe6ec66e..6da0d387516 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/ProposalService.java @@ -279,7 +279,7 @@ private void onProtectedDataRemoved(ProtectedStorageEntry entry) { if (inPhase || txInPastCycle || unconfirmedOrNonBsqTx) { if (tempProposals.contains(proposal)) { tempProposals.remove(proposal); - log.info("We received a remove request for a TempProposalPayload and have removed the proposal " + + log.debug("We received a remove request for a TempProposalPayload and have removed the proposal " + "from our list. proposal creation date={}, proposalTxId={}, inPhase={}, " + "txInPastCycle={}, unconfirmedOrNonBsqTx={}", proposal.getCreationDateAsDate(), proposal.getTxId(), inPhase, txInPastCycle, unconfirmedOrNonBsqTx); diff --git a/p2p/src/main/java/bisq/network/p2p/storage/HashMapChangedListener.java b/p2p/src/main/java/bisq/network/p2p/storage/HashMapChangedListener.java index aab352ff4e0..a3ac1c20258 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/HashMapChangedListener.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/HashMapChangedListener.java @@ -24,4 +24,12 @@ public interface HashMapChangedListener { @SuppressWarnings("UnusedParameters") void onRemoved(ProtectedStorageEntry data); + + // We process all expired entries after a delay (60 s) after onBootstrapComplete. + // We notify listeners of start and completion so they can optimize to only update after batch processing is done. + default void onBatchRemoveExpiredDataStarted() { + } + + default void onBatchRemoveExpiredDataCompleted() { + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index c80d5d990e8..1ca1fcb8524 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -195,9 +195,12 @@ public void onBootstrapComplete() { } }); - toRemoveSet.forEach( - protectedDataToRemove -> hashMapChangedListeners.forEach( + // Batch processing can cause performance issues, so we give listeners a chance to deal with it by notifying + // about start and end of iteration. + hashMapChangedListeners.forEach(HashMapChangedListener::onBatchRemoveExpiredDataStarted); + toRemoveSet.forEach(protectedDataToRemove -> hashMapChangedListeners.forEach( listener -> listener.onRemoved(protectedDataToRemove))); + hashMapChangedListeners.forEach(HashMapChangedListener::onBatchRemoveExpiredDataCompleted); if (sequenceNumberMap.size() > 1000) sequenceNumberMap.setMap(getPurgedSequenceNumberMap(sequenceNumberMap.getMap())); diff --git a/p2p/src/main/resources/TempProposalStore_BTC_MAINNET b/p2p/src/main/resources/TempProposalStore_BTC_MAINNET deleted file mode 100644 index ddb43b6f270e13836b4979a8c7b94cbff17fb3a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74533 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 bR-_ksKOT@6Ao<2zQ<1sDU6OUG3?SkkmJfyj From 40c21909a1c3b00a39d6594158008987853a9fdd Mon Sep 17 00:00:00 2001 From: wiz Date: Wed, 28 Aug 2019 01:55:52 +0900 Subject: [PATCH 2/3] Update data storage on expired data removal --- .../network/p2p/storage/P2PDataStorage.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 1ca1fcb8524..b909c61f4fb 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -198,8 +198,10 @@ public void onBootstrapComplete() { // Batch processing can cause performance issues, so we give listeners a chance to deal with it by notifying // about start and end of iteration. hashMapChangedListeners.forEach(HashMapChangedListener::onBatchRemoveExpiredDataStarted); - toRemoveSet.forEach(protectedDataToRemove -> hashMapChangedListeners.forEach( - listener -> listener.onRemoved(protectedDataToRemove))); + toRemoveSet.forEach(protectedStorageEntry -> { + hashMapChangedListeners.forEach(l -> l.onRemoved(protectedStorageEntry)); + removeFromProtectedDataStore(protectedStorageEntry); + }); hashMapChangedListeners.forEach(HashMapChangedListener::onBatchRemoveExpiredDataCompleted); if (sequenceNumberMap.size() > 1000) @@ -462,21 +464,26 @@ && checkSignature(protectedStorageEntry) broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null, isDataOwner); - if (protectedStoragePayload instanceof PersistablePayload) { - ByteArray compactHash = getCompactHashAsByteArray(protectedStoragePayload); - ProtectedStorageEntry previous = protectedDataStoreService.remove(compactHash, protectedStorageEntry); - if (previous != null) { - protectedDataStoreListeners.forEach(e -> e.onRemoved(protectedStorageEntry)); - } else { - log.info("We cannot remove the protectedStorageEntry from the persistedEntryMap as it does not exist."); - } - } + removeFromProtectedDataStore(protectedStorageEntry); } else { log.debug("remove failed"); } return result; } + private void removeFromProtectedDataStore(ProtectedStorageEntry protectedStorageEntry) { + ProtectedStoragePayload protectedStoragePayload = protectedStorageEntry.getProtectedStoragePayload(); + if (protectedStoragePayload instanceof PersistablePayload) { + ByteArray compactHash = getCompactHashAsByteArray(protectedStoragePayload); + ProtectedStorageEntry previous = protectedDataStoreService.remove(compactHash, protectedStorageEntry); + if (previous != null) { + protectedDataStoreListeners.forEach(e -> e.onRemoved(protectedStorageEntry)); + } else { + log.info("We cannot remove the protectedStorageEntry from the persistedEntryMap as it does not exist."); + } + } + } + @SuppressWarnings("UnusedReturnValue") public boolean removeMailboxData(ProtectedMailboxStorageEntry protectedMailboxStorageEntry, @Nullable NodeAddress sender, boolean isDataOwner) { ByteArray hashOfData = get32ByteHashAsByteArray(protectedMailboxStorageEntry.getProtectedStoragePayload()); From 8cdee2b36e946bba5478b7bae9a8b02fd121519c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 27 Aug 2019 19:27:39 +0200 Subject: [PATCH 3/3] Do not send BSQ blocks to full nodes --- common/src/main/java/bisq/common/app/Capability.java | 2 +- .../core/dao/node/messages/NewBlockBroadcastMessage.java | 2 +- .../main/java/bisq/core/setup/CoreNetworkCapabilities.java | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/bisq/common/app/Capability.java b/common/src/main/java/bisq/common/app/Capability.java index e01814fd35d..7bf426ba7c5 100644 --- a/common/src/main/java/bisq/common/app/Capability.java +++ b/common/src/main/java/bisq/common/app/Capability.java @@ -31,7 +31,7 @@ public enum Capability { PROPOSAL, BLIND_VOTE, ACK_MSG, - BSQ_BLOCK, + RECEIVE_BSQ_BLOCK, DAO_STATE, BUNDLE_OF_ENVELOPES } diff --git a/core/src/main/java/bisq/core/dao/node/messages/NewBlockBroadcastMessage.java b/core/src/main/java/bisq/core/dao/node/messages/NewBlockBroadcastMessage.java index c1a0e5da45e..ed8ae157e0a 100644 --- a/core/src/main/java/bisq/core/dao/node/messages/NewBlockBroadcastMessage.java +++ b/core/src/main/java/bisq/core/dao/node/messages/NewBlockBroadcastMessage.java @@ -66,6 +66,6 @@ public static NetworkEnvelope fromProto(PB.NewBlockBroadcastMessage proto, int m @Override public Capabilities getRequiredCapabilities() { - return new Capabilities(Capability.BSQ_BLOCK); + return new Capabilities(Capability.RECEIVE_BSQ_BLOCK); } } diff --git a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java index b49cf9ac6b4..cebcc92888c 100644 --- a/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java +++ b/core/src/main/java/bisq/core/setup/CoreNetworkCapabilities.java @@ -32,7 +32,7 @@ public static void setSupportedCapabilities(BisqEnvironment bisqEnvironment) { Capabilities.app.addAll(Capability.BUNDLE_OF_ENVELOPES); if (BisqEnvironment.isDaoActivated(bisqEnvironment)) { - Capabilities.app.addAll(Capability.PROPOSAL, Capability.BLIND_VOTE, Capability.BSQ_BLOCK, Capability.DAO_STATE); + Capabilities.app.addAll(Capability.PROPOSAL, Capability.BLIND_VOTE, Capability.DAO_STATE); maybeApplyDaoFullMode(bisqEnvironment); } @@ -46,6 +46,10 @@ public static void maybeApplyDaoFullMode(BisqEnvironment bisqEnvironment) { if (isFullDaoNode != null && !isFullDaoNode.isEmpty() && isFullDaoNode.toLowerCase().equals("true")) { log.info("Set Capability.DAO_FULL_NODE"); Capabilities.app.addAll(Capability.DAO_FULL_NODE); + } else { + // A lite node has the capability to receive bsq blocks. We do not want to send BSQ blocks to full nodes + // as they ignore them anyway. + Capabilities.app.addAll(Capability.RECEIVE_BSQ_BLOCK); } } }