Merge pull request #3747 from julianknutsen/clean-up-pse-objs

(6/6) Clean up technical debt in P2PDataStorage and ProtectedStorageEntry objects
This commit is contained in:
Christoph Atteneder 2019-12-09 20:34:26 +01:00 committed by GitHub
commit 3fe84975ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1734 additions and 652 deletions

View File

@ -20,7 +20,7 @@ package bisq.core.account.sign;
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 @@ import lombok.extern.slf4j.Slf4j;
// 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 {

View File

@ -19,7 +19,7 @@ package bisq.core.account.witness;
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 @@ import lombok.extern.slf4j.Slf4j;
// 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

View File

@ -127,7 +127,7 @@ public class AlertManager {
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 class AlertManager {
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);

View File

@ -163,7 +163,7 @@ public class MyProposalListService implements PersistedDataHost, DaoStateListene
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 @@ public class MyProposalListService implements PersistedDataHost, DaoStateListene
}
private boolean addToP2PNetworkAsProtectedData(Proposal proposal) {
return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey), true);
return p2PService.addProtectedStorageEntry(new TempProposalPayload(proposal, signaturePubKey));
}
private void rePublishMyProposalsOnceWellConnected() {

View File

@ -20,7 +20,7 @@ package bisq.core.dao.governance.proposal.storage.temp;
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 @@ import javax.annotation.concurrent.Immutable;
@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;

View File

@ -156,7 +156,7 @@ public abstract class StateNetworkService<Msg extends NewStateHashMessage,
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) {

View File

@ -105,7 +105,7 @@ public class FullNodeNetworkService implements MessageListener, PeerManager.List
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);
}

View File

@ -238,7 +238,7 @@ public class LiteNodeNetworkService implements MessageListener, ConnectionListen
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);

View File

@ -265,7 +265,7 @@ public class FilterManager {
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 class FilterManager {
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 {

View File

@ -148,7 +148,7 @@ public class OfferBookService {
return;
}
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload(), true);
boolean result = p2PService.addProtectedStorageEntry(offer.getOfferPayload());
if (result) {
resultHandler.handleResult();
} else {
@ -164,7 +164,7 @@ public class OfferBookService {
return;
}
boolean result = p2PService.refreshTTL(offerPayload, true);
boolean result = p2PService.refreshTTL(offerPayload);
if (result) {
resultHandler.handleResult();
} else {
@ -187,7 +187,7 @@ public class OfferBookService {
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 {

View File

@ -64,7 +64,7 @@ public abstract class DisputeAgentService<T extends 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 abstract class DisputeAgentService<T extends 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 {

View File

@ -24,7 +24,7 @@ import bisq.core.monetary.Volume;
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 @@ import javax.annotation.Nullable;
@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;

View File

@ -25,7 +25,7 @@ import bisq.core.offer.OfferPayload;
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 @@ import static com.google.common.base.Preconditions.checkNotNull;
@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

View File

@ -221,20 +221,15 @@ public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
versions.log(protectedStoragePayload);
});
Set<PersistableNetworkPayload> 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);

View File

@ -333,20 +333,15 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
result.log(protectedStoragePayload);
});
Set<PersistableNetworkPayload> 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;

View File

@ -699,7 +699,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
// 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 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
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 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
///////////////////////////////////////////////////////////////////////////////////////////
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 class P2PService implements SetupListener, MessageListener, ConnectionLis
}
}
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 class P2PService implements SetupListener, MessageListener, ConnectionLis
}
}
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;

View File

@ -111,7 +111,7 @@ public class BroadcastHandler implements PeerManager.Listener {
///////////////////////////////////////////////////////////////////////////////////////////
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 class BroadcastHandler implements PeerManager.Listener {
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());

View File

@ -56,9 +56,9 @@ public class Broadcaster implements BroadcastHandler.ResultHandler {
///////////////////////////////////////////////////////////////////////////////////////////
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);
}

View File

