Skip to content

Commit

Permalink
Merge pull request #263 from QuorumEngineering/feature/diff-for-new-r…
Browse files Browse the repository at this point in the history
…ecipients

Perform diff of PartyInfo
  • Loading branch information
prd-fox authored Jul 17, 2018
2 parents d2a616a + cb1f766 commit d881f33
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 168 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ script:
- mvn install

before_cache:
- mvn dependency:purge-local-repository -Dinclude=com.github.tessera -DresolutionFuzziness=groupId -Dverbose=true
- mvn dependency:purge-local-repository -Dinclude=com.github.tessera -DresolutionFuzziness=groupId -Dverbose=true -DreResolve=false

after_success:
- bash <(curl -s https://codecov.io/bash)
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.github.tessera.nacl.Key;
import com.github.tessera.node.model.PartyInfo;
import com.github.tessera.node.model.Recipient;

import java.util.Set;

public interface PartyInfoService {

/**
* Request PartyInfo data from all remote nodes that this node is aware of.
* Request PartyInfo data from all remote nodes that this node is aware of
*
* @return PartyInfo object
*/
PartyInfo getPartyInfo();
Expand All @@ -15,10 +19,26 @@ public interface PartyInfoService {
* Update the PartyInfo data store with the provided encoded data.
* This can happen when endpoint /partyinfo is triggered,
* or by a response from this node hitting another node /partyinfo endpoint
*
* @return updated PartyInfo object
*/
PartyInfo updatePartyInfo(PartyInfo partyInfo);

/**
* Retrieves the URL that the node is located at for the given public key
*
* @param key the public key to search for
* @return the url the key's node is located at
*/
String getURLFromRecipientKey(Key key);

/**
* Searches the provided {@link PartyInfo} for recipients that haven't yet been saved to
* the list of all known hosts
*
* @param partyInfoWithUnsavedRecipients received party info that should be diffed
* @return the list of all unknown recipients
*/
Set<Recipient> findUnsavedRecipients(PartyInfo partyInfoWithUnsavedRecipients);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import com.github.tessera.node.model.PartyInfo;
import com.github.tessera.node.model.Recipient;

import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toSet;

public class PartyInfoServiceImpl implements PartyInfoService {

Expand All @@ -20,22 +23,22 @@ public class PartyInfoServiceImpl implements PartyInfoService {
public PartyInfoServiceImpl(final PartyInfoStore partyInfoStore,
final Config configuration,
final KeyManager keyManager) {

this.partyInfoStore = Objects.requireNonNull(partyInfoStore);

final String advertisedUrl = configuration.getServerConfig().getServerUri().toString();

final Set<Party> initialParties = configuration.getPeers()
final Set<Party> initialParties = configuration
.getPeers()
.stream()
.map(Peer::getUrl)
.map(Party::new)
.collect(Collectors.toSet());
.collect(toSet());

final Set<Recipient> ourKeys = keyManager
.getPublicKeys()
.stream()
.map(key -> new Recipient(key, advertisedUrl))
.collect(Collectors.toSet());
.collect(toSet());

partyInfoStore.store(new PartyInfo(advertisedUrl, ourKeys, initialParties));
}
Expand All @@ -50,7 +53,7 @@ public PartyInfo updatePartyInfo(final PartyInfo partyInfo) {

partyInfoStore.store(partyInfo);

return partyInfoStore.getPartyInfo();
return this.getPartyInfo();
}

@Override
Expand All @@ -67,4 +70,14 @@ public String getURLFromRecipientKey(final Key key) {
return retrievedRecipientFromStore.getUrl();
}

@Override
public Set<Recipient> findUnsavedRecipients(final PartyInfo partyInfoWithUnsavedRecipients) {
final Set<Recipient> knownHosts = this.getPartyInfo().getRecipients();

final Set<Recipient> incomingRecipients = new HashSet<>(partyInfoWithUnsavedRecipients.getRecipients());
incomingRecipients.removeAll(knownHosts);

return Collections.unmodifiableSet(incomingRecipients);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.github.tessera.config.Peer;
import com.github.tessera.config.ServerConfig;
import com.github.tessera.key.KeyManager;
import com.github.tessera.key.exception.KeyNotFoundException;
import com.github.tessera.nacl.Key;
import com.github.tessera.node.model.Party;
import com.github.tessera.node.model.PartyInfo;
Expand All @@ -17,27 +18,39 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.Collections.*;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.Mockito.*;

public class PartyInfoServiceTest {

private static final String uri = "http://localhost:8080";
private static final String URI = "http://localhost:8080";

private static final Set<Recipient> NEW_RECIPIENTS = Stream
.of(
new Recipient(new Key("url1".getBytes()), "url1"),
new Recipient(new Key("url2".getBytes()), "url2")
).collect(toSet());

private PartyInfoStore partyInfoStore;

private Config configuration;

private KeyManager keyManager;

private PartyInfoService partyInfoService;

@Before
public void onSetUp() {

this.partyInfoStore = mock(PartyInfoStore.class);
final Config configuration = mock(Config.class);
final KeyManager keyManager = mock(KeyManager.class);
this.configuration = mock(Config.class);
this.keyManager = mock(KeyManager.class);

final ServerConfig serverConfig = new ServerConfig("http://localhost", 8080, null);
doReturn(serverConfig).when(configuration).getServerConfig();
Expand All @@ -52,8 +65,6 @@ public void onSetUp() {
)
);
doReturn(ourKeys).when(keyManager).getPublicKeys();

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);
}

@After
Expand All @@ -63,8 +74,9 @@ public void after() {

@Test
public void initialPartiesCorrectlyReadFromConfiguration() {
this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final PartyInfo partyInfo = new PartyInfo(uri, emptySet(), singleton(new Party("http://other-node.com:8080")));
final PartyInfo partyInfo = new PartyInfo(URI, emptySet(), singleton(new Party("http://other-node.com:8080")));
doReturn(partyInfo).when(partyInfoStore).getPartyInfo();

final Set<Party> initialParties = partyInfoService.getPartyInfo().getParties();
Expand All @@ -73,7 +85,7 @@ public void initialPartiesCorrectlyReadFromConfiguration() {

assertThat(initialParties).hasSize(1).containsExactly(new Party("http://other-node.com:8080"));
assertThat(initialRecipients).hasSize(0);
assertThat(ourUrl).isEqualTo(uri);
assertThat(ourUrl).isEqualTo(URI);

verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore, times(3)).getPartyInfo();
Expand All @@ -84,6 +96,8 @@ public void initialPartiesCorrectlyReadFromConfiguration() {
@Test
public void registeringPublicKeysUsesOurUrl() {

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final ArgumentCaptor<PartyInfo> captor = ArgumentCaptor.forClass(PartyInfo.class);

verify(partyInfoStore).store(captor.capture());
Expand All @@ -98,14 +112,16 @@ public void registeringPublicKeysUsesOurUrl() {
assertThat(allRegisteredKeys)
.hasSize(2)
.containsExactlyInAnyOrder(
new Recipient(new Key("some-key".getBytes()), uri),
new Recipient(new Key("another-public-key".getBytes()), uri)
new Recipient(new Key("some-key".getBytes()), URI),
new Recipient(new Key("another-public-key".getBytes()), URI)
);
}

@Test
public void updatePartyInfoDelegatesToStore() {

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final String secondParty = "http://other-node.com:8080";
final String thirdParty = "http://third-url.com:8080";

Expand All @@ -124,21 +140,90 @@ public void updatePartyInfoDelegatesToStore() {
@Test
public void getRecipientURLFromPartyInfoStore() {

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final Recipient recipient = new Recipient(new Key("key".getBytes()), "someurl");
final PartyInfo partyInfo = new PartyInfo(uri, singleton(recipient), emptySet());
final PartyInfo partyInfo = new PartyInfo(URI, singleton(recipient), emptySet());
doReturn(partyInfo).when(partyInfoStore).getPartyInfo();

final String result = partyInfoService.getURLFromRecipientKey(new Key("key".getBytes()));
assertThat(result).isEqualTo("someurl");

verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore).getPartyInfo();
}

@Test
public void getRecipientURLFromPartyInfoStoreFailsIfKeyDoesntExist() {

doReturn(new PartyInfo("", emptySet(), emptySet())).when(partyInfoStore).getPartyInfo();

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final Key failingKey = new Key("otherKey".getBytes());
final Throwable throwable = catchThrowable(() -> partyInfoService.getURLFromRecipientKey(failingKey));
assertThat(throwable).isInstanceOf(RuntimeException.class);
assertThat(throwable).isInstanceOf(KeyNotFoundException.class).hasMessage("Recipient not found");

verify(partyInfoStore, times(2)).getPartyInfo();
verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore).getPartyInfo();
}

@Test
public void diffPartyInfoReturnsFullSetOnEmptyStore() {
doReturn(new PartyInfo("", emptySet(), emptySet())).when(partyInfoStore).getPartyInfo();

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final PartyInfo incomingInfo = new PartyInfo("", NEW_RECIPIENTS, emptySet());

final Set<Recipient> unsavedRecipients = this.partyInfoService.findUnsavedRecipients(incomingInfo);

assertThat(unsavedRecipients)
.hasSize(2)
.containsExactlyInAnyOrder(NEW_RECIPIENTS.toArray(new Recipient[0]));

verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore).getPartyInfo();
}

@Test
public void diffPartyInfoReturnsEmptySetOnFullStore() {
doReturn(new PartyInfo("", NEW_RECIPIENTS, emptySet())).when(partyInfoStore).getPartyInfo();

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final PartyInfo incomingInfo = new PartyInfo("", NEW_RECIPIENTS, emptySet());

final Set<Recipient> unsavedRecipients = this.partyInfoService.findUnsavedRecipients(incomingInfo);

assertThat(unsavedRecipients).isEmpty();

verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore).getPartyInfo();
}

@Test
public void diffPartyInfoReturnsNodesNotInStore() {
doReturn(
new PartyInfo(
"",
singleton(new Recipient(new Key("url1".getBytes()), "url1")),
emptySet()
)
).when(partyInfoStore).getPartyInfo();

this.partyInfoService = new PartyInfoServiceImpl(partyInfoStore, configuration, keyManager);

final PartyInfo incomingInfo = new PartyInfo("", NEW_RECIPIENTS, emptySet());

final Set<Recipient> unsavedRecipients = this.partyInfoService.findUnsavedRecipients(incomingInfo);

assertThat(unsavedRecipients)
.hasSize(1)
.containsExactlyInAnyOrder(new Recipient(new Key("url2".getBytes()), "url2"));

verify(partyInfoStore).store(any(PartyInfo.class));
verify(partyInfoStore).getPartyInfo();
}

}
Loading

0 comments on commit d881f33

Please sign in to comment.