diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitness.java b/core/src/main/java/bisq/core/account/sign/SignedWitness.java index e5af940470c..d2268400ac6 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitness.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitness.java @@ -20,7 +20,7 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; import bisq.network.p2p.storage.payload.DateTolerantPayload; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.common.app.Capabilities; @@ -45,7 +45,7 @@ // Supports signatures made from EC key (arbitrators) and signature created with DSA key. @Slf4j @Value -public class SignedWitness implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope, +public class SignedWitness implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, PersistableEnvelope, DateTolerantPayload, CapabilityRequiringPayload { public enum VerificationMethod { diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitness.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitness.java index 9578df22973..07f05096602 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitness.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitness.java @@ -19,7 +19,7 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.DateTolerantPayload; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.common.proto.persistable.PersistableEnvelope; @@ -40,7 +40,7 @@ // so only the newly added objects since the last release will be retrieved over the P2P network. @Slf4j @Value -public class AccountAgeWitness implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope, DateTolerantPayload { +public class AccountAgeWitness implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, PersistableEnvelope, DateTolerantPayload { private static final long TOLERANCE = TimeUnit.DAYS.toMillis(1); private final byte[] hash; // Ripemd160(Sha256(concatenated accountHash, signature and sigPubKey)); 20 bytes diff --git a/core/src/main/java/bisq/core/alert/AlertManager.java b/core/src/main/java/bisq/core/alert/AlertManager.java index 658c675ee32..b21b7075054 100644 --- a/core/src/main/java/bisq/core/alert/AlertManager.java +++ b/core/src/main/java/bisq/core/alert/AlertManager.java @@ -127,7 +127,7 @@ public boolean addAlertMessageIfKeyIsValid(Alert alert, String privKeyString) { if (isKeyValid) { signAndAddSignatureToAlertMessage(alert); user.setDevelopersAlert(alert); - boolean result = p2PService.addProtectedStorageEntry(alert, true); + boolean result = p2PService.addProtectedStorageEntry(alert); if (result) { log.trace("Add alertMessage to network was successful. AlertMessage={}", alert); } @@ -139,7 +139,7 @@ public boolean addAlertMessageIfKeyIsValid(Alert alert, String privKeyString) { public boolean removeAlertMessageIfKeyIsValid(String privKeyString) { Alert alert = user.getDevelopersAlert(); if (isKeyValid(privKeyString) && alert != null) { - if (p2PService.removeData(alert, true)) + if (p2PService.removeData(alert)) log.trace("Remove alertMessage from network was successful. AlertMessage={}", alert); user.setDevelopersAlert(null); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java index d0933144c56..bad67f0bd6f 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java @@ -163,7 +163,7 @@ public void onFailure(TxBroadcastException exception) { public boolean remove(Proposal proposal) { if (canRemoveProposal(proposal, daoStateService, periodService)) { - boolean success = p2PService.removeData(new TempProposalPayload(proposal, signaturePubKey), true); + boolean success = p2PService.removeData(new TempProposalPayload(proposal, signaturePubKey)); if (!success) log.warn("Removal of proposal from p2p network failed. proposal={}", proposal); @@ -214,7 +214,7 @@ private void addToP2PNetworkAsProtectedData(Proposal proposal, ErrorMessageHandl } private boolean addToP2PNetworkAsProtectedData(Proposal proposal) { - return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey), true); + return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey)); } private void rePublishMyProposalsOnceWellConnected() { diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java index 6ea17a819d1..f5334fa6030 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalPayload.java @@ -20,7 +20,7 @@ import bisq.core.dao.state.model.governance.Proposal; import bisq.network.p2p.storage.payload.ExpirablePayload; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.crypto.Sig; @@ -55,7 +55,7 @@ @Getter @EqualsAndHashCode @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) -public class TempProposalPayload implements LazyProcessedPayload, ProtectedStoragePayload, +public class TempProposalPayload implements ProcessOncePersistableNetworkPayload, ProtectedStoragePayload, ExpirablePayload, PersistablePayload { protected final Proposal proposal; diff --git a/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java b/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java index 1a0c4cc34af..69d6c7ea639 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/network/StateNetworkService.java @@ -156,7 +156,7 @@ public void requestHashesFromAllConnectedSeedNodes(int fromHeight) { public void broadcastMyStateHash(StH myStateHash) { NewStateHashMessage newStateHashMessage = getNewStateHashMessage(myStateHash); - broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress(), null, true); + broadcaster.broadcast(newStateHashMessage, networkNode.getNodeAddress(), null); } public void requestHashes(int fromHeight, String peersAddress) { diff --git a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java index 97e8d5d56e7..bff48afb1ed 100644 --- a/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java +++ b/core/src/main/java/bisq/core/dao/node/full/network/FullNodeNetworkService.java @@ -105,7 +105,7 @@ public void publishNewBlock(Block block) { log.info("Publish new block at height={} and block hash={}", block.getHeight(), block.getHash()); RawBlock rawBlock = RawBlock.fromBlock(block); NewBlockBroadcastMessage newBlockBroadcastMessage = new NewBlockBroadcastMessage(rawBlock); - broadcaster.broadcast(newBlockBroadcastMessage, networkNode.getNodeAddress(), null, true); + broadcaster.broadcast(newBlockBroadcastMessage, networkNode.getNodeAddress(), null); } diff --git a/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java b/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java index 2cb081834f6..7fbab2f6b58 100644 --- a/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java +++ b/core/src/main/java/bisq/core/dao/node/lite/network/LiteNodeNetworkService.java @@ -238,7 +238,7 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { log.debug("We received a new message from peer {} and broadcast it to our peers. extBlockId={}", connection.getPeersNodeAddressOptional().orElse(null), extBlockId); receivedBlocks.add(extBlockId); - broadcaster.broadcast(newBlockBroadcastMessage, networkNode.getNodeAddress(), null, false); + broadcaster.broadcast(newBlockBroadcastMessage, connection.getPeersNodeAddressOptional().orElse(null), null); listeners.forEach(listener -> listener.onNewBlockReceived(newBlockBroadcastMessage)); } else { log.debug("We had that message already and do not further broadcast it. extBlockId={}", extBlockId); diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java index e14f7baa613..64ec22c9a1b 100644 --- a/core/src/main/java/bisq/core/filter/FilterManager.java +++ b/core/src/main/java/bisq/core/filter/FilterManager.java @@ -265,7 +265,7 @@ public boolean addFilterMessageIfKeyIsValid(Filter filter, String privKeyString) signAndAddSignatureToFilter(filter); user.setDevelopersFilter(filter); - boolean result = p2PService.addProtectedStorageEntry(filter, true); + boolean result = p2PService.addProtectedStorageEntry(filter); if (result) log.trace("Add filter to network was successful. FilterMessage = {}", filter); @@ -278,7 +278,7 @@ public boolean removeFilterMessageIfKeyIsValid(String privKeyString) { Filter filter = user.getDevelopersFilter(); if (filter == null) { log.warn("Developers filter is null"); - } else if (p2PService.removeData(filter, true)) { + } else if (p2PService.removeData(filter)) { log.trace("Remove filter from network was successful. FilterMessage = {}", filter); user.setDevelopersFilter(null); } else { diff --git a/core/src/main/java/bisq/core/offer/OfferBookService.java b/core/src/main/java/bisq/core/offer/OfferBookService.java index 6ccaa4dfa3e..6edea735e0a 100644 --- a/core/src/main/java/bisq/core/offer/OfferBookService.java +++ b/core/src/main/java/bisq/core/offer/OfferBookService.java @@ -148,7 +148,7 @@ public void addOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandl return; } - boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload(), true); + boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload()); if (result) { resultHandler.handleResult(); } else { @@ -164,7 +164,7 @@ public void refreshTTL(OfferPayload offerPayload, return; } - boolean result = p2PService.refreshTTL(offerPayload, true); + boolean result = p2PService.refreshTTL(offerPayload); if (result) { resultHandler.handleResult(); } else { @@ -187,7 +187,7 @@ public void deactivateOffer(OfferPayload offerPayload, public void removeOffer(OfferPayload offerPayload, @Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) { - if (p2PService.removeData(offerPayload, true)) { + if (p2PService.removeData(offerPayload)) { if (resultHandler != null) resultHandler.handleResult(); } else { diff --git a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentService.java b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentService.java index 2a5c284f2b4..e7661d8421d 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentService.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentService.java @@ -64,7 +64,7 @@ public void addDisputeAgent(T disputeAgent, log.debug("addDisputeAgent disputeAgent.hashCode() " + disputeAgent.hashCode()); if (!BisqEnvironment.getBaseCurrencyNetwork().isMainnet() || !Utilities.encodeToHex(disputeAgent.getRegistrationPubKey()).equals(DevEnv.DEV_PRIVILEGE_PUB_KEY)) { - boolean result = p2PService.addProtectedStorageEntry(disputeAgent, true); + boolean result = p2PService.addProtectedStorageEntry(disputeAgent); if (result) { log.trace("Add disputeAgent to network was successful. DisputeAgent.hashCode() = " + disputeAgent.hashCode()); resultHandler.handleResult(); @@ -81,7 +81,7 @@ public void removeDisputeAgent(T disputeAgent, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { log.debug("removeDisputeAgent disputeAgent.hashCode() " + disputeAgent.hashCode()); - if (p2PService.removeData(disputeAgent, true)) { + if (p2PService.removeData(disputeAgent)) { log.trace("Remove disputeAgent from network was successful. DisputeAgent.hashCode() = " + disputeAgent.hashCode()); resultHandler.handleResult(); } else { diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java index 54412a5de10..41c77aeb36e 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics.java @@ -24,7 +24,7 @@ import bisq.core.offer.OfferPayload; import bisq.network.p2p.storage.payload.ExpirablePayload; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.crypto.Sig; @@ -60,7 +60,7 @@ @Slf4j @EqualsAndHashCode(exclude = {"signaturePubKeyBytes"}) @Value -public final class TradeStatistics implements LazyProcessedPayload, ProtectedStoragePayload, ExpirablePayload, PersistablePayload { +public final class TradeStatistics implements ProcessOncePersistableNetworkPayload, ProtectedStoragePayload, ExpirablePayload, PersistablePayload { private final OfferPayload.Direction direction; private final String baseCurrency; private final String counterCurrency; diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java index 601284f4e74..ba1f6abaf25 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2.java @@ -25,7 +25,7 @@ import bisq.core.offer.OfferUtil; import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.common.app.Capabilities; @@ -63,7 +63,7 @@ @Slf4j @Value -public final class TradeStatistics2 implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload { +public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload { //We don't support arbitrators anymore so this entry will be only for pre v1.2. trades @Deprecated diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java index edebbf13d44..21a17fd9945 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java @@ -221,20 +221,15 @@ protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection conne versions.log(protectedStoragePayload); }); - Set persistableNetworkPayloadSet = dataResponse - .getPersistableNetworkPayloadSet(); - if (persistableNetworkPayloadSet != null) { - persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { + dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> { + // memorize message hashes + //Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length]; + //Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]); - // memorize message hashes - //Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length]; - //Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]); + //hashes.add(bytes); - //hashes.add(bytes); - - hashes.add(persistableNetworkPayload.getHash()); - }); - } + hashes.add(persistableNetworkPayload.getHash()); + }); bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), result); versionBucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), versions); diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java index 77462aa4e5e..aefb227189c 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java @@ -333,20 +333,15 @@ protected boolean treatMessage(NetworkEnvelope networkEnvelope, Connection conne result.log(protectedStoragePayload); }); - Set persistableNetworkPayloadSet = dataResponse - .getPersistableNetworkPayloadSet(); - if (persistableNetworkPayloadSet != null) { - persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { + dataResponse.getPersistableNetworkPayloadSet().forEach(persistableNetworkPayload -> { + // memorize message hashes + //Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length]; + //Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]); - // memorize message hashes - //Byte[] bytes = new Byte[persistableNetworkPayload.getHash().length]; - //Arrays.setAll(bytes, n -> persistableNetworkPayload.getHash()[n]); + //hashes.add(bytes); - //hashes.add(bytes); - - hashes.add(persistableNetworkPayload.getHash()); - }); - } + hashes.add(persistableNetworkPayload.getHash()); + }); bucketsPerHost.put(connection.getPeersNodeAddressProperty().getValue(), result); return true; diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index 733cc385cce..a78dc58d3d9 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -699,7 +699,7 @@ public void onBroadcastFailed(String errorMessage) { // to the logic from BroadcastHandler.sendToPeer } }; - boolean result = p2PDataStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, networkNode.getNodeAddress(), listener, true); + boolean result = p2PDataStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, networkNode.getNodeAddress(), listener); if (!result) { sendMailboxMessageListener.onFault("Data already exists in our local database"); @@ -760,7 +760,7 @@ private void delayedRemoveEntryFromMailbox(DecryptedMessageWithPubKey decryptedM expirableMailboxStoragePayload, keyRing.getSignatureKeyPair(), receiversPubKey); - p2PDataStorage.remove(protectedMailboxStorageEntry, networkNode.getNodeAddress(), true); + p2PDataStorage.remove(protectedMailboxStorageEntry, networkNode.getNodeAddress()); } catch (CryptoException e) { log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); } @@ -779,14 +779,14 @@ private void delayedRemoveEntryFromMailbox(DecryptedMessageWithPubKey decryptedM /////////////////////////////////////////////////////////////////////////////////////////// public boolean addPersistableNetworkPayload(PersistableNetworkPayload payload, boolean reBroadcast) { - return p2PDataStorage.addPersistableNetworkPayload(payload, networkNode.getNodeAddress(), true, true, reBroadcast, false); + return p2PDataStorage.addPersistableNetworkPayload(payload, networkNode.getNodeAddress(), reBroadcast); } - public boolean addProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, boolean isDataOwner) { + public boolean addProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload) { if (isBootstrapped()) { try { ProtectedStorageEntry protectedStorageEntry = p2PDataStorage.getProtectedStorageEntry(protectedStoragePayload, keyRing.getSignatureKeyPair()); - return p2PDataStorage.addProtectedStorageEntry(protectedStorageEntry, networkNode.getNodeAddress(), null, isDataOwner); + return p2PDataStorage.addProtectedStorageEntry(protectedStorageEntry, networkNode.getNodeAddress(), null); } catch (CryptoException e) { log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); return false; @@ -796,11 +796,11 @@ public boolean addProtectedStorageEntry(ProtectedStoragePayload protectedStorage } } - public boolean refreshTTL(ProtectedStoragePayload protectedStoragePayload, boolean isDataOwner) { + public boolean refreshTTL(ProtectedStoragePayload protectedStoragePayload) { if (isBootstrapped()) { try { RefreshOfferMessage refreshTTLMessage = p2PDataStorage.getRefreshTTLMessage(protectedStoragePayload, keyRing.getSignatureKeyPair()); - return p2PDataStorage.refreshTTL(refreshTTLMessage, networkNode.getNodeAddress(), isDataOwner); + return p2PDataStorage.refreshTTL(refreshTTLMessage, networkNode.getNodeAddress()); } catch (CryptoException e) { log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); return false; @@ -810,11 +810,11 @@ public boolean refreshTTL(ProtectedStoragePayload protectedStoragePayload, boole } } - public boolean removeData(ProtectedStoragePayload protectedStoragePayload, boolean isDataOwner) { + public boolean removeData(ProtectedStoragePayload protectedStoragePayload) { if (isBootstrapped()) { try { ProtectedStorageEntry protectedStorageEntry = p2PDataStorage.getProtectedStorageEntry(protectedStoragePayload, keyRing.getSignatureKeyPair()); - return p2PDataStorage.remove(protectedStorageEntry, networkNode.getNodeAddress(), isDataOwner); + return p2PDataStorage.remove(protectedStorageEntry, networkNode.getNodeAddress()); } catch (CryptoException e) { log.error("Signing at getDataWithSignedSeqNr failed. That should never happen."); return false; diff --git a/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java index dde4062d20a..cc8469d8bda 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/BroadcastHandler.java @@ -111,7 +111,7 @@ public void cancel() { /////////////////////////////////////////////////////////////////////////////////////////// public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, ResultHandler resultHandler, - @Nullable Listener listener, boolean isDataOwner) { + @Nullable Listener listener) { this.message = message; this.resultHandler = resultHandler; this.listener = listener; @@ -127,6 +127,8 @@ public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, Re Collections.shuffle(connectedPeersList); numPeers = connectedPeersList.size(); int delay = 50; + + boolean isDataOwner = (sender != null) && sender.equals(networkNode.getNodeAddress()); if (!isDataOwner) { // for not data owner (relay nodes) we send to max. 7 nodes and use a longer delay numPeers = Math.min(7, connectedPeersList.size()); diff --git a/p2p/src/main/java/bisq/network/p2p/peers/Broadcaster.java b/p2p/src/main/java/bisq/network/p2p/peers/Broadcaster.java index a5f85879e75..727c488b0e3 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/Broadcaster.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/Broadcaster.java @@ -56,9 +56,9 @@ public void shutDown() { /////////////////////////////////////////////////////////////////////////////////////////// public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, - @Nullable BroadcastHandler.Listener listener, boolean isDataOwner) { + @Nullable BroadcastHandler.Listener listener) { BroadcastHandler broadcastHandler = new BroadcastHandler(networkNode, peerManager); - broadcastHandler.broadcast(message, sender, this, listener, isDataOwner); + broadcastHandler.broadcast(message, sender, this, listener); broadcastHandlers.add(broadcastHandler); } diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/GetDataRequestHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/GetDataRequestHandler.java index 0972cf32c8c..bb2c4949f9f 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/GetDataRequestHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/GetDataRequestHandler.java @@ -22,27 +22,17 @@ import bisq.network.p2p.network.NetworkNode; import bisq.network.p2p.peers.getdata.messages.GetDataRequest; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; -import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; import bisq.network.p2p.storage.P2PDataStorage; -import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.network.p2p.storage.payload.ProtectedStorageEntry; -import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.Timer; import bisq.common.UserThread; -import bisq.common.util.Utilities; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; @@ -51,8 +41,8 @@ @Slf4j public class GetDataRequestHandler { private static final long TIMEOUT = 90; - private static final int MAX_ENTRIES = 10000; + private static final int MAX_ENTRIES = 10000; /////////////////////////////////////////////////////////////////////////////////////////// // Listener @@ -93,10 +83,35 @@ public GetDataRequestHandler(NetworkNode networkNode, P2PDataStorage dataStorage public void handle(GetDataRequest getDataRequest, final Connection connection) { long ts = System.currentTimeMillis(); - GetDataResponse getDataResponse = new GetDataResponse(getFilteredProtectedStorageEntries(getDataRequest, connection), - getFilteredPersistableNetworkPayload(getDataRequest, connection), - getDataRequest.getNonce(), - getDataRequest instanceof GetUpdatedDataRequest); + String connectionInfo = "connectionInfo" + connection.getPeersNodeAddressOptional() + .map(e -> "node address " + e.getFullAddress()) + .orElseGet(() -> "connection UID " + connection.getUid()); + + AtomicBoolean outPersistableNetworkPayloadOutputTruncated = new AtomicBoolean(false); + AtomicBoolean outProtectedStoragePayloadOutputTruncated = new AtomicBoolean(false); + GetDataResponse getDataResponse = dataStorage.buildGetDataResponse( + getDataRequest, + MAX_ENTRIES, + outPersistableNetworkPayloadOutputTruncated, + outProtectedStoragePayloadOutputTruncated, + connection.getCapabilities()); + + if (outPersistableNetworkPayloadOutputTruncated.get()) { + log.warn("The getData request from peer with {} caused too much PersistableNetworkPayload " + + "entries to get delivered. We limited the entries for the response to {} entries", + connectionInfo, MAX_ENTRIES); + } + + if (outProtectedStoragePayloadOutputTruncated.get()) { + log.warn("The getData request from peer with {} caused too much ProtectedStorageEntry " + + "entries to get delivered. We limited the entries for the response to {} entries", + connectionInfo, MAX_ENTRIES); + } + + log.info("The getDataResponse to peer with {} contains {} ProtectedStorageEntries and {} PersistableNetworkPayloads", + connectionInfo, + getDataResponse.getDataSet().size(), + getDataResponse.getPersistableNetworkPayloadSet().size()); if (timeoutTimer == null) { timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions @@ -136,81 +151,6 @@ public void onFailure(@NotNull Throwable throwable) { log.info("handle GetDataRequest took {} ms", System.currentTimeMillis() - ts); } - private Set getFilteredPersistableNetworkPayload(GetDataRequest getDataRequest, - Connection connection) { - Set tempLookupSet = new HashSet<>(); - String connectionInfo = "connectionInfo" + connection.getPeersNodeAddressOptional() - .map(e -> "node address " + e.getFullAddress()) - .orElseGet(() -> "connection UID " + connection.getUid()); - - Set excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys()); - AtomicInteger maxSize = new AtomicInteger(MAX_ENTRIES); - Set result = dataStorage.getAppendOnlyDataStoreMap().entrySet().stream() - .filter(e -> !excludedKeysAsByteArray.contains(e.getKey())) - .filter(e -> maxSize.decrementAndGet() >= 0) - .map(Map.Entry::getValue) - .filter(connection::noCapabilityRequiredOrCapabilityIsSupported) - .filter(payload -> { - boolean notContained = tempLookupSet.add(new P2PDataStorage.ByteArray(payload.getHash())); - return notContained; - }) - .collect(Collectors.toSet()); - if (maxSize.get() <= 0) { - log.warn("The getData request from peer with {} caused too much PersistableNetworkPayload " + - "entries to get delivered. We limited the entries for the response to {} entries", - connectionInfo, MAX_ENTRIES); - } - log.info("The getData request from peer with {} contains {} PersistableNetworkPayload entries ", - connectionInfo, result.size()); - return result; - } - - private Set getFilteredProtectedStorageEntries(GetDataRequest getDataRequest, - Connection connection) { - Set filteredDataSet = new HashSet<>(); - Set lookupSet = new HashSet<>(); - String connectionInfo = "connectionInfo" + connection.getPeersNodeAddressOptional() - .map(e -> "node address " + e.getFullAddress()) - .orElseGet(() -> "connection UID " + connection.getUid()); - - AtomicInteger maxSize = new AtomicInteger(MAX_ENTRIES); - Set excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys()); - Set filteredSet = dataStorage.getMap().entrySet().stream() - .filter(e -> !excludedKeysAsByteArray.contains(e.getKey())) - .filter(e -> maxSize.decrementAndGet() >= 0) - .map(Map.Entry::getValue) - .collect(Collectors.toSet()); - if (maxSize.get() <= 0) { - log.warn("The getData request from peer with {} caused too much ProtectedStorageEntry " + - "entries to get delivered. We limited the entries for the response to {} entries", - connectionInfo, MAX_ENTRIES); - } - log.info("getFilteredProtectedStorageEntries " + filteredSet.size()); - - for (ProtectedStorageEntry protectedStorageEntry : filteredSet) { - final ProtectedStoragePayload protectedStoragePayload = protectedStorageEntry.getProtectedStoragePayload(); - boolean doAdd = false; - if (protectedStoragePayload instanceof CapabilityRequiringPayload) { - if (connection.getCapabilities().containsAll(((CapabilityRequiringPayload) protectedStoragePayload).getRequiredCapabilities())) - doAdd = true; - else - log.debug("We do not send the message to the peer because they do not support the required capability for that message type.\n" + - "storagePayload is: " + Utilities.toTruncatedString(protectedStoragePayload)); - } else { - doAdd = true; - } - if (doAdd) { - boolean notContained = lookupSet.add(protectedStoragePayload.hashCode()); - if (notContained) - filteredDataSet.add(protectedStorageEntry); - } - } - - log.info("The getData request from peer with {} contains {} ProtectedStorageEntry entries ", - connectionInfo, filteredDataSet.size()); - return filteredDataSet; - } - public void stop() { cleanup(); } diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java index a116d171b7d..4d1ada32941 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataHandler.java @@ -25,10 +25,7 @@ import bisq.network.p2p.peers.PeerManager; import bisq.network.p2p.peers.getdata.messages.GetDataRequest; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; -import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; -import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; import bisq.network.p2p.storage.P2PDataStorage; -import bisq.network.p2p.storage.payload.LazyProcessedPayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; @@ -47,8 +44,6 @@ import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -58,7 +53,6 @@ @Slf4j class RequestDataHandler implements MessageListener { private static final long TIMEOUT = 90; - private static boolean initialRequestApplied = false; private NodeAddress peersNodeAddress; /* @@ -124,25 +118,10 @@ void requestData(NodeAddress nodeAddress, boolean isPreliminaryDataRequest) { if (!stopped) { GetDataRequest getDataRequest; - // We collect the keys of the PersistableNetworkPayload items so we exclude them in our request. - // PersistedStoragePayload items don't get removed, so we don't have an issue with the case that - // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would - // miss that event if we do not load the full set or use some delta handling. - Set excludedKeys = dataStorage.getAppendOnlyDataStoreMap().keySet().stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); - - Set excludedKeysFromPersistedEntryMap = dataStorage.getMap().keySet() - .stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); - - excludedKeys.addAll(excludedKeysFromPersistedEntryMap); - if (isPreliminaryDataRequest) - getDataRequest = new PreliminaryGetDataRequest(nonce, excludedKeys); + getDataRequest = dataStorage.buildPreliminaryGetDataRequest(nonce); else - getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce, excludedKeys); + getDataRequest = dataStorage.buildGetUpdatedDataRequest(networkNode.getNodeAddress(), nonce); if (timeoutTimer == null) { timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions @@ -207,7 +186,6 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { GetDataResponse getDataResponse = (GetDataResponse) networkEnvelope; final Set dataSet = getDataResponse.getDataSet(); Set persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet(); - logContents(networkEnvelope, dataSet, persistableNetworkPayloadSet); if (getDataResponse.getRequestNonce() == nonce) { @@ -218,50 +196,8 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { return; } - final NodeAddress sender = connection.getPeersNodeAddressOptional().get(); - - long ts2 = System.currentTimeMillis(); - AtomicInteger counter = new AtomicInteger(); - dataSet.forEach(e -> { - // We don't broadcast here (last param) as we are only connected to the seed node and would be pointless - dataStorage.addProtectedStorageEntry(e, sender, null, false, false); - counter.getAndIncrement(); - - }); - log.info("Processing {} protectedStorageEntries took {} ms.", counter.get(), System.currentTimeMillis() - ts2); - - /* // engage the firstRequest logic only if we are a seed node. Normal clients get here twice at most. - if (!Capabilities.app.containsAll(Capability.SEED_NODE)) - firstRequest = true;*/ - - if (persistableNetworkPayloadSet != null /*&& firstRequest*/) { - ts2 = System.currentTimeMillis(); - persistableNetworkPayloadSet.forEach(e -> { - if (e instanceof LazyProcessedPayload) { - // We use an optimized method as many checks are not required in that case to avoid - // performance issues. - // Processing 82645 items took now 61 ms compared to earlier version where it took ages (> 2min). - // Usually we only get about a few hundred or max. a few 1000 items. 82645 is all - // trade stats stats and all account age witness data. - - // We only apply it once from first response - if (!initialRequestApplied) { - dataStorage.addPersistableNetworkPayloadFromInitialRequest(e); - - } - } else { - // We don't broadcast here as we are only connected to the seed node and would be pointless - dataStorage.addPersistableNetworkPayload(e, sender, false, - false, false, false); - } - }); - - // We set initialRequestApplied to true after the loop, otherwise we would only process 1 entry - initialRequestApplied = true; - - log.info("Processing {} persistableNetworkPayloads took {} ms.", - persistableNetworkPayloadSet.size(), System.currentTimeMillis() - ts2); - } + dataStorage.processGetDataResponse(getDataResponse, + connection.getPeersNodeAddressOptional().get()); cleanup(); listener.onComplete(); @@ -310,24 +246,20 @@ private void logContents(NetworkEnvelope networkEnvelope, payloadByClassName.get(className).add(protectedStoragePayload); }); + persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { + // For logging different data types + String className = persistableNetworkPayload.getClass().getSimpleName(); + if (!payloadByClassName.containsKey(className)) + payloadByClassName.put(className, new HashSet<>()); - if (persistableNetworkPayloadSet != null) { - persistableNetworkPayloadSet.forEach(persistableNetworkPayload -> { - // For logging different data types - String className = persistableNetworkPayload.getClass().getSimpleName(); - if (!payloadByClassName.containsKey(className)) - payloadByClassName.put(className, new HashSet<>()); - - payloadByClassName.get(className).add(persistableNetworkPayload); - }); - } + payloadByClassName.get(className).add(persistableNetworkPayload); + }); // Log different data types StringBuilder sb = new StringBuilder(); sb.append("\n#################################################################\n"); sb.append("Connected to node: " + peersNodeAddress.getFullAddress() + "\n"); - final int items = dataSet.size() + - (persistableNetworkPayloadSet != null ? persistableNetworkPayloadSet.size() : 0); + final int items = dataSet.size() + persistableNetworkPayloadSet.size(); sb.append("Received ").append(items).append(" instances\n"); payloadByClassName.forEach((key, value) -> sb.append(key) .append(": ") diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java index 0386d1abe1b..a86d6f27834 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/GetDataResponse.java @@ -29,7 +29,6 @@ import bisq.common.proto.network.NetworkProtoResolver; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -37,7 +36,7 @@ import lombok.Value; import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; @Slf4j @EqualsAndHashCode(callSuper = true) @@ -47,17 +46,15 @@ public final class GetDataResponse extends NetworkEnvelope implements SupportedC private final Set dataSet; // Set of PersistableNetworkPayload objects - // We added that in v 0.6 and we would get a null object from older peers, so keep it annotated with @Nullable - @Nullable + // We added that in v 0.6 and the fromProto code will create an empty HashSet if it doesn't exist private final Set persistableNetworkPayloadSet; private final int requestNonce; private final boolean isGetUpdatedDataResponse; - @Nullable private final Capabilities supportedCapabilities; - public GetDataResponse(Set dataSet, - @Nullable Set persistableNetworkPayloadSet, + public GetDataResponse(@NotNull Set dataSet, + @NotNull Set persistableNetworkPayloadSet, int requestNonce, boolean isGetUpdatedDataResponse) { this(dataSet, @@ -72,11 +69,11 @@ public GetDataResponse(Set dataSet, // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private GetDataResponse(Set dataSet, - @Nullable Set persistableNetworkPayloadSet, + private GetDataResponse(@NotNull Set dataSet, + @NotNull Set persistableNetworkPayloadSet, int requestNonce, boolean isGetUpdatedDataResponse, - @Nullable Capabilities supportedCapabilities, + @NotNull Capabilities supportedCapabilities, int messageVersion) { super(messageVersion); @@ -100,13 +97,12 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { .setProtectedStorageEntry((protobuf.ProtectedStorageEntry) protectedStorageEntry.toProtoMessage()) .build()) .collect(Collectors.toList())) + .addAllPersistableNetworkPayloadItems(persistableNetworkPayloadSet.stream() + .map(PersistableNetworkPayload::toProtoMessage) + .collect(Collectors.toList())) .setRequestNonce(requestNonce) - .setIsGetUpdatedDataResponse(isGetUpdatedDataResponse); - - Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); - Optional.ofNullable(persistableNetworkPayloadSet).ifPresent(set -> builder.addAllPersistableNetworkPayloadItems(set.stream() - .map(PersistableNetworkPayload::toProtoMessage) - .collect(Collectors.toList()))); + .setIsGetUpdatedDataResponse(isGetUpdatedDataResponse) + .addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)); protobuf.NetworkEnvelope proto = getNetworkEnvelopeBuilder() .setGetDataResponse(builder) @@ -124,14 +120,11 @@ public static GetDataResponse fromProto(protobuf.GetDataResponse proto, .map(entry -> (ProtectedStorageEntry) resolver.fromProto(entry)) .collect(Collectors.toSet())); - Set persistableNetworkPayloadSet = proto.getPersistableNetworkPayloadItemsList().isEmpty() ? - null : - new HashSet<>( - proto.getPersistableNetworkPayloadItemsList().stream() + Set persistableNetworkPayloadSet = new HashSet<>( + proto.getPersistableNetworkPayloadItemsList().stream() .map(e -> (PersistableNetworkPayload) resolver.fromProto(e)) .collect(Collectors.toSet())); - //PersistableNetworkPayload return new GetDataResponse(dataSet, persistableNetworkPayloadSet, proto.getRequestNonce(), diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java index 0702b0f1756..db0e51eb7f6 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java @@ -26,7 +26,6 @@ import com.google.protobuf.ByteString; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -34,9 +33,7 @@ import lombok.Value; import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; - - +import org.jetbrains.annotations.NotNull; import protobuf.NetworkEnvelope; @@ -44,11 +41,10 @@ @EqualsAndHashCode(callSuper = true) @Value public final class PreliminaryGetDataRequest extends GetDataRequest implements AnonymousMessage, SupportedCapabilitiesMessage { - @Nullable private final Capabilities supportedCapabilities; public PreliminaryGetDataRequest(int nonce, - Set excludedKeys) { + @NotNull Set excludedKeys) { this(nonce, excludedKeys, Capabilities.app, Version.getP2PMessageVersion()); } @@ -58,8 +54,8 @@ public PreliminaryGetDataRequest(int nonce, /////////////////////////////////////////////////////////////////////////////////////////// private PreliminaryGetDataRequest(int nonce, - Set excludedKeys, - @Nullable Capabilities supportedCapabilities, + @NotNull Set excludedKeys, + @NotNull Capabilities supportedCapabilities, int messageVersion) { super(messageVersion, nonce, excludedKeys); @@ -69,13 +65,12 @@ private PreliminaryGetDataRequest(int nonce, @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.PreliminaryGetDataRequest.Builder builder = protobuf.PreliminaryGetDataRequest.newBuilder() + .addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)) .setNonce(nonce) .addAllExcludedKeys(excludedKeys.stream() .map(ByteString::copyFrom) .collect(Collectors.toList())); - Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); - NetworkEnvelope proto = getNetworkEnvelopeBuilder() .setPreliminaryGetDataRequest(builder) .build(); @@ -85,13 +80,9 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { public static PreliminaryGetDataRequest fromProto(protobuf.PreliminaryGetDataRequest proto, int messageVersion) { log.info("Received a PreliminaryGetDataRequest with {} kB", proto.getSerializedSize() / 1000d); - Capabilities supportedCapabilities = proto.getSupportedCapabilitiesList().isEmpty() ? - null : - Capabilities.fromIntList(proto.getSupportedCapabilitiesList()); - return new PreliminaryGetDataRequest(proto.getNonce(), ProtoUtil.byteSetFromProtoByteStringList(proto.getExcludedKeysList()), - supportedCapabilities, + Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion); } } 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 c0a22c691b2..9683f4ee01d 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -25,6 +25,10 @@ import bisq.network.p2p.network.NetworkNode; import bisq.network.p2p.peers.BroadcastHandler; import bisq.network.p2p.peers.Broadcaster; +import bisq.network.p2p.peers.getdata.messages.GetDataRequest; +import bisq.network.p2p.peers.getdata.messages.GetDataResponse; +import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; +import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; import bisq.network.p2p.storage.messages.AddDataMessage; import bisq.network.p2p.storage.messages.AddOncePayload; import bisq.network.p2p.storage.messages.AddPersistableNetworkPayloadMessage; @@ -32,8 +36,10 @@ import bisq.network.p2p.storage.messages.RefreshOfferMessage; import bisq.network.p2p.storage.messages.RemoveDataMessage; import bisq.network.p2p.storage.messages.RemoveMailboxDataMessage; +import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; import bisq.network.p2p.storage.payload.DateTolerantPayload; import bisq.network.p2p.storage.payload.ExpirablePayload; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; import bisq.network.p2p.storage.payload.MailboxStoragePayload; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedMailboxStorageEntry; @@ -48,6 +54,7 @@ import bisq.common.Timer; import bisq.common.UserThread; +import bisq.common.app.Capabilities; import bisq.common.crypto.CryptoException; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; @@ -88,6 +95,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.EqualsAndHashCode; @@ -106,7 +116,9 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers public static final int PURGE_AGE_DAYS = 10; @VisibleForTesting - public static int CHECK_TTL_INTERVAL_SEC = 60; + public static final int CHECK_TTL_INTERVAL_SEC = 60; + + private boolean initialRequestApplied = false; private final Broadcaster broadcaster; private final AppendOnlyDataStoreService appendOnlyDataStoreService; @@ -177,6 +189,179 @@ public synchronized void readFromResources(String postFix) { map.putAll(protectedDataStoreService.getMap()); } + /////////////////////////////////////////////////////////////////////////////////////////// + // RequestData API + /////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Returns a PreliminaryGetDataRequest that can be sent to a peer node to request missing Payload data. + */ + public PreliminaryGetDataRequest buildPreliminaryGetDataRequest(int nonce) { + return new PreliminaryGetDataRequest(nonce, this.getKnownPayloadHashes()); + } + + /** + * Returns a GetUpdatedDataRequest that can be sent to a peer node to request missing Payload data. + */ + public GetUpdatedDataRequest buildGetUpdatedDataRequest(NodeAddress senderNodeAddress, int nonce) { + return new GetUpdatedDataRequest(senderNodeAddress, nonce, this.getKnownPayloadHashes()); + } + + /** + * Returns the set of known payload hashes. This is used in the GetData path to request missing data from peer nodes + */ + private Set getKnownPayloadHashes() { + // We collect the keys of the PersistableNetworkPayload items so we exclude them in our request. + // PersistedStoragePayload items don't get removed, so we don't have an issue with the case that + // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would + // miss that event if we do not load the full set or use some delta handling. + Set excludedKeys =this.appendOnlyDataStoreService.getMap().keySet().stream() + .map(e -> e.bytes) + .collect(Collectors.toSet()); + + Set excludedKeysFromPersistedEntryMap = this.map.keySet() + .stream() + .map(e -> e.bytes) + .collect(Collectors.toSet()); + + excludedKeys.addAll(excludedKeysFromPersistedEntryMap); + + return excludedKeys; + } + + /** + * Generic function that can be used to filter a Map + * by a given set of keys and peer capabilities. + */ + static private Set filterKnownHashes( + Map toFilter, + Function objToPayload, + Set knownHashes, + Capabilities peerCapabilities, + int maxEntries, + AtomicBoolean outTruncated) { + + AtomicInteger limit = new AtomicInteger(maxEntries); + + Set filteredResults = toFilter.entrySet().stream() + .filter(e -> !knownHashes.contains(e.getKey())) + .filter(e -> limit.decrementAndGet() >= 0) + .map(Map.Entry::getValue) + .filter(networkPayload -> shouldTransmitPayloadToPeer(peerCapabilities, + objToPayload.apply(networkPayload))) + .collect(Collectors.toSet()); + + if (limit.get() < 0) + outTruncated.set(true); + + return filteredResults; + } + + /** + * Returns a GetDataResponse object that contains the Payloads known locally, but not remotely. + */ + public GetDataResponse buildGetDataResponse( + GetDataRequest getDataRequest, + int maxEntriesPerType, + AtomicBoolean outPersistableNetworkPayloadOutputTruncated, + AtomicBoolean outProtectedStorageEntryOutputTruncated, + Capabilities peerCapabilities) { + + Set excludedKeysAsByteArray = + P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys()); + + Set filteredPersistableNetworkPayloads = + filterKnownHashes( + this.appendOnlyDataStoreService.getMap(), + Function.identity(), + excludedKeysAsByteArray, + peerCapabilities, + maxEntriesPerType, + outPersistableNetworkPayloadOutputTruncated); + + Set filteredProtectedStorageEntries = + filterKnownHashes( + this.map, + ProtectedStorageEntry::getProtectedStoragePayload, + excludedKeysAsByteArray, + peerCapabilities, + maxEntriesPerType, + outProtectedStorageEntryOutputTruncated); + + return new GetDataResponse( + filteredProtectedStorageEntries, + filteredPersistableNetworkPayloads, + getDataRequest.getNonce(), + getDataRequest instanceof GetUpdatedDataRequest); + } + + /** + * Returns true if a Payload should be transmit to a peer given the peer's supported capabilities. + */ + private static boolean shouldTransmitPayloadToPeer(Capabilities peerCapabilities, NetworkPayload payload) { + + // Sanity check to ensure this isn't used outside P2PDataStorage + if (!(payload instanceof ProtectedStoragePayload || payload instanceof PersistableNetworkPayload)) + return false; + + // If the payload doesn't have a required capability, we should transmit it + if (!(payload instanceof CapabilityRequiringPayload)) + return true; + + // Otherwise, only transmit the Payload if the peer supports all capabilities required by the payload + boolean shouldTransmit = peerCapabilities.containsAll(((CapabilityRequiringPayload) payload).getRequiredCapabilities()); + + if (!shouldTransmit) { + log.debug("We do not send the message to the peer because they do not support the required capability for that message type.\n" + + "storagePayload is: " + Utilities.toTruncatedString(payload)); + } + + return shouldTransmit; + } + + /** + * Processes a GetDataResponse message and updates internal state. Does not broadcast updates to the P2P network + * or domain listeners. + */ + public void processGetDataResponse(GetDataResponse getDataResponse, NodeAddress sender) { + final Set dataSet = getDataResponse.getDataSet(); + Set persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet(); + + long ts2 = System.currentTimeMillis(); + dataSet.forEach(e -> { + // We don't broadcast here (last param) as we are only connected to the seed node and would be pointless + addProtectedStorageEntry(e, sender, null, false); + + }); + log.info("Processing {} protectedStorageEntries took {} ms.", dataSet.size(), this.clock.millis() - ts2); + + ts2 = this.clock.millis(); + persistableNetworkPayloadSet.forEach(e -> { + if (e instanceof ProcessOncePersistableNetworkPayload) { + // We use an optimized method as many checks are not required in that case to avoid + // performance issues. + // Processing 82645 items took now 61 ms compared to earlier version where it took ages (> 2min). + // Usually we only get about a few hundred or max. a few 1000 items. 82645 is all + // trade stats stats and all account age witness data. + + // We only apply it once from first response + if (!initialRequestApplied) { + addPersistableNetworkPayloadFromInitialRequest(e); + + } + } else { + // We don't broadcast here as we are only connected to the seed node and would be pointless + addPersistableNetworkPayload(e, sender,false, false, false); + } + }); + log.info("Processing {} persistableNetworkPayloads took {} ms.", + persistableNetworkPayloadSet.size(), this.clock.millis() - ts2); + + // We only process PersistableNetworkPayloads implementing ProcessOncePersistableNetworkPayload once. It can cause performance + // issues and since the data is rarely out of sync it is not worth it to apply them from multiple peers during + // startup. + initialRequestApplied = true; + } /////////////////////////////////////////////////////////////////////////////////////////// // API @@ -229,16 +414,16 @@ public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) { if (networkEnvelope instanceof BroadcastMessage) { connection.getPeersNodeAddressOptional().ifPresent(peersNodeAddress -> { if (networkEnvelope instanceof AddDataMessage) { - addProtectedStorageEntry(((AddDataMessage) networkEnvelope).getProtectedStorageEntry(), peersNodeAddress, null, false); + addProtectedStorageEntry(((AddDataMessage) networkEnvelope).getProtectedStorageEntry(), peersNodeAddress, null, true); } else if (networkEnvelope instanceof RemoveDataMessage) { - remove(((RemoveDataMessage) networkEnvelope).getProtectedStorageEntry(), peersNodeAddress, false); + remove(((RemoveDataMessage) networkEnvelope).getProtectedStorageEntry(), peersNodeAddress); } else if (networkEnvelope instanceof RemoveMailboxDataMessage) { - remove(((RemoveMailboxDataMessage) networkEnvelope).getProtectedMailboxStorageEntry(), peersNodeAddress, false); + remove(((RemoveMailboxDataMessage) networkEnvelope).getProtectedMailboxStorageEntry(), peersNodeAddress); } else if (networkEnvelope instanceof RefreshOfferMessage) { - refreshTTL((RefreshOfferMessage) networkEnvelope, peersNodeAddress, false); + refreshTTL((RefreshOfferMessage) networkEnvelope, peersNodeAddress); } else if (networkEnvelope instanceof AddPersistableNetworkPayloadMessage) { addPersistableNetworkPayload(((AddPersistableNetworkPayloadMessage) networkEnvelope).getPersistableNetworkPayload(), - peersNodeAddress, false, true, false, true); + peersNodeAddress, true, false, true); } }); } @@ -255,46 +440,31 @@ public void onConnection(Connection connection) { @Override public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) { - if (connection.hasPeersNodeAddress() && !closeConnectionReason.isIntended) { - map.values() - .forEach(protectedStorageEntry -> { - NetworkPayload networkPayload = protectedStorageEntry.getProtectedStoragePayload(); - if (networkPayload instanceof ExpirablePayload && networkPayload instanceof RequiresOwnerIsOnlinePayload) { - NodeAddress ownerNodeAddress = ((RequiresOwnerIsOnlinePayload) networkPayload).getOwnerNodeAddress(); - if (connection.getPeersNodeAddressOptional().isPresent() && - ownerNodeAddress.equals(connection.getPeersNodeAddressOptional().get())) { - // We have a RequiresLiveOwnerData data object with the node address of the - // disconnected peer. We remove that data from our map. - - // Check if we have the data (e.g. OfferPayload) - ByteArray hashOfPayload = get32ByteHashAsByteArray(networkPayload); - boolean containsKey = map.containsKey(hashOfPayload); - if (containsKey) { - log.debug("We remove the data as the data owner got disconnected with " + - "closeConnectionReason=" + closeConnectionReason); - - // We only set the data back by half of the TTL and remove the data only if is has - // expired after that back dating. - // We might get connection drops which are not caused by the node going offline, so - // we give more tolerance with that approach, giving the node the change to - // refresh the TTL with a refresh message. - // We observed those issues during stress tests, but it might have been caused by the - // test set up (many nodes/connections over 1 router) - // TODO investigate what causes the disconnections. - // Usually the are: SOCKET_TIMEOUT ,TERMINATED (EOFException) - protectedStorageEntry.backDate(); - if (protectedStorageEntry.isExpired(this.clock)) { - log.info("We found an expired data entry which we have already back dated. " + - "We remove the protectedStoragePayload:\n\t" + Utilities.toTruncatedString(protectedStorageEntry.getProtectedStoragePayload(), 100)); - removeFromMapAndDataStore(protectedStorageEntry, hashOfPayload); - } - } else { - log.debug("Remove data ignored as we don't have an entry for that data."); - } - } - } - }); - } + if (closeConnectionReason.isIntended) + return; + + if (!connection.getPeersNodeAddressOptional().isPresent()) + return; + + NodeAddress peersNodeAddress = connection.getPeersNodeAddressOptional().get(); + + // Backdate all the eligible payloads based on the node that disconnected + map.values().stream() + .filter(protectedStorageEntry -> protectedStorageEntry.getProtectedStoragePayload() instanceof RequiresOwnerIsOnlinePayload) + .filter(protectedStorageEntry -> ((RequiresOwnerIsOnlinePayload) protectedStorageEntry.getProtectedStoragePayload()).getOwnerNodeAddress().equals(peersNodeAddress)) + .forEach(protectedStorageEntry -> { + // We only set the data back by half of the TTL and remove the data only if is has + // expired after that back dating. + // We might get connection drops which are not caused by the node going offline, so + // we give more tolerance with that approach, giving the node the chance to + // refresh the TTL with a refresh message. + // We observed those issues during stress tests, but it might have been caused by the + // test set up (many nodes/connections over 1 router) + // TODO investigate what causes the disconnections. + // Usually the are: SOCKET_TIMEOUT ,TERMINATED (EOFException) + log.debug("Backdating {} due to closeConnectionReason={}", protectedStorageEntry, closeConnectionReason); + protectedStorageEntry.backDate(); + }); } @Override @@ -304,15 +474,31 @@ public void onError(Throwable throwable) { /////////////////////////////////////////////////////////////////////////////////////////// - // API + // Client API /////////////////////////////////////////////////////////////////////////////////////////// + /** + * Adds a PersistableNetworkPayload to the local P2P data storage. If it does not already exist locally, it will + * be broadcast to the P2P network. + * @param payload PersistableNetworkPayload to add to the network + * @param sender local NodeAddress, if available + * @param allowReBroadcast true if the PersistableNetworkPayload should be rebroadcast even if it + * already exists locally + * @return true if the PersistableNetworkPayload passes all validation and exists in the P2PDataStore + * on completion + */ public boolean addPersistableNetworkPayload(PersistableNetworkPayload payload, @Nullable NodeAddress sender, - boolean isDataOwner, - boolean allowBroadcast, - boolean reBroadcast, - boolean checkDate) { + boolean allowReBroadcast) { + return addPersistableNetworkPayload( + payload, sender, true, allowReBroadcast, false); + } + + private boolean addPersistableNetworkPayload(PersistableNetworkPayload payload, + @Nullable NodeAddress sender, + boolean allowBroadcast, + boolean reBroadcast, + boolean checkDate) { log.trace("addPersistableNetworkPayload payload={}", payload); // Payload hash size does not match expectation for that type of message. @@ -346,7 +532,7 @@ public boolean addPersistableNetworkPayload(PersistableNetworkPayload payload, // Broadcast the payload if requested by caller if (allowBroadcast) - broadcaster.broadcast(new AddPersistableNetworkPayloadMessage(payload), sender, null, isDataOwner); + broadcaster.broadcast(new AddPersistableNetworkPayloadMessage(payload), sender, null); return true; } @@ -356,27 +542,33 @@ public boolean addPersistableNetworkPayload(PersistableNetworkPayload payload, // Overwriting an entry would be also no issue. We also skip notifying listeners as we get called before the domain // is ready so no listeners are set anyway. We might get called twice from a redundant call later, so listeners // might be added then but as we have the data already added calling them would be irrelevant as well. - public boolean addPersistableNetworkPayloadFromInitialRequest(PersistableNetworkPayload payload) { + private void addPersistableNetworkPayloadFromInitialRequest(PersistableNetworkPayload payload) { byte[] hash = payload.getHash(); if (payload.verifyHashSize()) { ByteArray hashAsByteArray = new ByteArray(hash); appendOnlyDataStoreService.put(hashAsByteArray, payload); - return true; } else { log.warn("We got a hash exceeding our permitted size"); - return false; } } + /** + * Adds a ProtectedStorageEntry to the local P2P data storage. If it does not already exist locally, it will be + * broadcast to the P2P network. + * + * @param protectedStorageEntry ProtectedStorageEntry to add to the network + * @param sender local NodeAddress, if available + * @param listener optional listener that can be used to receive events on broadcast + * @return true if the ProtectedStorageEntry was added to the local P2P data storage and broadcast + */ public boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageEntry, @Nullable NodeAddress sender, - @Nullable BroadcastHandler.Listener listener, boolean isDataOwner) { - return addProtectedStorageEntry(protectedStorageEntry, sender, listener, isDataOwner, true); + @Nullable BroadcastHandler.Listener listener) { + return addProtectedStorageEntry(protectedStorageEntry, sender, listener, true); } - public boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageEntry, + private boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageEntry, @Nullable NodeAddress sender, @Nullable BroadcastHandler.Listener listener, - boolean isDataOwner, boolean allowBroadcast) { ProtectedStoragePayload protectedStoragePayload = protectedStorageEntry.getProtectedStoragePayload(); ByteArray hashOfPayload = get32ByteHashAsByteArray(protectedStoragePayload); @@ -422,7 +614,7 @@ public boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageEn // Optionally, broadcast the add/update depending on the calling environment if (allowBroadcast) - broadcastProtectedStorageEntry(protectedStorageEntry, sender, listener, isDataOwner); + broadcaster.broadcast(new AddDataMessage(protectedStorageEntry), sender, listener); // Persist ProtectedStorageEntrys carrying PersistablePayload payloads if (protectedStoragePayload instanceof PersistablePayload) @@ -431,16 +623,15 @@ public boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageEn return true; } - private void broadcastProtectedStorageEntry(ProtectedStorageEntry protectedStorageEntry, - @Nullable NodeAddress sender, - @Nullable BroadcastHandler.Listener broadcastListener, - boolean isDataOwner) { - broadcast(new AddDataMessage(protectedStorageEntry), sender, broadcastListener, isDataOwner); - } - + /** + * Updates a local RefreshOffer with TTL changes and broadcasts those changes to the network + * + * @param refreshTTLMessage refreshTTLMessage containing the update + * @param sender local NodeAddress, if available + * @return true if the RefreshOffer was successfully updated and changes broadcast + */ public boolean refreshTTL(RefreshOfferMessage refreshTTLMessage, - @Nullable NodeAddress sender, - boolean isDataOwner) { + @Nullable NodeAddress sender) { ByteArray hashOfPayload = new ByteArray(refreshTTLMessage.getHashOfPayload()); ProtectedStorageEntry storedData = map.get(hashOfPayload); @@ -476,14 +667,21 @@ public boolean refreshTTL(RefreshOfferMessage refreshTTLMessage, sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 1000); // Always broadcast refreshes - broadcast(refreshTTLMessage, sender, null, isDataOwner); + broadcaster.broadcast(refreshTTLMessage, sender, null); return true; } + /** + * Removes a ProtectedStorageEntry from the local P2P data storage. If it is successful, it will broadcast that + * change to the P2P network. + * + * @param protectedStorageEntry ProtectedStorageEntry to add to the network + * @param sender local NodeAddress, if available + * @return true if the ProtectedStorageEntry was removed from the local P2P data storage and broadcast + */ public boolean remove(ProtectedStorageEntry protectedStorageEntry, - @Nullable NodeAddress sender, - boolean isDataOwner) { + @Nullable NodeAddress sender) { ProtectedStoragePayload protectedStoragePayload = protectedStorageEntry.getProtectedStoragePayload(); ByteArray hashOfPayload = get32ByteHashAsByteArray(protectedStoragePayload); @@ -504,7 +702,9 @@ public boolean remove(ProtectedStorageEntry protectedStorageEntry, sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.getSequenceNumber(), this.clock.millis())); sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 300); - maybeAddToRemoveAddOncePayloads(protectedStoragePayload, hashOfPayload); + // Update that we have seen this AddOncePayload so the next time it is seen it fails verification + if (protectedStoragePayload instanceof AddOncePayload) + removedAddOncePayloads.add(hashOfPayload); if (storedEntry != null) { // Valid remove entry, do the remove and signal listeners @@ -517,9 +717,9 @@ public boolean remove(ProtectedStorageEntry protectedStorageEntry, printData("after remove"); if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry) { - broadcast(new RemoveMailboxDataMessage((ProtectedMailboxStorageEntry) protectedStorageEntry), sender, null, isDataOwner); + broadcaster.broadcast(new RemoveMailboxDataMessage((ProtectedMailboxStorageEntry) protectedStorageEntry), sender, null); } else { - broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null, isDataOwner); + broadcaster.broadcast(new RemoveDataMessage(protectedStorageEntry), sender, null); } return true; @@ -556,13 +756,6 @@ public void removeInvalidProtectedStorageEntry(ProtectedStorageEntry protectedSt // source (network). } - private void maybeAddToRemoveAddOncePayloads(ProtectedStoragePayload protectedStoragePayload, - ByteArray hashOfData) { - if (protectedStoragePayload instanceof AddOncePayload) { - removedAddOncePayloads.add(hashOfData); - } - } - public ProtectedStorageEntry getProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, KeyPair ownerStoragePubKey) throws CryptoException { @@ -689,11 +882,6 @@ private boolean hasSequenceNrIncreased(int newSequenceNumber, ByteArray hashOfDa } } - private void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, - @Nullable BroadcastHandler.Listener listener, boolean isDataOwner) { - broadcaster.broadcast(message, sender, listener, isDataOwner); - } - public static ByteArray get32ByteHashAsByteArray(NetworkPayload data) { return new ByteArray(P2PDataStorage.get32ByteHash(data)); } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/payload/MailboxStoragePayload.java b/p2p/src/main/java/bisq/network/p2p/storage/payload/MailboxStoragePayload.java index 7f40c8e9def..dbe8f622316 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/payload/MailboxStoragePayload.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/payload/MailboxStoragePayload.java @@ -37,6 +37,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + import javax.annotation.Nullable; /** @@ -63,7 +65,7 @@ public final class MailboxStoragePayload implements ProtectedStoragePayload, Exp private Map extraDataMap; public MailboxStoragePayload(PrefixedSealedAndSignedMessage prefixedSealedAndSignedMessage, - PublicKey senderPubKeyForAddOperation, + @NotNull PublicKey senderPubKeyForAddOperation, PublicKey ownerPubKey) { this.prefixedSealedAndSignedMessage = prefixedSealedAndSignedMessage; this.senderPubKeyForAddOperation = senderPubKeyForAddOperation; diff --git a/p2p/src/main/java/bisq/network/p2p/storage/payload/LazyProcessedPayload.java b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProcessOncePersistableNetworkPayload.java similarity index 72% rename from p2p/src/main/java/bisq/network/p2p/storage/payload/LazyProcessedPayload.java rename to p2p/src/main/java/bisq/network/p2p/storage/payload/ProcessOncePersistableNetworkPayload.java index f63d1cf9b62..70fe1c35abe 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/payload/LazyProcessedPayload.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProcessOncePersistableNetworkPayload.java @@ -20,8 +20,9 @@ import bisq.common.Payload; /** - * Marker interface for payload which gets delayed processed at startup so we don't hit performance too much. - * Used for TradeStatistics and AccountAgeWitness. + * Marker interface for PersistableNetworkPayloads that are only added during the FIRST call to + * P2PDataStorage::processDataResponse. This improves performance for objects that don't go out + * of sync frequently. */ -public interface LazyProcessedPayload extends Payload { +public interface ProcessOncePersistableNetworkPayload extends Payload { } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntry.java b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntry.java index feaa7b19dd6..626ff6abea0 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntry.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntry.java @@ -101,13 +101,12 @@ public boolean isValidForAddOperation() { return false; } - boolean result = mailboxStoragePayload.getSenderPubKeyForAddOperation() != null && - mailboxStoragePayload.getSenderPubKeyForAddOperation().equals(this.getOwnerPubKey()); + boolean result = mailboxStoragePayload.getSenderPubKeyForAddOperation().equals(this.getOwnerPubKey()); if (!result) { String res1 = this.toString(); String res2 = "null"; - if (mailboxStoragePayload != null && mailboxStoragePayload.getOwnerPubKey() != null) + if (mailboxStoragePayload.getOwnerPubKey() != null) res2 = Utilities.encodeToHex(mailboxStoragePayload.getSenderPubKeyForAddOperation().getEncoded(),true); log.warn("ProtectedMailboxStorageEntry::isValidForAddOperation() failed. " + @@ -141,7 +140,7 @@ public boolean isValidForRemoveOperation() { if (!result) { String res1 = this.toString(); String res2 = "null"; - if (mailboxStoragePayload != null && mailboxStoragePayload.getOwnerPubKey() != null) + if (mailboxStoragePayload.getOwnerPubKey() != null) res2 = Utilities.encodeToHex(mailboxStoragePayload.getOwnerPubKey().getEncoded(), true); log.warn("ProtectedMailboxStorageEntry::isValidForRemoveOperation() failed. " + diff --git a/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedStorageEntry.java b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedStorageEntry.java index 1982052bc2b..f550330cfa6 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedStorageEntry.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/payload/ProtectedStorageEntry.java @@ -39,6 +39,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + @Getter @EqualsAndHashCode @Slf4j @@ -47,14 +49,14 @@ public class ProtectedStorageEntry implements NetworkPayload, PersistablePayload private final byte[] ownerPubKeyBytes; transient private final PublicKey ownerPubKey; private final int sequenceNumber; - private byte[] signature; + private final byte[] signature; private long creationTimeStamp; - public ProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, - PublicKey ownerPubKey, - int sequenceNumber, - byte[] signature, - Clock clock) { + public ProtectedStorageEntry(@NotNull ProtectedStoragePayload protectedStoragePayload, + @NotNull PublicKey ownerPubKey, + int sequenceNumber, + byte[] signature, + Clock clock) { this(protectedStoragePayload, Sig.getPublicKeyBytes(ownerPubKey), ownerPubKey, @@ -64,13 +66,13 @@ public ProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, clock); } - protected ProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, - byte[] ownerPubKeyBytes, - PublicKey ownerPubKey, - int sequenceNumber, - byte[] signature, - long creationTimeStamp, - Clock clock) { + protected ProtectedStorageEntry(@NotNull ProtectedStoragePayload protectedStoragePayload, + byte[] ownerPubKeyBytes, + @NotNull PublicKey ownerPubKey, + int sequenceNumber, + byte[] signature, + long creationTimeStamp, + Clock clock) { Preconditions.checkArgument(!(protectedStoragePayload instanceof PersistableNetworkPayload)); @@ -80,21 +82,21 @@ protected ProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, this.sequenceNumber = sequenceNumber; this.signature = signature; - this.creationTimeStamp = creationTimeStamp; - maybeAdjustCreationTimeStamp(clock); + // We don't allow creation date in the future, but we cannot be too strict as clocks are not synced + this.creationTimeStamp = Math.min(creationTimeStamp, clock.millis()); } /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private ProtectedStorageEntry(ProtectedStoragePayload protectedStoragePayload, - byte[] ownerPubKeyBytes, - int sequenceNumber, - byte[] signature, - long creationTimeStamp, - Clock clock) { + private ProtectedStorageEntry(@NotNull ProtectedStoragePayload protectedStoragePayload, + byte[] ownerPubKeyBytes, + int sequenceNumber, + byte[] signature, + long creationTimeStamp, + Clock clock) { this(protectedStoragePayload, ownerPubKeyBytes, Sig.getPublicKeyFromBytes(ownerPubKeyBytes), @@ -135,22 +137,11 @@ public static ProtectedStorageEntry fromProto(protobuf.ProtectedStorageEntry pro // API /////////////////////////////////////////////////////////////////////////////////////////// - public void maybeAdjustCreationTimeStamp(Clock clock) { - // We don't allow creation date in the future, but we cannot be too strict as clocks are not synced - if (creationTimeStamp > clock.millis()) - creationTimeStamp = clock.millis(); - } - public void backDate() { if (protectedStoragePayload instanceof ExpirablePayload) creationTimeStamp -= ((ExpirablePayload) protectedStoragePayload).getTTL() / 2; } - // TODO: only used in tests so find a better way to test and delete public API - public void updateSignature(byte[] signature) { - this.signature = signature; - } - public boolean isExpired(Clock clock) { return protectedStoragePayload instanceof ExpirablePayload && (clock.millis() - creationTimeStamp) > ((ExpirablePayload) protectedStoragePayload).getTTL(); @@ -167,18 +158,15 @@ public boolean isValidForAddOperation() { // TODO: The code currently supports MailboxStoragePayload objects inside ProtectedStorageEntry. Fix this. if (protectedStoragePayload instanceof MailboxStoragePayload) { MailboxStoragePayload mailboxStoragePayload = (MailboxStoragePayload) this.getProtectedStoragePayload(); - return mailboxStoragePayload.getSenderPubKeyForAddOperation() != null && - mailboxStoragePayload.getSenderPubKeyForAddOperation().equals(this.getOwnerPubKey()); + return mailboxStoragePayload.getSenderPubKeyForAddOperation().equals(this.getOwnerPubKey()); } else { - boolean result = this.ownerPubKey != null && - this.protectedStoragePayload != null && - this.ownerPubKey.equals(protectedStoragePayload.getOwnerPubKey()); + boolean result = this.ownerPubKey.equals(protectedStoragePayload.getOwnerPubKey()); if (!result) { String res1 = this.toString(); String res2 = "null"; - if (protectedStoragePayload != null && protectedStoragePayload.getOwnerPubKey() != null) + if (protectedStoragePayload.getOwnerPubKey() != null) res2 = Utilities.encodeToHex(protectedStoragePayload.getOwnerPubKey().getEncoded(), true); log.warn("ProtectedStorageEntry::isValidForAddOperation() failed. Entry owner does not match Payload owner:\n" + @@ -201,7 +189,7 @@ public boolean isValidForRemoveOperation() { if (!result) { String res1 = this.toString(); String res2 = "null"; - if (protectedStoragePayload != null && protectedStoragePayload.getOwnerPubKey() != null) + if (protectedStoragePayload.getOwnerPubKey() != null) res2 = Utilities.encodeToHex(protectedStoragePayload.getOwnerPubKey().getEncoded(), true); log.warn("ProtectedStorageEntry::isValidForRemoveOperation() failed. Entry owner does not match Payload owner:\n" + @@ -239,9 +227,8 @@ public boolean matchesRelevantPubKey(ProtectedStorageEntry protectedStorageEntry boolean result = protectedStorageEntry.getOwnerPubKey().equals(this.ownerPubKey); if (!result) { - log.warn("New data entry does not match our stored data. storedData.ownerPubKey=" + - (protectedStorageEntry.getOwnerPubKey() != null ? protectedStorageEntry.getOwnerPubKey().toString() : "null") + - ", ownerPubKey=" + this.ownerPubKey); + log.warn("New data entry does not match our stored data. storedData.ownerPubKey={}, ownerPubKey={}}", + protectedStorageEntry.getOwnerPubKey().toString(), this.ownerPubKey); } return result; diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageBuildGetDataResponseTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageBuildGetDataResponseTest.java new file mode 100644 index 00000000000..5a65f435c45 --- /dev/null +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageBuildGetDataResponseTest.java @@ -0,0 +1,481 @@ +/* + * 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.network.p2p.storage; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.TestUtils; +import bisq.network.p2p.network.NetworkNode; +import bisq.network.p2p.peers.getdata.messages.GetDataRequest; +import bisq.network.p2p.peers.getdata.messages.GetDataResponse; +import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; +import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; +import bisq.network.p2p.storage.mocks.PersistableNetworkPayloadStub; +import bisq.network.p2p.storage.payload.CapabilityRequiringPayload; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; +import bisq.network.p2p.storage.payload.ProtectedStoragePayload; + +import bisq.common.app.Capabilities; +import bisq.common.app.Capability; +import bisq.common.crypto.Sig; + +import com.google.protobuf.Message; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + + + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class P2PDataStorageBuildGetDataResponseTest { + abstract static class P2PDataStorageBuildGetDataResponseTestBase { + // GIVEN null & non-null supportedCapabilities + private TestState testState; + + abstract GetDataRequest buildGetDataRequest(int nonce, Set knownKeys); + + @Mock + NetworkNode networkNode; + + private NodeAddress localNodeAddress; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + this.testState = new TestState(); + + this.localNodeAddress = new NodeAddress("localhost", 8080); + when(networkNode.getNodeAddress()).thenReturn(this.localNodeAddress); + + // Set up basic capabilities to ensure message contains it + Capabilities.app.addAll(Capability.MEDIATION); + } + + static class RequiredCapabilitiesPNPStub extends PersistableNetworkPayloadStub + implements CapabilityRequiringPayload { + Capabilities capabilities; + + RequiredCapabilitiesPNPStub(Capabilities capabilities, byte[] hash) { + super(hash); + this.capabilities = capabilities; + } + + @Override + public Capabilities getRequiredCapabilities() { + return capabilities; + } + } + + /** + * Generates a unique ProtectedStorageEntry that is valid for add. This is used to initialize P2PDataStorage state + * so the tests can validate the correct behavior. Adds of identical payloads with different sequence numbers + * is not supported. + */ + private ProtectedStorageEntry getProtectedStorageEntryForAdd() throws NoSuchAlgorithmException { + return getProtectedStorageEntryForAdd(null); + } + + private ProtectedStorageEntry getProtectedStorageEntryForAdd(Capabilities requiredCapabilities) + throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + + // Payload stub + ProtectedStoragePayload protectedStoragePayload; + + if (requiredCapabilities == null) + protectedStoragePayload = mock(ProtectedStoragePayload.class); + else { + protectedStoragePayload = mock(ProtectedStoragePayload.class, + withSettings().extraInterfaces(CapabilityRequiringPayload.class)); + when(((CapabilityRequiringPayload) protectedStoragePayload).getRequiredCapabilities()) + .thenReturn(requiredCapabilities); + } + + Message messageMock = mock(Message.class); + when(messageMock.toByteArray()).thenReturn(Sig.getPublicKeyBytes(ownerKeys.getPublic())); + when(protectedStoragePayload.toProtoMessage()).thenReturn(messageMock); + + // Entry stub + ProtectedStorageEntry stub = mock(ProtectedStorageEntry.class); + when(stub.getOwnerPubKey()).thenReturn(ownerKeys.getPublic()); + when(stub.isValidForAddOperation()).thenReturn(true); + when(stub.matchesRelevantPubKey(any(ProtectedStorageEntry.class))).thenReturn(true); + when(stub.getSequenceNumber()).thenReturn(1); + when(stub.getProtectedStoragePayload()).thenReturn(protectedStoragePayload); + + return stub; + } + + // TESTCASE: Given a GetDataRequest w/ unknown PNP, nothing is sent back + @Test + public void buildGetDataResponse_unknownPNPDoNothing() { + PersistableNetworkPayload fromPeer = new PersistableNetworkPayloadStub(new byte[]{1}); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>(Collections.singletonList(fromPeer.getHash()))); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/ known PNP, nothing is sent back + @Test + public void buildGetDataResponse_knownPNPDoNothing() { + PersistableNetworkPayload fromPeerAndLocal = new PersistableNetworkPayloadStub(new byte[]{1}); + + this.testState.mockedStorage.addPersistableNetworkPayload( + fromPeerAndLocal, this.localNodeAddress, false); + + GetDataRequest getDataRequest = + this.buildGetDataRequest( + 1, + new HashSet<>(Collections.singletonList(fromPeerAndLocal.getHash()))); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP, send it back + @Test + public void buildGetDataResponse_unknownPNPSendBack() { + PersistableNetworkPayload onlyLocal = new PersistableNetworkPayloadStub(new byte[]{1}); + + this.testState.mockedStorage.addPersistableNetworkPayload( + onlyLocal, this.localNodeAddress, false); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().contains(onlyLocal)); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP, don't send more than truncation limit + @Test + public void buildGetDataResponse_unknownPNPSendBackTruncation() { + PersistableNetworkPayload onlyLocal1 = new PersistableNetworkPayloadStub(new byte[]{1}); + PersistableNetworkPayload onlyLocal2 = new PersistableNetworkPayloadStub(new byte[]{2}); + + this.testState.mockedStorage.addPersistableNetworkPayload( + onlyLocal1, this.localNodeAddress, false); + this.testState.mockedStorage.addPersistableNetworkPayload( + onlyLocal2, this.localNodeAddress, false); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertTrue(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertEquals(1, getDataResponse.getPersistableNetworkPayloadSet().size()); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().contains(onlyLocal1)); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP, but missing required capabilities, nothing is sent back + @Test + public void buildGetDataResponse_unknownPNPCapabilitiesMismatchDontSendBack() { + PersistableNetworkPayload onlyLocal = + new RequiredCapabilitiesPNPStub(new Capabilities(Collections.singletonList(Capability.MEDIATION)), + new byte[]{1}); + + this.testState.mockedStorage.addPersistableNetworkPayload( + onlyLocal, this.localNodeAddress, false); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 2, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP that requires capabilities (and they match) send it back + @Test + public void buildGetDataResponse_unknownPNPCapabilitiesMatch() { + PersistableNetworkPayload onlyLocal = + new RequiredCapabilitiesPNPStub(new Capabilities(Collections.singletonList(Capability.MEDIATION)), + new byte[]{1}); + + this.testState.mockedStorage.addPersistableNetworkPayload( + onlyLocal, this.localNodeAddress, false); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(Collections.singletonList(Capability.MEDIATION)); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 2, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().contains(onlyLocal)); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/ unknown PSE, nothing is sent back + @Test + public void buildGetDataResponse_unknownPSEDoNothing() throws NoSuchAlgorithmException { + ProtectedStorageEntry fromPeer = getProtectedStorageEntryForAdd(); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, + new HashSet<>(Collections.singletonList( + P2PDataStorage.get32ByteHash(fromPeer.getProtectedStoragePayload())))); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/ known PSE, nothing is sent back + @Test + public void buildGetDataResponse_knownPSEDoNothing() throws NoSuchAlgorithmException { + ProtectedStorageEntry fromPeerAndLocal = getProtectedStorageEntryForAdd(); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, + new HashSet<>(Collections.singletonList( + P2PDataStorage.get32ByteHash(fromPeerAndLocal.getProtectedStoragePayload())))); + + this.testState.mockedStorage.addProtectedStorageEntry( + fromPeerAndLocal, this.localNodeAddress, null); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PSE, send it back + @Test + public void buildGetDataResponse_unknownPSESendBack() throws NoSuchAlgorithmException { + ProtectedStorageEntry onlyLocal = getProtectedStorageEntryForAdd(); + + GetDataRequest getDataRequest = this.buildGetDataRequest(1, new HashSet<>()); + + this.testState.mockedStorage.addProtectedStorageEntry( + onlyLocal, this.localNodeAddress, null); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().contains(onlyLocal)); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP, don't send more than truncation limit + @Test + public void buildGetDataResponse_unknownPSESendBackTruncation() throws NoSuchAlgorithmException { + ProtectedStorageEntry onlyLocal1 = getProtectedStorageEntryForAdd(); + ProtectedStorageEntry onlyLocal2 = getProtectedStorageEntryForAdd(); + + GetDataRequest getDataRequest = this.buildGetDataRequest(1, new HashSet<>()); + + this.testState.mockedStorage.addProtectedStorageEntry( + onlyLocal1, this.localNodeAddress, null); + this.testState.mockedStorage.addProtectedStorageEntry( + onlyLocal2, this.localNodeAddress, null); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 1, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertTrue(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertEquals(1, getDataResponse.getDataSet().size()); + Assert.assertTrue( + getDataResponse.getDataSet().contains(onlyLocal1) + || getDataResponse.getDataSet().contains(onlyLocal2)); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP, but missing required capabilities, nothing is sent back + @Test + public void buildGetDataResponse_unknownPSECapabilitiesMismatchDontSendBack() throws NoSuchAlgorithmException { + ProtectedStorageEntry onlyLocal = + getProtectedStorageEntryForAdd(new Capabilities(Collections.singletonList(Capability.MEDIATION))); + + this.testState.mockedStorage.addProtectedStorageEntry( + onlyLocal, this.localNodeAddress, null); + + GetDataRequest getDataRequest = this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 2, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().isEmpty()); + } + + // TESTCASE: Given a GetDataRequest w/o known PNP that requires capabilities (and they match) send it back + @Test + public void buildGetDataResponse_unknownPSECapabilitiesMatch() throws NoSuchAlgorithmException { + ProtectedStorageEntry onlyLocal = + getProtectedStorageEntryForAdd(new Capabilities(Collections.singletonList(Capability.MEDIATION))); + + this.testState.mockedStorage.addProtectedStorageEntry( + onlyLocal, this.localNodeAddress, null); + + GetDataRequest getDataRequest = + this.buildGetDataRequest(1, new HashSet<>()); + + AtomicBoolean outPNPTruncated = new AtomicBoolean(false); + AtomicBoolean outPSETruncated = new AtomicBoolean(false); + Capabilities peerCapabilities = new Capabilities(Collections.singletonList(Capability.MEDIATION)); + GetDataResponse getDataResponse = this.testState.mockedStorage.buildGetDataResponse( + getDataRequest, 2, outPNPTruncated, outPSETruncated, peerCapabilities); + + Assert.assertFalse(outPNPTruncated.get()); + Assert.assertFalse(outPSETruncated.get()); + Assert.assertEquals(1, getDataResponse.getRequestNonce()); + Assert.assertEquals(getDataRequest instanceof GetUpdatedDataRequest, getDataResponse.isGetUpdatedDataResponse()); + Assert.assertEquals(getDataResponse.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataResponse.getPersistableNetworkPayloadSet().isEmpty()); + Assert.assertTrue(getDataResponse.getDataSet().contains(onlyLocal)); + } + } + + public static class P2PDataStorageBuildGetDataResponseTestPreliminary extends P2PDataStorageBuildGetDataResponseTestBase { + + @Override + GetDataRequest buildGetDataRequest(int nonce, Set knownKeys) { + return new PreliminaryGetDataRequest(nonce, knownKeys); + } + } + + public static class P2PDataStorageBuildGetDataResponseTestUpdated extends P2PDataStorageBuildGetDataResponseTestBase { + + @Override + GetDataRequest buildGetDataRequest(int nonce, Set knownKeys) { + return new GetUpdatedDataRequest(new NodeAddress("peer", 10), nonce, knownKeys); + } + } +} diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageClientAPITest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageClientAPITest.java index 4e0cb6c895f..789411af399 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageClientAPITest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageClientAPITest.java @@ -71,9 +71,9 @@ public void getProtectedStorageEntry_NoExist() throws NoSuchAlgorithmException, ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); - this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true); + this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true, true, true); } // TESTCASE: Adding an entry from the getProtectedStorageEntry API of an existing item correctly updates the item @@ -84,13 +84,13 @@ public void getProtectedStorageEntry() throws NoSuchAlgorithmException, CryptoEx ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true); + this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null); - this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true); + this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true, true, true); } // TESTCASE: Adding an entry from the getProtectedStorageEntry API of an existing item (added from onMessage path) correctly updates the item @@ -108,9 +108,9 @@ public void getProtectedStorageEntry_FirstOnMessageSecondAPI() throws NoSuchAlgo SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); - this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true); + this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, true, true, true, true); } // TESTCASE: Updating an entry from the getRefreshTTLMessage API correctly errors if the item hasn't been seen @@ -123,9 +123,9 @@ public void getRefreshTTLMessage_NoExists() throws NoSuchAlgorithmException, Cry RefreshOfferMessage refreshOfferMessage = this.testState.mockedStorage.getRefreshTTLMessage(protectedStoragePayload, ownerKeys); SavedTestState beforeState = this.testState.saveTestState(refreshOfferMessage); - Assert.assertFalse(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress(), true)); + Assert.assertFalse(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress())); - this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, false, true); + this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, false); } // TESTCASE: Updating an entry from the getRefreshTTLMessage API correctly "refreshes" the item @@ -135,19 +135,19 @@ public void getRefreshTTLMessage() throws NoSuchAlgorithmException, CryptoExcept ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true); + this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null); RefreshOfferMessage refreshOfferMessage = this.testState.mockedStorage.getRefreshTTLMessage(protectedStoragePayload, ownerKeys); - this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress(), true); + this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress()); refreshOfferMessage = this.testState.mockedStorage.getRefreshTTLMessage(protectedStoragePayload, ownerKeys); this.testState.incrementClock(); SavedTestState beforeState = this.testState.saveTestState(refreshOfferMessage); - Assert.assertTrue(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress(), true)); + Assert.assertTrue(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress())); - this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, true, true); + this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, true); } // TESTCASE: Updating an entry from the getRefreshTTLMessage API correctly "refreshes" the item when it was originally added from onMessage path @@ -157,7 +157,7 @@ public void getRefreshTTLMessage_FirstOnMessageSecondAPI() throws NoSuchAlgorith ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true); + this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null); Connection mockedConnection = mock(Connection.class); when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(TestState.getTestNodeAddress())); @@ -169,9 +169,9 @@ public void getRefreshTTLMessage_FirstOnMessageSecondAPI() throws NoSuchAlgorith this.testState.incrementClock(); SavedTestState beforeState = this.testState.saveTestState(refreshOfferMessage); - Assert.assertTrue(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress(), true)); + Assert.assertTrue(this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress())); - this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, true, true); + this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, true); } // TESTCASE: Removing a non-existent mailbox entry from the getMailboxDataWithSignedSeqNr API @@ -186,9 +186,9 @@ public void getMailboxDataWithSignedSeqNr_RemoveNoExist() throws NoSuchAlgorithm this.testState.mockedStorage.getMailboxDataWithSignedSeqNr(mailboxStoragePayload, receiverKeys, receiverKeys.getPublic()); SavedTestState beforeState = this.testState.saveTestState(protectedMailboxStorageEntry); - Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress(), true)); + Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress())); - this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, false, false, false, true, true); + this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, false, false, true, true); } // TESTCASE: Adding, then removing a mailbox message from the getMailboxDataWithSignedSeqNr API @@ -202,15 +202,15 @@ public void getMailboxDataWithSignedSeqNr_AddThenRemove() throws NoSuchAlgorithm ProtectedMailboxStorageEntry protectedMailboxStorageEntry = this.testState.mockedStorage.getMailboxDataWithSignedSeqNr(mailboxStoragePayload, senderKeys, receiverKeys.getPublic()); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedMailboxStorageEntry, TestState.getTestNodeAddress(), null)); protectedMailboxStorageEntry = this.testState.mockedStorage.getMailboxDataWithSignedSeqNr(mailboxStoragePayload, receiverKeys, receiverKeys.getPublic()); SavedTestState beforeState = this.testState.saveTestState(protectedMailboxStorageEntry); - Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress(), true)); + Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress())); - this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, true, true, true, true,true); + this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, true, true, true, true); } // TESTCASE: Removing a mailbox message that was added from the onMessage handler @@ -235,8 +235,8 @@ public void getMailboxDataWithSignedSeqNr_ValidRemoveAddFromMessage() throws NoS this.testState.mockedStorage.getMailboxDataWithSignedSeqNr(mailboxStoragePayload, receiverKeys, receiverKeys.getPublic()); SavedTestState beforeState = this.testState.saveTestState(protectedMailboxStorageEntry); - Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress(), true)); + Assert.assertTrue(this.testState.mockedStorage.remove(protectedMailboxStorageEntry, TestState.getTestNodeAddress())); - this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, true, true, true, true,true); + this.testState.verifyProtectedStorageRemove(beforeState, protectedMailboxStorageEntry, true, true, true, true); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageGetDataIntegrationTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageGetDataIntegrationTest.java new file mode 100644 index 00000000000..21e48d2757b --- /dev/null +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageGetDataIntegrationTest.java @@ -0,0 +1,193 @@ +/* + * 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.network.p2p.storage; + +import bisq.network.p2p.TestUtils; +import bisq.network.p2p.peers.getdata.messages.GetDataRequest; +import bisq.network.p2p.peers.getdata.messages.GetDataResponse; +import bisq.network.p2p.storage.mocks.PersistableExpirableProtectedStoragePayloadStub; +import bisq.network.p2p.storage.mocks.ProtectedStoragePayloadStub; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; +import bisq.network.p2p.storage.payload.ProtectedStoragePayload; + +import bisq.common.app.Capabilities; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class P2PDataStorageGetDataIntegrationTest { + + /** + * Generates a unique ProtectedStorageEntry that is valid for add and remove. + */ + private ProtectedStorageEntry getProtectedStorageEntry() throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + + return getProtectedStorageEntry( + ownerKeys.getPublic(), new ProtectedStoragePayloadStub(ownerKeys.getPublic()), 1); + } + + private ProtectedStorageEntry getProtectedStorageEntry( + PublicKey ownerPubKey, + ProtectedStoragePayload protectedStoragePayload, + int sequenceNumber) { + ProtectedStorageEntry stub = mock(ProtectedStorageEntry.class); + when(stub.getOwnerPubKey()).thenReturn(ownerPubKey); + when(stub.isValidForAddOperation()).thenReturn(true); + when(stub.isValidForRemoveOperation()).thenReturn(true); + when(stub.matchesRelevantPubKey(any(ProtectedStorageEntry.class))).thenReturn(true); + when(stub.getSequenceNumber()).thenReturn(sequenceNumber); + when(stub.getProtectedStoragePayload()).thenReturn(protectedStoragePayload); + + return stub; + } + + // TESTCASE: Basic synchronization of a ProtectedStorageEntry works between a seed node and client node + @Test + public void basicSynchronizationWorks() throws NoSuchAlgorithmException { + TestState seedNodeTestState = new TestState(); + P2PDataStorage seedNode = seedNodeTestState.mockedStorage; + + TestState clientNodeTestState = new TestState(); + P2PDataStorage clientNode = clientNodeTestState.mockedStorage; + + ProtectedStorageEntry onSeedNode = getProtectedStorageEntry(); + seedNode.addProtectedStorageEntry(onSeedNode, null, null); + + GetDataRequest getDataRequest = clientNode.buildPreliminaryGetDataRequest(1); + + GetDataResponse getDataResponse = seedNode.buildGetDataResponse( + getDataRequest, 1, new AtomicBoolean(), new AtomicBoolean(), new Capabilities()); + + TestState.SavedTestState beforeState = clientNodeTestState.saveTestState(onSeedNode); + clientNode.processGetDataResponse(getDataResponse, null); + + clientNodeTestState.verifyProtectedStorageAdd( + beforeState, onSeedNode, true, true, false, true); + } + + // TESTCASE: Synchronization after peer restart works for in-memory ProtectedStorageEntrys + @Test + public void basicSynchronizationWorksAfterRestartTransient() throws NoSuchAlgorithmException { + ProtectedStorageEntry transientEntry = getProtectedStorageEntry(); + + TestState seedNodeTestState = new TestState(); + P2PDataStorage seedNode = seedNodeTestState.mockedStorage; + + TestState clientNodeTestState = new TestState(); + P2PDataStorage clientNode = clientNodeTestState.mockedStorage; + + seedNode.addProtectedStorageEntry(transientEntry, null, null); + + clientNode.addProtectedStorageEntry(transientEntry, null, null); + + clientNodeTestState.simulateRestart(); + clientNode = clientNodeTestState.mockedStorage; + + GetDataRequest getDataRequest = clientNode.buildPreliminaryGetDataRequest(1); + + GetDataResponse getDataResponse = seedNode.buildGetDataResponse( + getDataRequest, 1, new AtomicBoolean(), new AtomicBoolean(), new Capabilities()); + + TestState.SavedTestState beforeState = clientNodeTestState.saveTestState(transientEntry); + clientNode.processGetDataResponse(getDataResponse, null); + + clientNodeTestState.verifyProtectedStorageAdd( + beforeState, transientEntry, true, true, false, true); + } + + // TESTCASE: Synchronization after peer restart works for in-memory ProtectedStorageEntrys + @Test + public void basicSynchronizationWorksAfterRestartPersistent() throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + ProtectedStoragePayload persistentPayload = + new PersistableExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); + ProtectedStorageEntry persistentEntry = getProtectedStorageEntry(ownerKeys.getPublic(), persistentPayload, 1); + + TestState seedNodeTestState = new TestState(); + P2PDataStorage seedNode = seedNodeTestState.mockedStorage; + + TestState clientNodeTestState = new TestState(); + P2PDataStorage clientNode = clientNodeTestState.mockedStorage; + + seedNode.addProtectedStorageEntry(persistentEntry, null, null); + + clientNode.addProtectedStorageEntry(persistentEntry, null, null); + + clientNodeTestState.simulateRestart(); + clientNode = clientNodeTestState.mockedStorage; + + GetDataRequest getDataRequest = clientNode.buildPreliminaryGetDataRequest(1); + + GetDataResponse getDataResponse = seedNode.buildGetDataResponse( + getDataRequest, 1, new AtomicBoolean(), new AtomicBoolean(), new Capabilities()); + + TestState.SavedTestState beforeState = clientNodeTestState.saveTestState(persistentEntry); + clientNode.processGetDataResponse(getDataResponse, null); + + clientNodeTestState.verifyProtectedStorageAdd( + beforeState, persistentEntry, false, false, false, false); + Assert.assertTrue(clientNodeTestState.mockedStorage.getMap().containsValue(persistentEntry)); + } + + // TESTCASE: Removes seen only by the seednode should be replayed on the client node + // during startup + // XXXBUGXXX: #3610 Lost removes are never replayed. + @Test + public void lostRemoveNeverUpdated() throws NoSuchAlgorithmException { + TestState seedNodeTestState = new TestState(); + P2PDataStorage seedNode = seedNodeTestState.mockedStorage; + + TestState clientNodeTestState = new TestState(); + P2PDataStorage clientNode = clientNodeTestState.mockedStorage; + + // Both nodes see the add + KeyPair ownerKeys = TestUtils.generateKeyPair(); + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + ProtectedStorageEntry onSeedNodeAndClientNode = getProtectedStorageEntry( + ownerKeys.getPublic(), protectedStoragePayload, 1); + seedNode.addProtectedStorageEntry(onSeedNodeAndClientNode, null, null); + clientNode.addProtectedStorageEntry(onSeedNodeAndClientNode, null, null); + + // Seed node sees the remove, but client node does not + seedNode.remove(getProtectedStorageEntry( + ownerKeys.getPublic(), protectedStoragePayload, 2), null); + + GetDataRequest getDataRequest = clientNode.buildPreliminaryGetDataRequest(1); + + GetDataResponse getDataResponse = seedNode.buildGetDataResponse( + getDataRequest, 1, new AtomicBoolean(), new AtomicBoolean(), new Capabilities()); + + TestState.SavedTestState beforeState = clientNodeTestState.saveTestState(onSeedNodeAndClientNode); + clientNode.processGetDataResponse(getDataResponse, null); + + // Should succeed + clientNodeTestState.verifyProtectedStorageRemove( + beforeState, onSeedNodeAndClientNode, false, false, false, false); + } +} diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageOnMessageHandlerTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageOnMessageHandlerTest.java index 5a73e8acdd2..b5fe473732e 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageOnMessageHandlerTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageOnMessageHandlerTest.java @@ -69,7 +69,7 @@ public void invalidBroadcastMessage() { this.testState.mockedStorage.onMessage(envelope, mockedConnection); verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class)); - verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null), anyBoolean()); + verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null)); } @Test @@ -82,7 +82,7 @@ public void unsupportedBroadcastMessage() { this.testState.mockedStorage.onMessage(envelope, mockedConnection); verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class)); - verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null), anyBoolean()); + verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null)); } @Test @@ -96,6 +96,6 @@ public void invalidConnectionObject() { this.testState.mockedStorage.onMessage(envelope, mockedConnection); verify(this.testState.appendOnlyDataStoreListener, never()).onAdded(any(PersistableNetworkPayload.class)); - verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null), anyBoolean()); + verify(this.testState.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), eq(null)); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoragePersistableNetworkPayloadTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoragePersistableNetworkPayloadTest.java index 9213bcb0fbe..8f1dafd8d72 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoragePersistableNetworkPayloadTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoragePersistableNetworkPayloadTest.java @@ -49,11 +49,10 @@ * Each subclass (Payload type) can optionally add additional tests that verify functionality only relevant * to that payload. * - * Each test case is run through 4 entry points to verify the correct behavior: + * Each test case is run through 3 entry points to verify the correct behavior: * - * 1. RequestData path [addPersistableNetworkPayloadFromInitialRequest] - * 2 & 3 Client API [addPersistableNetworkPayload(reBroadcast=(true && false))] - * 4. onMessage() [onMessage(AddPersistableNetworkPayloadMessage)] + * 1 & 2 Client API [addPersistableNetworkPayload(reBroadcast=(true && false))] + * 3. onMessage() [onMessage(AddPersistableNetworkPayloadMessage)] */ @SuppressWarnings("unused") public class P2PDataStoragePersistableNetworkPayloadTest { @@ -66,14 +65,8 @@ public abstract static class AddPersistableNetworkPayloadTest { public TestCase testCase; @Parameterized.Parameter(1) - public boolean allowBroadcast; - - @Parameterized.Parameter(2) public boolean reBroadcast; - @Parameterized.Parameter(3) - public boolean checkDate; - PersistableNetworkPayload persistableNetworkPayload; abstract PersistableNetworkPayload createInstance(); @@ -81,25 +74,18 @@ public abstract static class AddPersistableNetworkPayloadTest { enum TestCase { PUBLIC_API, ON_MESSAGE, - INIT, - } - - boolean expectBroadcastOnStateChange() { - return this.testCase != TestCase.INIT; } - boolean expectedIsDataOwner() { - return this.testCase == TestCase.PUBLIC_API; - } - - void doAddAndVerify(PersistableNetworkPayload persistableNetworkPayload, boolean expectedReturnValue, boolean expectedStateChange) { + void doAddAndVerify(PersistableNetworkPayload persistableNetworkPayload, + boolean expectedReturnValue, + boolean expectedHashMapAndDataStoreUpdated, + boolean expectedListenersSignaled, + boolean expectedBroadcast) { SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); - if (this.testCase == TestCase.INIT) { - Assert.assertEquals(expectedReturnValue, this.testState.mockedStorage.addPersistableNetworkPayloadFromInitialRequest(persistableNetworkPayload)); - } else if (this.testCase == TestCase.PUBLIC_API) { + if (this.testCase == TestCase.PUBLIC_API) { Assert.assertEquals(expectedReturnValue, - this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload, TestState.getTestNodeAddress(), true, this.allowBroadcast, this.reBroadcast, this.checkDate)); + this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload, TestState.getTestNodeAddress(), this.reBroadcast)); } else { // onMessage Connection mockedConnection = mock(Connection.class); when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(TestState.getTestNodeAddress())); @@ -107,7 +93,7 @@ void doAddAndVerify(PersistableNetworkPayload persistableNetworkPayload, boolean testState.mockedStorage.onMessage(new AddPersistableNetworkPayloadMessage(persistableNetworkPayload), mockedConnection); } - this.testState.verifyPersistableAdd(beforeState, persistableNetworkPayload, expectedStateChange, this.expectBroadcastOnStateChange(), this.expectedIsDataOwner()); + this.testState.verifyPersistableAdd(beforeState, persistableNetworkPayload, expectedHashMapAndDataStoreUpdated, expectedListenersSignaled, expectedBroadcast); } @Before @@ -121,18 +107,15 @@ public void setup() { public static Collection data() { List data = new ArrayList<>(); - // Init doesn't use other parameters - data.add(new Object[] { TestCase.INIT, false, false, false }); - // onMessage doesn't use other parameters - data.add(new Object[] { TestCase.ON_MESSAGE, false, false, false }); + data.add(new Object[] { TestCase.ON_MESSAGE, false }); // Client API uses two permutations // Normal path - data.add(new Object[] { TestCase.PUBLIC_API, true, true, false }); + data.add(new Object[] { TestCase.PUBLIC_API, true }); // Refresh path - data.add(new Object[] { TestCase.PUBLIC_API, true, false, false }); + data.add(new Object[] { TestCase.PUBLIC_API, false }); return data; } @@ -140,17 +123,15 @@ public static Collection data() { @Test public void addPersistableNetworkPayload() { // First add should succeed regardless of parameters - doAddAndVerify(this.persistableNetworkPayload, true, true); + doAddAndVerify(this.persistableNetworkPayload, true, true, true, true); } @Test public void addPersistableNetworkPayloadDuplicate() { - doAddAndVerify(this.persistableNetworkPayload, true, true); + doAddAndVerify(this.persistableNetworkPayload, true, true, true, true); - // Second call only succeeds if reBroadcast was set or we are adding through the init - // path which just overwrites - boolean expectedReturnValue = this.reBroadcast || this.testCase == TestCase.INIT; - doAddAndVerify(this.persistableNetworkPayload, expectedReturnValue, false); + // We return true and broadcast if reBroadcast is set + doAddAndVerify(this.persistableNetworkPayload, this.reBroadcast, false, false, this.reBroadcast); } } @@ -167,7 +148,7 @@ PersistableNetworkPayloadStub createInstance() { public void invalidHash() { PersistableNetworkPayload persistableNetworkPayload = new PersistableNetworkPayloadStub(false); - doAddAndVerify(persistableNetworkPayload, false, false); + doAddAndVerify(persistableNetworkPayload, false, false, false, false); } } @@ -190,7 +171,7 @@ public void outOfTolerance() { // The onMessage path checks for tolerance boolean expectedReturn = this.testCase != TestCase.ON_MESSAGE; - doAddAndVerify(persistableNetworkPayload, expectedReturn, expectedReturn); + doAddAndVerify(persistableNetworkPayload, expectedReturn, expectedReturn, expectedReturn, expectedReturn); } } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProcessGetDataResponse.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProcessGetDataResponse.java new file mode 100644 index 00000000000..0a3c7cf9184 --- /dev/null +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProcessGetDataResponse.java @@ -0,0 +1,263 @@ +/* + * 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.network.p2p.storage; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.TestUtils; +import bisq.network.p2p.peers.getdata.messages.GetDataResponse; +import bisq.network.p2p.storage.mocks.PersistableNetworkPayloadStub; +import bisq.network.p2p.storage.mocks.ProtectedStoragePayloadStub; +import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; +import bisq.network.p2p.storage.payload.ProtectedStoragePayload; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.mockito.MockitoAnnotations; + +public class P2PDataStorageProcessGetDataResponse { + private TestState testState; + + private NodeAddress peerNodeAddress; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + this.testState = new TestState(); + + this.peerNodeAddress = new NodeAddress("peer", 8080); + } + + static private GetDataResponse buildGetDataResponse(PersistableNetworkPayload persistableNetworkPayload) { + return buildGetDataResponse(Collections.emptyList(), Collections.singletonList(persistableNetworkPayload)); + } + + static private GetDataResponse buildGetDataResponse(ProtectedStorageEntry protectedStorageEntry) { + return buildGetDataResponse(Collections.singletonList(protectedStorageEntry), Collections.emptyList()); + } + + static private GetDataResponse buildGetDataResponse( + List protectedStorageEntries, + List persistableNetworkPayloads) { + return new GetDataResponse( + new HashSet<>(protectedStorageEntries), + new HashSet<>(persistableNetworkPayloads), + 1, + false); + } + + /** + * Generates a unique ProtectedStorageEntry that is valid for add. This is used to initialize P2PDataStorage state + * so the tests can validate the correct behavior. Adds of identical payloads with different sequence numbers + * is not supported. + */ + private ProtectedStorageEntry getProtectedStorageEntryForAdd() throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + + ProtectedStorageEntry stub = mock(ProtectedStorageEntry.class); + when(stub.getOwnerPubKey()).thenReturn(ownerKeys.getPublic()); + when(stub.isValidForAddOperation()).thenReturn(true); + when(stub.matchesRelevantPubKey(any(ProtectedStorageEntry.class))).thenReturn(true); + when(stub.getSequenceNumber()).thenReturn(1); + when(stub.getProtectedStoragePayload()).thenReturn(protectedStoragePayload); + + return stub; + } + + static class LazyPersistableNetworkPayloadStub extends PersistableNetworkPayloadStub + implements ProcessOncePersistableNetworkPayload { + + LazyPersistableNetworkPayloadStub(byte[] hash) { + super(hash); + } + + LazyPersistableNetworkPayloadStub(boolean validHashSize) { + super(validHashSize); + } + } + + // TESTCASE: GetDataResponse w/ missing PNP is added with no broadcast or listener signal + // XXXBUGXXX: We signal listeners w/ non ProcessOncePersistableNetworkPayloads + @Test + public void processGetDataResponse_newPNPUpdatesState() { + PersistableNetworkPayload persistableNetworkPayload = new PersistableNetworkPayloadStub(new byte[] { 1 }); + + GetDataResponse getDataResponse = buildGetDataResponse(persistableNetworkPayload); + + TestState.SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, persistableNetworkPayload, true, true, false); + } + + // TESTCASE: GetDataResponse w/ invalid PNP does nothing (LazyProcessed) + @Test + public void processGetDataResponse_newInvalidPNPDoesNothing() { + PersistableNetworkPayload persistableNetworkPayload = new LazyPersistableNetworkPayloadStub(false); + + GetDataResponse getDataResponse = buildGetDataResponse(persistableNetworkPayload); + + TestState.SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, persistableNetworkPayload, false, false, false); + } + + // TESTCASE: GetDataResponse w/ existing PNP changes no state + @Test + public void processGetDataResponse_duplicatePNPDoesNothing() { + PersistableNetworkPayload persistableNetworkPayload = new PersistableNetworkPayloadStub(new byte[] { 1 }); + this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload, + this.peerNodeAddress, false); + + GetDataResponse getDataResponse = buildGetDataResponse(persistableNetworkPayload); + + TestState.SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, persistableNetworkPayload, false, false, false); + } + + // TESTCASE: GetDataResponse w/ missing PNP is added with no broadcast or listener signal (ProcessOncePersistableNetworkPayload) + @Test + public void processGetDataResponse_newPNPUpdatesState_LazyProcessed() { + PersistableNetworkPayload persistableNetworkPayload = new LazyPersistableNetworkPayloadStub(new byte[] { 1 }); + + GetDataResponse getDataResponse = buildGetDataResponse(persistableNetworkPayload); + + TestState.SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, persistableNetworkPayload, true, false, false); + } + + // TESTCASE: GetDataResponse w/ existing PNP changes no state (ProcessOncePersistableNetworkPayload) + @Test + public void processGetDataResponse_duplicatePNPDoesNothing_LazyProcessed() { + PersistableNetworkPayload persistableNetworkPayload = new LazyPersistableNetworkPayloadStub(new byte[] { 1 }); + this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload, + this.peerNodeAddress, false); + + GetDataResponse getDataResponse = buildGetDataResponse(persistableNetworkPayload); + + TestState.SavedTestState beforeState = this.testState.saveTestState(persistableNetworkPayload); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, persistableNetworkPayload, false, false, false); + } + + // TESTCASE: Second call to processGetDataResponse adds PNP for non-ProcessOncePersistableNetworkPayloads + @Test + public void processGetDataResponse_secondProcessNewPNPUpdatesState() { + PersistableNetworkPayload addFromFirstProcess = new PersistableNetworkPayloadStub(new byte[] { 1 }); + GetDataResponse getDataResponse = buildGetDataResponse(addFromFirstProcess); + + TestState.SavedTestState beforeState = this.testState.saveTestState(addFromFirstProcess); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, addFromFirstProcess, true, true, false); + + PersistableNetworkPayload addFromSecondProcess = new PersistableNetworkPayloadStub(new byte[] { 2 }); + getDataResponse = buildGetDataResponse(addFromSecondProcess); + beforeState = this.testState.saveTestState(addFromSecondProcess); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, addFromSecondProcess, true, true, false); + } + + // TESTCASE: Second call to processGetDataResponse does not add any PNP (LazyProcessed) + @Test + public void processGetDataResponse_secondProcessNoPNPUpdates_LazyProcessed() { + PersistableNetworkPayload addFromFirstProcess = new LazyPersistableNetworkPayloadStub(new byte[] { 1 }); + GetDataResponse getDataResponse = buildGetDataResponse(addFromFirstProcess); + + TestState.SavedTestState beforeState = this.testState.saveTestState(addFromFirstProcess); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, addFromFirstProcess, true, false, false); + + PersistableNetworkPayload addFromSecondProcess = new LazyPersistableNetworkPayloadStub(new byte[] { 2 }); + getDataResponse = buildGetDataResponse(addFromSecondProcess); + beforeState = this.testState.saveTestState(addFromSecondProcess); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyPersistableAdd( + beforeState, addFromSecondProcess, false, false, false); + } + + // TESTCASE: GetDataResponse w/ missing PSE is added with no broadcast or listener signal + // XXXBUGXXX: We signal listeners for all ProtectedStorageEntrys + @Test + public void processGetDataResponse_newPSEUpdatesState() throws NoSuchAlgorithmException { + ProtectedStorageEntry protectedStorageEntry = getProtectedStorageEntryForAdd(); + GetDataResponse getDataResponse = buildGetDataResponse(protectedStorageEntry); + + TestState.SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, true, true, false, true); + } + + // TESTCASE: GetDataResponse w/ existing PSE changes no state + @Test + public void processGetDataResponse_duplicatePSEDoesNothing() throws NoSuchAlgorithmException { + ProtectedStorageEntry protectedStorageEntry = getProtectedStorageEntryForAdd(); + this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, this.peerNodeAddress, null); + + GetDataResponse getDataResponse = buildGetDataResponse(protectedStorageEntry); + + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + TestState.SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, false, false, false, false); + } + + // TESTCASE: GetDataResponse w/ missing PSE is added with no broadcast or listener signal + // XXXBUGXXX: We signal listeners for all ProtectedStorageEntrys + @Test + public void processGetDataResponse_secondCallNewPSEUpdatesState() throws NoSuchAlgorithmException { + ProtectedStorageEntry protectedStorageEntry = getProtectedStorageEntryForAdd(); + GetDataResponse getDataResponse = buildGetDataResponse(protectedStorageEntry); + + TestState.SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, true, true, false, true); + + protectedStorageEntry = getProtectedStorageEntryForAdd(); + getDataResponse = buildGetDataResponse(protectedStorageEntry); + beforeState = this.testState.saveTestState(protectedStorageEntry); + this.testState.mockedStorage.processGetDataResponse(getDataResponse, this.peerNodeAddress); + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, true, true, false, true); + } +} diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProtectedStorageEntryTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProtectedStorageEntryTest.java index 9f5bd539234..a6d34d93d19 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProtectedStorageEntryTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageProtectedStorageEntryTest.java @@ -83,12 +83,6 @@ abstract public static class ProtectedStorageEntryTestBase { @Parameterized.Parameter(0) public boolean useMessageHandler; - boolean expectIsDataOwner() { - // The onMessage handler variant should always broadcast with isDataOwner == false - // The Client API should always broadcast with isDataOwner == true - return !useMessageHandler; - } - @Parameterized.Parameters(name = "{index}: Test with useMessageHandler={0}") public static Collection data() { List data = new ArrayList<>(); @@ -119,8 +113,7 @@ boolean doRemove(ProtectedStorageEntry entry) { return true; } else { - // XXX: All callers just pass in true, a future patch can remove the argument. - return testState.mockedStorage.remove(entry, TestState.getTestNodeAddress(), true); + return testState.mockedStorage.remove(entry, TestState.getTestNodeAddress()); } } @@ -133,10 +126,8 @@ boolean doAdd(ProtectedStorageEntry protectedStorageEntry) { return true; } else { - // XXX: All external callers just pass in true for isDataOwner and allowBroadcast a future patch can - // remove the argument. return this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, - TestState.getTestNodeAddress(), null, true); + TestState.getTestNodeAddress(), null); } } @@ -149,8 +140,7 @@ boolean doRefreshTTL(RefreshOfferMessage refreshOfferMessage) { return true; } else { - // XXX: All external callers just pass in true for isDataOwner a future patch can remove the argument. - return this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress(), true); + return this.testState.mockedStorage.refreshTTL(refreshOfferMessage, TestState.getTestNodeAddress()); } } @@ -197,7 +187,13 @@ void doProtectedStorageAddAndVerify(ProtectedStorageEntry protectedStorageEntry, if (!this.useMessageHandler) Assert.assertEquals(expectedReturnValue, addResult); - this.testState.verifyProtectedStorageAdd(beforeState, protectedStorageEntry, expectedStateChange, this.expectIsDataOwner()); + if (expectedStateChange) { + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, true, true, true, true); + } else{ + this.testState.verifyProtectedStorageAdd( + beforeState, protectedStorageEntry, false, false, false, false); + } } void doProtectedStorageRemoveAndVerify(ProtectedStorageEntry entry, @@ -214,7 +210,7 @@ void doProtectedStorageRemoveAndVerify(ProtectedStorageEntry entry, if (!this.useMessageHandler) Assert.assertEquals(expectedReturnValue, addResult); - this.testState.verifyProtectedStorageRemove(beforeState, entry, expectedHashMapAndDataStoreUpdated, expectedListenersSignaled, expectedBroadcast, expectedSeqNrWrite, this.expectIsDataOwner()); + this.testState.verifyProtectedStorageRemove(beforeState, entry, expectedHashMapAndDataStoreUpdated, expectedListenersSignaled, expectedBroadcast, expectedSeqNrWrite); } /// Valid Add Tests (isValidForAdd() and matchesRelevantPubKey() return true) @@ -490,7 +486,7 @@ void doRefreshTTLAndVerify(RefreshOfferMessage refreshOfferMessage, boolean expe if (!this.useMessageHandler) Assert.assertEquals(expectedReturnValue, returnValue); - this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, expectStateChange, this.expectIsDataOwner()); + this.testState.verifyRefreshTTL(beforeState, refreshOfferMessage, expectStateChange); } // TESTCASE: Refresh an entry that doesn't exist @@ -686,8 +682,7 @@ boolean doRemove(ProtectedStorageEntry entry) { return true; } else { - // XXX: All external callers just pass in true, a future patch can remove the argument. - return testState.mockedStorage.remove(entry, TestState.getTestNodeAddress(), true); + return testState.mockedStorage.remove(entry, TestState.getTestNodeAddress()); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRemoveExpiredTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRemoveExpiredTest.java index 454799ed515..f6b8c659109 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRemoveExpiredTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRemoveExpiredTest.java @@ -62,12 +62,12 @@ public void removeExpiredEntries_SkipsNonExpirableEntries() throws NoSuchAlgorit KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false); } // TESTCASE: Correctly skips all PersistableNetworkPayloads since they are not expirable @@ -75,7 +75,7 @@ public void removeExpiredEntries_SkipsNonExpirableEntries() throws NoSuchAlgorit public void removeExpiredEntries_skipsPersistableNetworkPayload() { PersistableNetworkPayload persistableNetworkPayload = new PersistableNetworkPayloadStub(true); - Assert.assertTrue(this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload,getTestNodeAddress(), true, true, false, false)); + Assert.assertTrue(this.testState.mockedStorage.addPersistableNetworkPayload(persistableNetworkPayload,getTestNodeAddress(), false)); this.testState.mockedStorage.removeExpiredEntries(); @@ -88,12 +88,12 @@ public void removeExpiredEntries_SkipNonExpiredExpirableEntries() throws CryptoE KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false); } // TESTCASE: Correctly expires non-persistable entries that are expired @@ -102,7 +102,7 @@ public void removeExpiredEntries_ExpiresExpiredExpirableEntries() throws CryptoE KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic(), 0); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); // Increment the clock by an hour which will cause the Payloads to be outside the TTL range this.testState.incrementClock(); @@ -110,7 +110,7 @@ public void removeExpiredEntries_ExpiresExpiredExpirableEntries() throws CryptoE SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, true, true, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, true, true, false, false); } // TESTCASE: Correctly skips persistable entries that are not expired @@ -119,12 +119,12 @@ public void removeExpiredEntries_SkipNonExpiredPersistableExpirableEntries() thr KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new PersistableExpirableProtectedStoragePayloadStub(ownerKeys.getPublic()); ProtectedStorageEntry protectedStorageEntry = this.testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(this.testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, false, false, false, false); } // TESTCASE: Correctly expires persistable entries that are expired @@ -133,7 +133,7 @@ public void removeExpiredEntries_ExpiresExpiredPersistableExpirableEntries() thr KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new PersistableExpirableProtectedStoragePayloadStub(ownerKeys.getPublic(), 0); ProtectedStorageEntry protectedStorageEntry = testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null)); // Increment the clock by an hour which will cause the Payloads to be outside the TTL range this.testState.incrementClock(); @@ -141,7 +141,7 @@ public void removeExpiredEntries_ExpiresExpiredPersistableExpirableEntries() thr SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, true, true, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, true, true, false, false); } // TESTCASE: Ensure we try to purge old entries sequence number map when size exceeds the maximum size @@ -158,14 +158,14 @@ public void removeExpiredEntries_PurgeSeqNrMap() throws CryptoException, NoSuchA ProtectedStorageEntry purgedProtectedStorageEntry = testState.mockedStorage.getProtectedStorageEntry(purgedProtectedStoragePayload, purgedOwnerKeys); expectedRemoves.add(purgedProtectedStorageEntry); - Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(purgedProtectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(purgedProtectedStorageEntry, TestState.getTestNodeAddress(), null)); for (int i = 0; i < MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE - 1; ++i) { KeyPair ownerKeys = TestUtils.generateKeyPair(); ProtectedStoragePayload protectedStoragePayload = new PersistableExpirableProtectedStoragePayloadStub(ownerKeys.getPublic(), 0); ProtectedStorageEntry tmpEntry = testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); expectedRemoves.add(tmpEntry); - Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(tmpEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(tmpEntry, TestState.getTestNodeAddress(), null)); } // Increment the time by 5 days which is less than the purge requirement. This will allow the map to have @@ -178,7 +178,7 @@ public void removeExpiredEntries_PurgeSeqNrMap() throws CryptoException, NoSuchA ProtectedStorageEntry keepProtectedStorageEntry = testState.mockedStorage.getProtectedStorageEntry(keepProtectedStoragePayload, keepOwnerKeys); expectedRemoves.add(keepProtectedStorageEntry); - Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(keepProtectedStorageEntry, TestState.getTestNodeAddress(), null, true)); + Assert.assertTrue(testState.mockedStorage.addProtectedStorageEntry(keepProtectedStorageEntry, TestState.getTestNodeAddress(), null)); // P2PDataStorage::PURGE_AGE_DAYS == 10 days // Advance time past it so they will be valid purge targets @@ -187,6 +187,6 @@ public void removeExpiredEntries_PurgeSeqNrMap() throws CryptoException, NoSuchA // The first 4 entries (11 days old) should be purged from the SequenceNumberMap SavedTestState beforeState = this.testState.saveTestState(purgedProtectedStorageEntry); this.testState.mockedStorage.removeExpiredEntries(); - this.testState.verifyProtectedStorageRemove(beforeState, expectedRemoves, true, true, false, false, false); + this.testState.verifyProtectedStorageRemove(beforeState, expectedRemoves, true, true, false, false); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java new file mode 100644 index 00000000000..27e522daaad --- /dev/null +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java @@ -0,0 +1,171 @@ +/* + * 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.network.p2p.storage; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.TestUtils; +import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; +import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; +import bisq.network.p2p.storage.mocks.PersistableNetworkPayloadStub; +import bisq.network.p2p.storage.mocks.ProtectedStoragePayloadStub; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; +import bisq.network.p2p.storage.payload.ProtectedStorageEntry; +import bisq.network.p2p.storage.payload.ProtectedStoragePayload; + +import bisq.common.app.Capabilities; +import bisq.common.app.Capability; + +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; + +import java.util.Set; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.*; + +public class P2PDataStorageRequestDataTest { + private TestState testState; + + private NodeAddress localNodeAddress; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + this.testState = new TestState(); + + this.localNodeAddress = new NodeAddress("localhost", 8080); + + // Set up basic capabilities to ensure message contains it + Capabilities.app.addAll(Capability.MEDIATION); + } + + /** + * Returns true if the target bytes are found in the container set. + */ + private boolean byteSetContains(Set container, byte[] target) { + // Set.contains() doesn't do a deep compare, so generate a Set so equals() does what + // we want + Set translatedContainer = + P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(container); + + return translatedContainer.contains(new P2PDataStorage.ByteArray(target)); + } + + /** + * Generates a unique ProtectedStorageEntry that is valid for add. This is used to initialize P2PDataStorage state + * so the tests can validate the correct behavior. Adds of identical payloads with different sequence numbers + * is not supported. + */ + private ProtectedStorageEntry getProtectedStorageEntryForAdd() throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + + ProtectedStorageEntry stub = mock(ProtectedStorageEntry.class); + when(stub.getOwnerPubKey()).thenReturn(ownerKeys.getPublic()); + when(stub.isValidForAddOperation()).thenReturn(true); + when(stub.matchesRelevantPubKey(any(ProtectedStorageEntry.class))).thenReturn(true); + when(stub.getSequenceNumber()).thenReturn(1); + when(stub.getProtectedStoragePayload()).thenReturn(protectedStoragePayload); + + return stub; + } + + // TESTCASE: P2PDataStorage with no entries returns an empty PreliminaryGetDataRequest + @Test + public void buildPreliminaryGetDataRequest_EmptyP2PDataStore() { + PreliminaryGetDataRequest getDataRequest = this.testState.mockedStorage.buildPreliminaryGetDataRequest(1); + + Assert.assertEquals(getDataRequest.getNonce(), 1); + Assert.assertEquals(getDataRequest.getSupportedCapabilities(), Capabilities.app); + Assert.assertTrue(getDataRequest.getExcludedKeys().isEmpty()); + } + + // TESTCASE: P2PDataStorage with no entries returns an empty PreliminaryGetDataRequest + @Test + public void buildGetUpdatedDataRequest_EmptyP2PDataStore() { + GetUpdatedDataRequest getDataRequest = + this.testState.mockedStorage.buildGetUpdatedDataRequest(this.localNodeAddress, 1); + + Assert.assertEquals(getDataRequest.getNonce(), 1); + Assert.assertEquals(getDataRequest.getSenderNodeAddress(), this.localNodeAddress); + Assert.assertTrue(getDataRequest.getExcludedKeys().isEmpty()); + } + + // TESTCASE: P2PDataStorage with PersistableNetworkPayloads and ProtectedStorageEntry generates + // correct GetDataRequestMessage with both sets of keys. + @Test + public void buildPreliminaryGetDataRequest_FilledP2PDataStore() throws NoSuchAlgorithmException { + PersistableNetworkPayload toAdd1 = new PersistableNetworkPayloadStub(new byte[] { 1 }); + PersistableNetworkPayload toAdd2 = new PersistableNetworkPayloadStub(new byte[] { 2 }); + ProtectedStorageEntry toAdd3 = getProtectedStorageEntryForAdd(); + ProtectedStorageEntry toAdd4 = getProtectedStorageEntryForAdd(); + + this.testState.mockedStorage.addPersistableNetworkPayload(toAdd1, this.localNodeAddress, false); + this.testState.mockedStorage.addPersistableNetworkPayload(toAdd2, this.localNodeAddress, false); + + this.testState.mockedStorage.addProtectedStorageEntry(toAdd3, this.localNodeAddress, null); + this.testState.mockedStorage.addProtectedStorageEntry(toAdd4, this.localNodeAddress, null); + + PreliminaryGetDataRequest getDataRequest = this.testState.mockedStorage.buildPreliminaryGetDataRequest(1); + + Assert.assertEquals(getDataRequest.getNonce(), 1); + Assert.assertEquals(getDataRequest.getSupportedCapabilities(), Capabilities.app); + Assert.assertEquals(4, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd1.getHash())); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd2.getHash())); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), + P2PDataStorage.get32ByteHash(toAdd3.getProtectedStoragePayload()))); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), + P2PDataStorage.get32ByteHash(toAdd4.getProtectedStoragePayload()))); + } + + // TESTCASE: P2PDataStorage with PersistableNetworkPayloads and ProtectedStorageEntry generates + // correct GetDataRequestMessage with both sets of keys. + @Test + public void requestData_FilledP2PDataStore_GetUpdatedDataRequest() throws NoSuchAlgorithmException { + PersistableNetworkPayload toAdd1 = new PersistableNetworkPayloadStub(new byte[] { 1 }); + PersistableNetworkPayload toAdd2 = new PersistableNetworkPayloadStub(new byte[] { 2 }); + ProtectedStorageEntry toAdd3 = getProtectedStorageEntryForAdd(); + ProtectedStorageEntry toAdd4 = getProtectedStorageEntryForAdd(); + + this.testState.mockedStorage.addPersistableNetworkPayload(toAdd1, this.localNodeAddress, false); + this.testState.mockedStorage.addPersistableNetworkPayload(toAdd2, this.localNodeAddress, false); + + this.testState.mockedStorage.addProtectedStorageEntry(toAdd3, this.localNodeAddress, null); + this.testState.mockedStorage.addProtectedStorageEntry(toAdd4, this.localNodeAddress, null); + + GetUpdatedDataRequest getDataRequest = + this.testState.mockedStorage.buildGetUpdatedDataRequest(this.localNodeAddress, 1); + + Assert.assertEquals(getDataRequest.getNonce(), 1); + Assert.assertEquals(getDataRequest.getSenderNodeAddress(), this.localNodeAddress); + Assert.assertEquals(4, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd1.getHash())); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd2.getHash())); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), + P2PDataStorage.get32ByteHash(toAdd3.getProtectedStoragePayload()))); + Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), + P2PDataStorage.get32ByteHash(toAdd4.getProtectedStoragePayload()))); + } +} diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoreDisconnectTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoreDisconnectTest.java index 1f58b815188..18cd2a21fc9 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoreDisconnectTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStoreDisconnectTest.java @@ -26,11 +26,9 @@ import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.crypto.CryptoException; -import bisq.common.proto.persistable.PersistablePayload; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -57,19 +55,18 @@ private static ProtectedStorageEntry populateTestState(TestState testState, ProtectedStoragePayload protectedStoragePayload = new ExpirableProtectedStoragePayloadStub(ownerKeys.getPublic(), ttl); ProtectedStorageEntry protectedStorageEntry = testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, TestState.getTestNodeAddress(), null, false); + testState.mockedStorage.addProtectedStorageEntry(protectedStorageEntry, getTestNodeAddress(), null); return protectedStorageEntry; } private static void verifyStateAfterDisconnect(TestState currentState, SavedTestState beforeState, - boolean wasRemoved, boolean wasTTLReduced) { ProtectedStorageEntry protectedStorageEntry = beforeState.protectedStorageEntryBeforeOp; currentState.verifyProtectedStorageRemove(beforeState, protectedStorageEntry, - wasRemoved, wasRemoved, false, false, false); + false, false, false, false); if (wasTTLReduced) Assert.assertTrue(protectedStorageEntry.getCreationTimeStamp() < beforeState.creationTimestampBeforeUpdate); @@ -85,39 +82,48 @@ public void setUp() { // TESTCASE: Bad peer info @Test - public void peerConnectionUnknown() { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(false); + public void peerConnectionUnknown() throws CryptoException, NoSuchAlgorithmException { + when(this.mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.empty()); + ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 2); + + SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); + + verifyStateAfterDisconnect(this.testState, beforeState, false); } // TESTCASE: Intended disconnects don't trigger expiration @Test - public void connectionClosedIntended() { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); + public void connectionClosedIntended() throws CryptoException, NoSuchAlgorithmException { + when(this.mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(getTestNodeAddress())); + ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 2); + + SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); + this.testState.mockedStorage.onDisconnect(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER, mockedConnection); + + verifyStateAfterDisconnect(this.testState, beforeState, false); } // TESTCASE: Peer NodeAddress unknown @Test public void connectionClosedSkipsItemsPeerInfoBadState() throws NoSuchAlgorithmException, CryptoException { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); - when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.empty()); + when(this.mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.empty()); - ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 1); + ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 2); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); - verifyStateAfterDisconnect(this.testState, beforeState, false, false); + verifyStateAfterDisconnect(this.testState, beforeState, false); } // TESTCASE: Unintended disconnects reduce the TTL for entrys that match disconnected peer @Test public void connectionClosedReduceTTL() throws NoSuchAlgorithmException, CryptoException { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); - when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(TestState.getTestNodeAddress())); + when(this.mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(getTestNodeAddress())); ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, TimeUnit.DAYS.toMillis(90)); @@ -125,76 +131,20 @@ public void connectionClosedReduceTTL() throws NoSuchAlgorithmException, CryptoE this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); - verifyStateAfterDisconnect(this.testState, beforeState, false, true); + verifyStateAfterDisconnect(this.testState, beforeState, true); } // TESTCASE: Unintended disconnects don't reduce TTL for entrys that are not from disconnected peer @Test public void connectionClosedSkipsItemsNotFromPeer() throws NoSuchAlgorithmException, CryptoException { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); - when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(new NodeAddress("notTestNode", 2020))); + when(this.mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(new NodeAddress("notTestNode", 2020))); - ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 1); + ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 2); SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); - verifyStateAfterDisconnect(this.testState, beforeState, false, false); - } - - // TESTCASE: Unintended disconnects expire entrys that match disconnected peer and TTL is low enough for expire - @Test - public void connectionClosedReduceTTLAndExpireItemsFromPeer() throws NoSuchAlgorithmException, CryptoException { - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); - when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(TestState.getTestNodeAddress())); - - ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 1); - - SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); - - // Increment the time by 1 hour which will put the protectedStorageState outside TTL - this.testState.incrementClock(); - - this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); - - verifyStateAfterDisconnect(this.testState, beforeState, true, false); - } - - // TESTCASE: ProtectedStoragePayloads implementing the PersistablePayload interface are correctly removed - // from the persistent store during the onDisconnect path. - @Test - public void connectionClosedReduceTTLAndExpireItemsFromPeerPersistable() - throws NoSuchAlgorithmException, CryptoException { - - class ExpirablePersistentProtectedStoragePayloadStub - extends ExpirableProtectedStoragePayloadStub implements PersistablePayload { - - private ExpirablePersistentProtectedStoragePayloadStub(PublicKey ownerPubKey) { - super(ownerPubKey, 0); - } - } - - when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true); - when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.of(TestState.getTestNodeAddress())); - - KeyPair ownerKeys = TestUtils.generateKeyPair(); - ProtectedStoragePayload protectedStoragePayload = - new ExpirablePersistentProtectedStoragePayloadStub(ownerKeys.getPublic()); - - ProtectedStorageEntry protectedStorageEntry = - testState.mockedStorage.getProtectedStorageEntry(protectedStoragePayload, ownerKeys); - - testState.mockedStorage.addProtectedStorageEntry( - protectedStorageEntry, TestState.getTestNodeAddress(), null, false); - - SavedTestState beforeState = this.testState.saveTestState(protectedStorageEntry); - - // Increment the time by 1 hour which will put the protectedStorageState outside TTL - this.testState.incrementClock(); - - this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection); - - verifyStateAfterDisconnect(this.testState, beforeState, true, false); + verifyStateAfterDisconnect(this.testState, beforeState, false); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java index c4ceab5ca2a..0ede4a03c8e 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java @@ -171,69 +171,66 @@ private void verifySequenceNumberMapWriteContains(P2PDataStorage.ByteArray paylo void verifyPersistableAdd(SavedTestState beforeState, PersistableNetworkPayload persistableNetworkPayload, - boolean expectedStateChange, - boolean expectedBroadcastAndListenersSignaled, - boolean expectedIsDataOwner) { + boolean expectedHashMapAndDataStoreUpdated, + boolean expectedListenersSignaled, + boolean expectedBroadcast) { P2PDataStorage.ByteArray hash = new P2PDataStorage.ByteArray(persistableNetworkPayload.getHash()); - if (expectedStateChange) { - // Payload is accessible from get() + if (expectedHashMapAndDataStoreUpdated) Assert.assertEquals(persistableNetworkPayload, this.mockedStorage.getAppendOnlyDataStoreMap().get(hash)); - } else { - // On failure, just ensure the state remained the same as before the add - if (beforeState.persistableNetworkPayloadBeforeOp != null) - Assert.assertEquals(beforeState.persistableNetworkPayloadBeforeOp, this.mockedStorage.getAppendOnlyDataStoreMap().get(hash)); - else - Assert.assertNull(this.mockedStorage.getAppendOnlyDataStoreMap().get(hash)); - } - - if (expectedStateChange && expectedBroadcastAndListenersSignaled) { - // Broadcast Called - verify(this.mockBroadcaster).broadcast(any(AddPersistableNetworkPayloadMessage.class), any(NodeAddress.class), - eq(null), eq(expectedIsDataOwner)); + else + Assert.assertEquals(beforeState.persistableNetworkPayloadBeforeOp, this.mockedStorage.getAppendOnlyDataStoreMap().get(hash)); - // Verify the listeners were updated once + if (expectedListenersSignaled) verify(this.appendOnlyDataStoreListener).onAdded(persistableNetworkPayload); - - } else { - verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), any(BroadcastHandler.Listener.class), anyBoolean()); - - // Verify the listeners were never updated + else verify(this.appendOnlyDataStoreListener, never()).onAdded(persistableNetworkPayload); - } + + if (expectedBroadcast) + verify(this.mockBroadcaster).broadcast(any(AddPersistableNetworkPayloadMessage.class), + nullable(NodeAddress.class), isNull()); + else + verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class)); } void verifyProtectedStorageAdd(SavedTestState beforeState, ProtectedStorageEntry protectedStorageEntry, - boolean expectedStateChange, - boolean expectedIsDataOwner) { + boolean expectedHashMapAndDataStoreUpdated, + boolean expectedListenersSignaled, + boolean expectedBroadcast, + boolean expectedSequenceNrMapWrite) { P2PDataStorage.ByteArray hashMapHash = P2PDataStorage.get32ByteHashAsByteArray(protectedStorageEntry.getProtectedStoragePayload()); - if (expectedStateChange) { + if (expectedHashMapAndDataStoreUpdated) { Assert.assertEquals(protectedStorageEntry, this.mockedStorage.getMap().get(hashMapHash)); if (protectedStorageEntry.getProtectedStoragePayload() instanceof PersistablePayload) Assert.assertEquals(protectedStorageEntry, this.protectedDataStoreService.getMap().get(hashMapHash)); + } else { + Assert.assertEquals(beforeState.protectedStorageEntryBeforeOp, this.mockedStorage.getMap().get(hashMapHash)); + Assert.assertEquals(beforeState.protectedStorageEntryBeforeOpDataStoreMap, this.protectedDataStoreService.getMap().get(hashMapHash)); + } + if (expectedListenersSignaled) { verify(this.hashMapChangedListener).onAdded(Collections.singletonList(protectedStorageEntry)); + } else { + verify(this.hashMapChangedListener, never()).onAdded(Collections.singletonList(protectedStorageEntry)); + } + if (expectedBroadcast) { final ArgumentCaptor captor = ArgumentCaptor.forClass(BroadcastMessage.class); - verify(this.mockBroadcaster).broadcast(captor.capture(), any(NodeAddress.class), - eq(null), eq(expectedIsDataOwner)); + verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class), isNull()); BroadcastMessage broadcastMessage = captor.getValue(); Assert.assertTrue(broadcastMessage instanceof AddDataMessage); Assert.assertEquals(protectedStorageEntry, ((AddDataMessage) broadcastMessage).getProtectedStorageEntry()); + } else { + verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class)); + } + if (expectedSequenceNrMapWrite) { this.verifySequenceNumberMapWriteContains(P2PDataStorage.get32ByteHashAsByteArray(protectedStorageEntry.getProtectedStoragePayload()), protectedStorageEntry.getSequenceNumber()); } else { - Assert.assertEquals(beforeState.protectedStorageEntryBeforeOp, this.mockedStorage.getMap().get(hashMapHash)); - Assert.assertEquals(beforeState.protectedStorageEntryBeforeOpDataStoreMap, this.protectedDataStoreService.getMap().get(hashMapHash)); - - verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), any(BroadcastHandler.Listener.class), anyBoolean()); - - // Internal state didn't change... nothing should be notified - verify(this.hashMapChangedListener, never()).onAdded(Collections.singletonList(protectedStorageEntry)); verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); } } @@ -243,12 +240,11 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, boolean expectedHashMapAndDataStoreUpdated, boolean expectedListenersSignaled, boolean expectedBroadcast, - boolean expectedSeqNrWrite, - boolean expectedIsDataOwner) { + boolean expectedSeqNrWrite) { verifyProtectedStorageRemove(beforeState, Collections.singletonList(protectedStorageEntry), expectedHashMapAndDataStoreUpdated, expectedListenersSignaled, expectedBroadcast, - expectedSeqNrWrite, expectedIsDataOwner); + expectedSeqNrWrite); } void verifyProtectedStorageRemove(SavedTestState beforeState, @@ -256,8 +252,7 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, boolean expectedHashMapAndDataStoreUpdated, boolean expectedListenersSignaled, boolean expectedBroadcast, - boolean expectedSeqNrWrite, - boolean expectedIsDataOwner) { + boolean expectedSeqNrWrite) { // The default matcher expects orders to stay the same. So, create a custom matcher function since // we don't care about the order. @@ -280,7 +275,7 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); if (!expectedBroadcast) - verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), any(BroadcastHandler.Listener.class), anyBoolean()); + verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class)); protectedStorageEntries.forEach(protectedStorageEntry -> { @@ -292,9 +287,9 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, if (expectedBroadcast) { if (protectedStorageEntry instanceof ProtectedMailboxStorageEntry) - verify(this.mockBroadcaster).broadcast(any(RemoveMailboxDataMessage.class), any(NodeAddress.class), eq(null), eq(expectedIsDataOwner)); + verify(this.mockBroadcaster).broadcast(any(RemoveMailboxDataMessage.class), nullable(NodeAddress.class), isNull()); else - verify(this.mockBroadcaster).broadcast(any(RemoveDataMessage.class), any(NodeAddress.class), eq(null), eq(expectedIsDataOwner)); + verify(this.mockBroadcaster).broadcast(any(RemoveDataMessage.class), nullable(NodeAddress.class), isNull()); } @@ -312,8 +307,7 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, void verifyRefreshTTL(SavedTestState beforeState, RefreshOfferMessage refreshOfferMessage, - boolean expectedStateChange, - boolean expectedIsDataOwner) { + boolean expectedStateChange) { P2PDataStorage.ByteArray payloadHash = new P2PDataStorage.ByteArray(refreshOfferMessage.getHashOfPayload()); ProtectedStorageEntry entryAfterRefresh = this.mockedStorage.getMap().get(payloadHash); @@ -325,8 +319,7 @@ void verifyRefreshTTL(SavedTestState beforeState, Assert.assertTrue(entryAfterRefresh.getCreationTimeStamp() > beforeState.creationTimestampBeforeUpdate); final ArgumentCaptor captor = ArgumentCaptor.forClass(BroadcastMessage.class); - verify(this.mockBroadcaster).broadcast(captor.capture(), any(NodeAddress.class), - eq(null), eq(expectedIsDataOwner)); + verify(this.mockBroadcaster).broadcast(captor.capture(), nullable(NodeAddress.class), isNull()); BroadcastMessage broadcastMessage = captor.getValue(); Assert.assertTrue(broadcastMessage instanceof RefreshOfferMessage); @@ -343,7 +336,7 @@ void verifyRefreshTTL(SavedTestState beforeState, Assert.assertEquals(beforeState.creationTimestampBeforeUpdate, entryAfterRefresh.getCreationTimeStamp()); } - verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), any(NodeAddress.class), any(BroadcastHandler.Listener.class), anyBoolean()); + verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class), nullable(BroadcastHandler.Listener.class)); verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/mocks/PersistableNetworkPayloadStub.java b/p2p/src/test/java/bisq/network/p2p/storage/mocks/PersistableNetworkPayloadStub.java index 6ab73155480..057cb2c42b8 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/mocks/PersistableNetworkPayloadStub.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/mocks/PersistableNetworkPayloadStub.java @@ -28,9 +28,19 @@ */ public class PersistableNetworkPayloadStub implements PersistableNetworkPayload { private final boolean hashSizeValid; + private final byte[] hash; public PersistableNetworkPayloadStub(boolean hashSizeValid) { + this(hashSizeValid, new byte[] { 1 }); + } + + public PersistableNetworkPayloadStub(byte[] hash) { + this(true, hash); + } + + private PersistableNetworkPayloadStub(boolean hashSizeValid, byte[] hash) { this.hashSizeValid = hashSizeValid; + this.hash = hash; } @Override @@ -40,7 +50,7 @@ public protobuf.PersistableNetworkPayload toProtoMessage() { @Override public byte[] getHash() { - return new byte[] { 1 }; + return hash; } @Override diff --git a/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntryTest.java b/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntryTest.java index 74d466aa148..035e9e03d57 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntryTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedMailboxStorageEntryTest.java @@ -107,14 +107,13 @@ public void isValidForAddOperation_EntryReceiverPayloadReceiverMismatch() throws // TESTCASE: validForAddOperation() should fail if the signature isn't valid @Test - public void isValidForAddOperation_BadSignature() throws NoSuchAlgorithmException, CryptoException { + public void isValidForAddOperation_BadSignature() throws NoSuchAlgorithmException { KeyPair senderKeys = TestUtils.generateKeyPair(); KeyPair receiverKeys = TestUtils.generateKeyPair(); MailboxStoragePayload mailboxStoragePayload = buildMailboxStoragePayload(senderKeys.getPublic(), receiverKeys.getPublic()); - ProtectedStorageEntry protectedStorageEntry = buildProtectedMailboxStorageEntry(mailboxStoragePayload, senderKeys, receiverKeys.getPublic(), 1); - - protectedStorageEntry.updateSignature( new byte[] { 0 }); + ProtectedStorageEntry protectedStorageEntry = new ProtectedMailboxStorageEntry( + mailboxStoragePayload, senderKeys.getPublic(), 1, new byte[] { 0 }, receiverKeys.getPublic(), Clock.systemDefaultZone()); Assert.assertFalse(protectedStorageEntry.isValidForAddOperation()); } @@ -145,14 +144,14 @@ public void validForRemoveEntryOwnerPayloadOwnerMismatch() throws NoSuchAlgorith // TESTCASE: isValidForRemoveOperation() should fail if the signature is bad @Test - public void isValidForRemoveOperation_BadSignature() throws NoSuchAlgorithmException, CryptoException { + public void isValidForRemoveOperation_BadSignature() throws NoSuchAlgorithmException { KeyPair senderKeys = TestUtils.generateKeyPair(); KeyPair receiverKeys = TestUtils.generateKeyPair(); MailboxStoragePayload mailboxStoragePayload = buildMailboxStoragePayload(senderKeys.getPublic(), receiverKeys.getPublic()); - ProtectedStorageEntry protectedStorageEntry = buildProtectedMailboxStorageEntry(mailboxStoragePayload, receiverKeys, receiverKeys.getPublic(), 1); - - protectedStorageEntry.updateSignature(new byte[] { 0 }); + ProtectedStorageEntry protectedStorageEntry = + new ProtectedMailboxStorageEntry(mailboxStoragePayload, receiverKeys.getPublic(), + 1, new byte[] { 0 }, receiverKeys.getPublic(), Clock.systemDefaultZone()); Assert.assertFalse(protectedStorageEntry.isValidForRemoveOperation()); } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedStorageEntryTest.java b/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedStorageEntryTest.java index 1b5313977e7..a624095ba49 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedStorageEntryTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/payload/ProtectedStorageEntryTest.java @@ -31,6 +31,7 @@ import java.security.PublicKey; import java.time.Clock; +import java.time.Duration; import org.junit.Assert; import org.junit.Before; @@ -125,11 +126,13 @@ public void isValidForAddOperation_invalidMailboxPayloadReceiver() throws NoSuch // TESTCASE: validForAddOperation() should fail if the signature isn't valid @Test - public void isValidForAddOperation_BadSignature() throws NoSuchAlgorithmException, CryptoException { + public void isValidForAddOperation_BadSignature() throws NoSuchAlgorithmException { KeyPair ownerKeys = TestUtils.generateKeyPair(); - ProtectedStorageEntry protectedStorageEntry = buildProtectedStorageEntry(ownerKeys, ownerKeys, 1); - protectedStorageEntry.updateSignature( new byte[] { 0 }); + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + ProtectedStorageEntry protectedStorageEntry = + new ProtectedStorageEntry(protectedStoragePayload, ownerKeys.getPublic(), + 1, new byte[] { 0 }, Clock.systemDefaultZone()); Assert.assertFalse(protectedStorageEntry.isValidForAddOperation()); } @@ -181,11 +184,13 @@ public void isValidForRemoveOperation_invalidMailboxPayloadReceiver() throws NoS // TESTCASE: isValidForRemoveOperation() should fail if the signature is bad @Test - public void isValidForRemoveOperation_BadSignature() throws NoSuchAlgorithmException, CryptoException { + public void isValidForRemoveOperation_BadSignature() throws NoSuchAlgorithmException { KeyPair ownerKeys = TestUtils.generateKeyPair(); - ProtectedStorageEntry protectedStorageEntry = buildProtectedStorageEntry(ownerKeys, ownerKeys, 1); - protectedStorageEntry.updateSignature(new byte[] { 0 }); + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + ProtectedStorageEntry protectedStorageEntry = + new ProtectedStorageEntry(protectedStoragePayload, ownerKeys.getPublic(), + 1, new byte[] { 0 }, Clock.systemDefaultZone()); Assert.assertFalse(protectedStorageEntry.isValidForRemoveOperation()); } @@ -249,4 +254,20 @@ public protobuf.PersistableNetworkPayload toProtoMessage() { new ProtectedStorageEntry(incompatiblePayload,ownerKeys.getPublic(), 1, new byte[] { 0 }, Clock.systemDefaultZone()); } + + // TESTCASE: PSEs received with future-dated timestamps are updated to be min(currentTime, creationTimeStamp) + @Test + public void futureTimestampIsSanitized() throws NoSuchAlgorithmException { + KeyPair ownerKeys = TestUtils.generateKeyPair(); + + Clock baseClock = Clock.systemDefaultZone(); + Clock futureClock = Clock.offset(baseClock, Duration.ofDays(1)); + + ProtectedStoragePayload protectedStoragePayload = new ProtectedStoragePayloadStub(ownerKeys.getPublic()); + ProtectedStorageEntry protectedStorageEntry = + new ProtectedStorageEntry(protectedStoragePayload, Sig.getPublicKeyBytes(ownerKeys.getPublic()), + ownerKeys.getPublic(), 1, new byte[] { 0 }, futureClock.millis(), baseClock); + + Assert.assertTrue(protectedStorageEntry.getCreationTimeStamp() <= baseClock.millis()); + } }