@ -22,27 +22,17 @@ import bisq.network.p2p.network.Connection;
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 @@ import org.jetbrains.annotations.NotNull;
@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 class GetDataRequestHandler {
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 class GetDataRequestHandler {
log.info("handle GetDataRequest took {} ms", System.currentTimeMillis() - ts);
}
private Set<PersistableNetworkPayload> getFilteredPersistableNetworkPayload(GetDataRequest getDataRequest,
Connection connection) {
Set<P2PDataStorage.ByteArray> tempLookupSet = new HashSet<>();
String connectionInfo = "connectionInfo" + connection.getPeersNodeAddressOptional()
.map(e -> "node address " + e.getFullAddress())
.orElseGet(() -> "connection UID " + connection.getUid());
Set<P2PDataStorage.ByteArray> excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys());
AtomicInteger maxSize = new AtomicInteger(MAX_ENTRIES);
Set<PersistableNetworkPayload> 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<ProtectedStorageEntry> getFilteredProtectedStorageEntries(GetDataRequest getDataRequest,
Connection connection) {
Set<ProtectedStorageEntry> filteredDataSet = new HashSet<>();
Set<Integer> 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<P2PDataStorage.ByteArray> excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys());
Set<ProtectedStorageEntry> 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();
}

View File

@ -25,10 +25,7 @@ import bisq.network.p2p.network.NetworkNode;
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.HashSet;
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 @@ import org.jetbrains.annotations.Nullable;
@Slf4j
class RequestDataHandler implements MessageListener {
private static final long TIMEOUT = 90;
private static boolean initialRequestApplied = false;
private NodeAddress peersNodeAddress;
/*
@ -124,25 +118,10 @@ class RequestDataHandler implements MessageListener {
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<byte[]> excludedKeys = dataStorage.getAppendOnlyDataStoreMap().keySet().stream()
.map(e -> e.bytes)
.collect(Collectors.toSet());
Set<byte[]> 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 @@ class RequestDataHandler implements MessageListener {
GetDataResponse getDataResponse = (GetDataResponse) networkEnvelope;
final Set<ProtectedStorageEntry> dataSet = getDataResponse.getDataSet();
Set<PersistableNetworkPayload> persistableNetworkPayloadSet = getDataResponse.getPersistableNetworkPayloadSet();
logContents(networkEnvelope, dataSet, persistableNetworkPayloadSet);
if (getDataResponse.getRequestNonce() == nonce) {
@ -218,50 +196,8 @@ class RequestDataHandler implements MessageListener {
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 @@ class RequestDataHandler implements MessageListener {
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(": ")

View File

@ -29,7 +29,6 @@ import bisq.common.proto.network.NetworkEnvelope;
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.EqualsAndHashCode;
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<ProtectedStorageEntry> 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<PersistableNetworkPayload> persistableNetworkPayloadSet;
private final int requestNonce;
private final boolean isGetUpdatedDataResponse;
@Nullable
private final Capabilities supportedCapabilities;
public GetDataResponse(Set<ProtectedStorageEntry> dataSet,
@Nullable Set<PersistableNetworkPayload> persistableNetworkPayloadSet,
public GetDataResponse(@NotNull Set<ProtectedStorageEntry> dataSet,
@NotNull Set<PersistableNetworkPayload> persistableNetworkPayloadSet,
int requestNonce,
boolean isGetUpdatedDataResponse) {
this(dataSet,
@ -72,11 +69,11 @@ public final class GetDataResponse extends NetworkEnvelope implements SupportedC
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private GetDataResponse(Set<ProtectedStorageEntry> dataSet,
@Nullable Set<PersistableNetworkPayload> persistableNetworkPayloadSet,
private GetDataResponse(@NotNull Set<ProtectedStorageEntry> dataSet,
@NotNull Set<PersistableNetworkPayload> persistableNetworkPayloadSet,
int requestNonce,
boolean isGetUpdatedDataResponse,
@Nullable Capabilities supportedCapabilities,
@NotNull Capabilities supportedCapabilities,
int messageVersion) {
super(messageVersion);
@ -100,13 +97,12 @@ public final class GetDataResponse extends NetworkEnvelope implements SupportedC
.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 final class GetDataResponse extends NetworkEnvelope implements SupportedC
.map(entry -> (ProtectedStorageEntry) resolver.fromProto(entry))
.collect(Collectors.toSet()));
Set<PersistableNetworkPayload> persistableNetworkPayloadSet = proto.getPersistableNetworkPayloadItemsList().isEmpty() ?
null :
new HashSet<>(
proto.getPersistableNetworkPayloadItemsList().stream()
Set<PersistableNetworkPayload> persistableNetworkPayloadSet = new HashSet<>(
proto.getPersistableNetworkPayloadItemsList().stream()
.map(e -> (PersistableNetworkPayload) resolver.fromProto(e))
.collect(Collectors.toSet()));
//PersistableNetworkPayload
return new GetDataResponse(dataSet,
persistableNetworkPayloadSet,
proto.getRequestNonce(),

View File

@ -26,7 +26,6 @@ import bisq.common.proto.ProtoUtil;
import com.google.protobuf.ByteString;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -34,9 +33,7 @@ import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import protobuf.NetworkEnvelope;
@ -44,11 +41,10 @@ import protobuf.NetworkEnvelope;
@EqualsAndHashCode(callSuper = true)
@Value
public final class PreliminaryGetDataRequest extends GetDataRequest implements AnonymousMessage, SupportedCapabilitiesMessage {
@Nullable
private final Capabilities supportedCapabilities;
public PreliminaryGetDataRequest(int nonce,
Set<byte[]> excludedKeys) {
@NotNull Set<byte[]> excludedKeys) {
this(nonce, excludedKeys, Capabilities.app, Version.getP2PMessageVersion());
}
@ -58,8 +54,8 @@ public final class PreliminaryGetDataRequest extends GetDataRequest implements A
///////////////////////////////////////////////////////////////////////////////////////////
private PreliminaryGetDataRequest(int nonce,
Set<byte[]> excludedKeys,
@Nullable Capabilities supportedCapabilities,
@NotNull Set<byte[]> excludedKeys,
@NotNull Capabilities supportedCapabilities,
int messageVersion) {
super(messageVersion, nonce, excludedKeys);
@ -69,13 +65,12 @@ public final class PreliminaryGetDataRequest extends GetDataRequest implements A
@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 final class PreliminaryGetDataRequest extends GetDataRequest implements A
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);
}
}

View File

@ -25,6 +25,10 @@ import bisq.network.p2p.network.MessageListener;
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.BroadcastMessage;
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.network.p2p.storage.persistence.SequenceNumberMap;
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.Set;
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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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<byte[]> 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<byte[]> excludedKeys =this.appendOnlyDataStoreService.getMap().keySet().stream()
.map(e -> e.bytes)
.collect(Collectors.toSet());
Set<byte[]> 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<ByteArray, ProtectedStorageEntry || PersistableNetworkPayload>
* by a given set of keys and peer capabilities.
*/
static private <T extends NetworkPayload> Set<T> filterKnownHashes(
Map<ByteArray, T> toFilter,
Function<T, ? extends NetworkPayload> objToPayload,
Set<ByteArray> knownHashes,
Capabilities peerCapabilities,
int maxEntries,
AtomicBoolean outTruncated) {
AtomicInteger limit = new AtomicInteger(maxEntries);
Set<T> 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<P2PDataStorage.ByteArray> excludedKeysAsByteArray =
P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys());
Set<PersistableNetworkPayload> filteredPersistableNetworkPayloads =
filterKnownHashes(
this.appendOnlyDataStoreService.getMap(),
Function.identity(),
excludedKeysAsByteArray,
peerCapabilities,
maxEntriesPerType,
outPersistableNetworkPayloadOutputTruncated);
Set<ProtectedStorageEntry> 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<ProtectedStorageEntry> dataSet = getDataResponse.getDataSet();
Set<PersistableNetworkPayload> 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
@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.
if (closeConnectionReason.isIntended)
return;
// 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);
if (!connection.getPeersNodeAddressOptional().isPresent())
return;
// 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.");
}
}
}
});
}
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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
///////////////////////////////////////////////////////////////////////////////////////////
// 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 <code>true</code> if the PersistableNetworkPayload should be rebroadcast even if it
* already exists locally
* @return <code>true</code> 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
// 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
// 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 <code>true</code> 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
// 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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 <code>true</code> 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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 <code>true</code> 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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
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 class P2PDataStorage implements MessageListener, ConnectionListener, Pers
// 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 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
}
}
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));
}

View File

@ -37,6 +37,8 @@ import lombok.EqualsAndHashCode;
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<String, String> extraDataMap;
public MailboxStoragePayload(PrefixedSealedAndSignedMessage prefixedSealedAndSignedMessage,
PublicKey senderPubKeyForAddOperation,
@NotNull PublicKey senderPubKeyForAddOperation,
PublicKey ownerPubKey) {
this.prefixedSealedAndSignedMessage = prefixedSealedAndSignedMessage;
this.senderPubKeyForAddOperation = senderPubKeyForAddOperation;

View File

@ -20,8 +20,9 @@ package bisq.network.p2p.storage.payload;
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 {
}

View File

@ -101,13 +101,12 @@ public class ProtectedMailboxStorageEntry extends ProtectedStorageEntry {
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 class ProtectedMailboxStorageEntry extends ProtectedStorageEntry {
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. " +

View File

@ -39,6 +39,8 @@ import lombok.EqualsAndHashCode;
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 class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
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 @@ public class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
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 class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
// 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 class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
// 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 class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
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 class ProtectedStorageEntry implements NetworkPayload, PersistablePayload
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;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<byte[]> 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<byte[]> knownKeys) {
return new PreliminaryGetDataRequest(nonce, knownKeys);
}
}
public static class P2PDataStorageBuildGetDataResponseTestUpdated extends P2PDataStorageBuildGetDataResponseTestBase {
@Override
GetDataRequest buildGetDataRequest(int nonce, Set<byte[]> knownKeys) {
return new GetUpdatedDataRequest(new NodeAddress("peer", 10), nonce, knownKeys);
}
}
}

View File

@ -71,9 +71,9 @@ public class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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 class P2PDataStorageClientAPITest {
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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -69,7 +69,7 @@ public class P2PDataStorageOnMessageHandlerTest {
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 class P2PDataStorageOnMessageHandlerTest {
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 class P2PDataStorageOnMessageHandlerTest {
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));
}
}

View File

@ -49,11 +49,10 @@ import static bisq.network.p2p.storage.TestState.*;
* 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 class P2PDataStoragePersistableNetworkPayloadTest {
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 class P2PDataStoragePersistableNetworkPayloadTest {
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 @@ public class P2PDataStoragePersistableNetworkPayloadTest {
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 class P2PDataStoragePersistableNetworkPayloadTest {
public static Collection<Object[]> data() {
List<Object[]> 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 class P2PDataStoragePersistableNetworkPayloadTest {
@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 @@ public class P2PDataStoragePersistableNetworkPayloadTest {
public void invalidHash() {
PersistableNetworkPayload persistableNetworkPayload = new PersistableNetworkPayloadStub(false);
doAddAndVerify(persistableNetworkPayload, false, false);
doAddAndVerify(persistableNetworkPayload, false, false, false, false);
}
}
@ -190,7 +171,7 @@ public class P2PDataStoragePersistableNetworkPayloadTest {
// The onMessage path checks for tolerance
boolean expectedReturn = this.testCase != TestCase.ON_MESSAGE;
doAddAndVerify(persistableNetworkPayload, expectedReturn, expectedReturn);
doAddAndVerify(persistableNetworkPayload, expectedReturn, expectedReturn, expectedReturn, expectedReturn);
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedStorageEntry> protectedStorageEntries,
List<PersistableNetworkPayload> 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);
}
}

View File

@ -83,12 +83,6 @@ public class P2PDataStorageProtectedStorageEntryTest {
@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<Object[]> data() {
List<Object[]> data = new ArrayList<>();
@ -119,8 +113,7 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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 @@ public class P2PDataStorageProtectedStorageEntryTest {
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());
}
}

View File

@ -62,12 +62,12 @@ public class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
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 class P2PDataStorageRemoveExpiredTest {
// 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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<byte[]> container, byte[] target) {
// Set<byte[]>.contains() doesn't do a deep compare, so generate a Set<ByteArray> so equals() does what
// we want
Set<P2PDataStorage.ByteArray> 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())));
}
}

View File

@ -26,11 +26,9 @@ import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
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 @@ public class P2PDataStoreDisconnectTest {
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 class P2PDataStoreDisconnectTest {
// TESTCASE: Bad peer info
@Test
public void peerConnectionUnknown() {
when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(false);
this.testState.mockedStorage.onDisconnect(CloseConnectionReason.SOCKET_CLOSED, mockedConnection);
}
// TESTCASE: Intended disconnects don't trigger expiration
@Test
public void connectionClosedIntended() {
when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true);
this.testState.mockedStorage.onDisconnect(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER, mockedConnection);
}
// TESTCASE: Peer NodeAddress unknown
@Test
public void connectionClosedSkipsItemsPeerInfoBadState() throws NoSuchAlgorithmException, CryptoException {
when(this.mockedConnection.hasPeersNodeAddress()).thenReturn(true);
when(mockedConnection.getPeersNodeAddressOptional()).thenReturn(Optional.empty());
ProtectedStorageEntry protectedStorageEntry = populateTestState(testState, 1);
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, false);
verifyStateAfterDisconnect(this.testState, beforeState, false);
}
// TESTCASE: Intended disconnects don't trigger expiration
@Test
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.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: 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 class P2PDataStoreDisconnectTest {
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);
}
}

View File

@ -171,69 +171,66 @@ public class TestState {
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));
}
else
Assert.assertEquals(beforeState.persistableNetworkPayloadBeforeOp, this.mockedStorage.getAppendOnlyDataStoreMap().get(hash));
if (expectedStateChange && expectedBroadcastAndListenersSignaled) {
// Broadcast Called
verify(this.mockBroadcaster).broadcast(any(AddPersistableNetworkPayloadMessage.class), any(NodeAddress.class),
eq(null), eq(expectedIsDataOwner));
// 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<BroadcastMessage> 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 @@ public class TestState {
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 @@ public class TestState {
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 @@ public class TestState {
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 @@ public class TestState {
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 @@ public class TestState {
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 @@ public class TestState {
Assert.assertTrue(entryAfterRefresh.getCreationTimeStamp() > beforeState.creationTimestampBeforeUpdate);
final ArgumentCaptor<BroadcastMessage> 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 @@ public class TestState {
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());
}
}

View File

@ -28,9 +28,19 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
*/
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 class PersistableNetworkPayloadStub implements PersistableNetworkPayload
@Override
public byte[] getHash() {
return new byte[] { 1 };
return hash;
}
@Override

View File

@ -107,14 +107,13 @@ public class ProtectedMailboxStorageEntryTest {
// 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 class ProtectedMailboxStorageEntryTest {
// 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());
}

View File

@ -31,6 +31,7 @@ import java.security.NoSuchAlgorithmException;
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 class ProtectedStorageEntryTest {
// 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 class ProtectedStorageEntryTest {
// 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 class ProtectedStorageEntryTest {
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());
}
}