[NC-875] Implement iterative peer search (#268)

* basic updatdes

* spotless inter alia

* building successfully

* funtioning

* minor update to docs

* rebased in previous commit, attempt to pass build server

* eliminate distanceSortedPeers

* spotless update

* revamp outstanding requests

* implementation of timeoutTask and corresponding test

* use setPeriodic

* testing with DiscoveryPeer

* remove commenceTimeoutTask from constructor

* isolate clock functionality out of recursive state

* update to docs

* validate size of outstandingrequestlist

* improve sanity check test

* remove extraneous copy

* add accurate interface parameters

* finalize
S. Matthew English 6 years ago committed by GitHub
parent e0d4f21e73
commit df88b69b2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDistanceCalculator.java
  2. 33
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerTable.java
  3. 223
      ethereum/p2p/src/main/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java
  4. 17
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/PeerDiscoveryControllerDistanceCalculatorTest.java
  5. 331
      ethereum/p2p/src/test/java/tech/pegasys/pantheon/ethereum/p2p/discovery/internal/RecursivePeerRefreshStateTest.java
  6. 173
      ethereum/p2p/src/test/resources/peers.json

@ -0,0 +1,50 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.Arrays;
public class PeerDistanceCalculator {
/**
* Calculates the XOR distance between two values.
*
* @param v1 the first value
* @param v2 the second value
* @return the distance
*/
static int distance(final BytesValue v1, final BytesValue v2) {
assert (v1.size() == v2.size());
final byte[] v1b = v1.extractArray();
final byte[] v2b = v2.extractArray();
if (Arrays.equals(v1b, v2b)) {
return 0;
}
int distance = v1b.length * 8;
for (int i = 0; i < v1b.length; i++) {
final byte xor = (byte) (0xff & (v1b[i] ^ v2b[i]));
if (xor == 0) {
distance -= 8;
} else {
int p = 7;
while (((xor >> p--) & 0x01) == 0) {
distance--;
}
break;
}
}
return distance;
}
}

@ -15,6 +15,7 @@ package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableList;
import static java.util.Comparator.comparingInt; import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDistanceCalculator.distance;
import tech.pegasys.pantheon.crypto.Hash; import tech.pegasys.pantheon.crypto.Hash;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer; import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
@ -203,38 +204,6 @@ public class PeerTable {
return distance == null ? distance(keccak256, peer.keccak256()) : distance; return distance == null ? distance(keccak256, peer.keccak256()) : distance;
} }
/**
* Calculates the XOR distance between two values.
*
* @param v1 the first value
* @param v2 the second value
* @return the distance
*/
static int distance(final BytesValue v1, final BytesValue v2) {
assert (v1.size() == v2.size());
final byte[] v1b = v1.extractArray();
final byte[] v2b = v2.extractArray();
if (Arrays.equals(v1b, v2b)) {
return 0;
}
int distance = v1b.length * 8;
for (int i = 0; i < v1b.length; i++) {
final byte xor = (byte) (0xff & (v1b[i] ^ v2b[i]));
if (xor == 0) {
distance -= 8;
} else {
int p = 7;
while (((xor >> p--) & 0x01) == 0) {
distance--;
}
break;
}
}
return distance;
}
/** A class that encapsulates the result of a peer addition to the table. */ /** A class that encapsulates the result of a peer addition to the table. */
public static class AddResult { public static class AddResult {
/** The outcome of the operation. */ /** The outcome of the operation. */

@ -0,0 +1,223 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static java.util.stream.Collectors.toList;
import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDistanceCalculator.distance;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
import tech.pegasys.pantheon.ethereum.p2p.peers.Peer;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.google.common.annotations.VisibleForTesting;
class RecursivePeerRefreshState {
private final int CONCURRENT_REQUEST_LIMIT = 3;
private final BytesValue target;
private final PeerBlacklist peerBlacklist;
private final BondingAgent bondingAgent;
private final NeighborFinder neighborFinder;
private final List<PeerDistance> anteList;
private final List<OutstandingRequest> outstandingRequestList;
private final List<BytesValue> contactedInCurrentExecution;
RecursivePeerRefreshState(
final BytesValue target,
final PeerBlacklist peerBlacklist,
final BondingAgent bondingAgent,
final NeighborFinder neighborFinder) {
this.target = target;
this.peerBlacklist = peerBlacklist;
this.bondingAgent = bondingAgent;
this.neighborFinder = neighborFinder;
this.anteList = new ArrayList<>();
this.outstandingRequestList = new ArrayList<>();
this.contactedInCurrentExecution = new ArrayList<>();
}
void kickstartBootstrapPeers(final List<Peer> bootstrapPeers) {
for (Peer bootstrapPeer : bootstrapPeers) {
final BytesValue peerId = bootstrapPeer.getId();
outstandingRequestList.add(new OutstandingRequest(bootstrapPeer));
contactedInCurrentExecution.add(peerId);
bondingAgent.performBonding(bootstrapPeer, true);
neighborFinder.issueFindNodeRequest(bootstrapPeer, target);
}
}
/**
* This method is intended to be called periodically by the {@link PeerDiscoveryController}, which
* will maintain a timer for purposes of effecting expiration of requests outstanding. Requests
* once encountered are deemed eligible for eviction if they have not been dispatched before the
* next invocation of the method.
*/
public void executeTimeoutEvaluation() {
for (int i = 0; i < outstandingRequestList.size(); i++) {
if (outstandingRequestList.get(i).getEvaluation()) {
final List<DiscoveryPeer> queryCandidates = determineFindNodeCandidates(anteList.size());
for (DiscoveryPeer candidate : queryCandidates) {
if (!contactedInCurrentExecution.contains(candidate.getId())
&& !outstandingRequestList.contains(new OutstandingRequest(candidate))) {
outstandingRequestList.remove(i);
executeFindNodeRequest(candidate);
}
}
}
outstandingRequestList.get(i).setEvaluation();
}
}
private void executeFindNodeRequest(final DiscoveryPeer peer) {
final BytesValue peerId = peer.getId();
outstandingRequestList.add(new OutstandingRequest(peer));
contactedInCurrentExecution.add(peerId);
neighborFinder.issueFindNodeRequest(peer, target);
}
/**
* The lookup initiator starts by picking CONCURRENT_REQUEST_LIMIT closest nodes to the target it
* knows of. The initiator then issues concurrent FindNode packets to those nodes.
*/
private void initiatePeerRefreshCycle(final List<DiscoveryPeer> peers) {
for (DiscoveryPeer peer : peers) {
if (!contactedInCurrentExecution.contains(peer.getId())) {
executeFindNodeRequest(peer);
}
}
}
void onNeighboursPacketReceived(final NeighborsPacketData neighboursPacket, final Peer peer) {
if (outstandingRequestList.contains(new OutstandingRequest(peer))) {
final List<DiscoveryPeer> receivedPeerList = neighboursPacket.getNodes();
for (DiscoveryPeer receivedPeer : receivedPeerList) {
if (!peerBlacklist.contains(receivedPeer)) {
bondingAgent.performBonding(receivedPeer, false);
anteList.add(new PeerDistance(receivedPeer, distance(target, receivedPeer.getId())));
}
}
outstandingRequestList.remove(new OutstandingRequest(peer));
queryNearestNodes();
}
}
private List<DiscoveryPeer> determineFindNodeCandidates(final int threshold) {
anteList.sort(
(peer1, peer2) -> {
if (peer1.getDistance() > peer2.getDistance()) return 1;
if (peer1.getDistance() < peer2.getDistance()) return -1;
return 0;
});
return anteList.subList(0, threshold).stream().map(PeerDistance::getPeer).collect(toList());
}
private void queryNearestNodes() {
if (outstandingRequestList.isEmpty()) {
final List<DiscoveryPeer> queryCandidates =
determineFindNodeCandidates(CONCURRENT_REQUEST_LIMIT);
initiatePeerRefreshCycle(queryCandidates);
}
}
@VisibleForTesting
List<OutstandingRequest> getOutstandingRequestList() {
return outstandingRequestList;
}
static class PeerDistance {
DiscoveryPeer peer;
Integer distance;
PeerDistance(final DiscoveryPeer peer, final Integer distance) {
this.peer = peer;
this.distance = distance;
}
DiscoveryPeer getPeer() {
return peer;
}
Integer getDistance() {
return distance;
}
@Override
public String toString() {
return peer + ": " + distance;
}
}
static class OutstandingRequest {
boolean evaluation;
Peer peer;
OutstandingRequest(final Peer peer) {
this.evaluation = false;
this.peer = peer;
}
boolean getEvaluation() {
return evaluation;
}
Peer getPeer() {
return peer;
}
void setEvaluation() {
this.evaluation = true;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final OutstandingRequest that = (OutstandingRequest) o;
return Objects.equals(peer.getId(), that.peer.getId());
}
@Override
public int hashCode() {
return Objects.hash(peer.getId());
}
@Override
public String toString() {
return peer.toString();
}
}
public interface NeighborFinder {
/**
* Sends a FIND_NEIGHBORS message to a {@link DiscoveryPeer}, in search of a target value.
*
* @param peer the peer to interrogate
* @param target the target node ID to find
*/
void issueFindNodeRequest(final Peer peer, final BytesValue target);
}
public interface BondingAgent {
/**
* Initiates a bonding PING-PONG cycle with a peer.
*
* @param peer The targeted peer.
* @param bootstrap Whether this is a bootstrap interaction.
*/
void performBonding(final Peer peer, final boolean bootstrap);
}
}

@ -13,6 +13,7 @@
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal; package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.pantheon.ethereum.p2p.discovery.internal.PeerDistanceCalculator.distance;
import tech.pegasys.pantheon.util.bytes.BytesValue; import tech.pegasys.pantheon.util.bytes.BytesValue;
@ -26,55 +27,55 @@ public class PeerDiscoveryControllerDistanceCalculatorTest {
public void distanceZero() { public void distanceZero() {
final byte[] id = new byte[64]; final byte[] id = new byte[64];
new Random().nextBytes(id); new Random().nextBytes(id);
assertThat(PeerTable.distance(BytesValue.wrap(id), BytesValue.wrap(id))).isEqualTo(0); assertThat(distance(BytesValue.wrap(id), BytesValue.wrap(id))).isEqualTo(0);
} }
@Test @Test
public void distance1() { public void distance1() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400000");
final BytesValue id2 = BytesValue.fromHexString("0x8f19400001"); final BytesValue id2 = BytesValue.fromHexString("0x8f19400001");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(1); assertThat(distance(id1, id2)).isEqualTo(1);
} }
@Test @Test
public void distance2() { public void distance2() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400000");
final BytesValue id2 = BytesValue.fromHexString("0x8f19400002"); final BytesValue id2 = BytesValue.fromHexString("0x8f19400002");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(2); assertThat(distance(id1, id2)).isEqualTo(2);
} }
@Test @Test
public void distance3() { public void distance3() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400000");
final BytesValue id2 = BytesValue.fromHexString("0x8f19400004"); final BytesValue id2 = BytesValue.fromHexString("0x8f19400004");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(3); assertThat(distance(id1, id2)).isEqualTo(3);
} }
@Test @Test
public void distance9() { public void distance9() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400100"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400100");
final BytesValue id2 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id2 = BytesValue.fromHexString("0x8f19400000");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(9); assertThat(distance(id1, id2)).isEqualTo(9);
} }
@Test @Test
public void distance40() { public void distance40() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400000");
final BytesValue id2 = BytesValue.fromHexString("0x0f19400000"); final BytesValue id2 = BytesValue.fromHexString("0x0f19400000");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(40); assertThat(distance(id1, id2)).isEqualTo(40);
} }
@Test(expected = AssertionError.class) @Test(expected = AssertionError.class)
public void distance40_differentLengths() { public void distance40_differentLengths() {
final BytesValue id1 = BytesValue.fromHexString("0x8f19400000"); final BytesValue id1 = BytesValue.fromHexString("0x8f19400000");
final BytesValue id2 = BytesValue.fromHexString("0x0f1940000099"); final BytesValue id2 = BytesValue.fromHexString("0x0f1940000099");
assertThat(PeerTable.distance(id1, id2)).isEqualTo(40); assertThat(distance(id1, id2)).isEqualTo(40);
} }
@Test @Test
public void distanceZero_emptyArrays() { public void distanceZero_emptyArrays() {
final BytesValue id1 = BytesValue.EMPTY; final BytesValue id1 = BytesValue.EMPTY;
final BytesValue id2 = BytesValue.EMPTY; final BytesValue id2 = BytesValue.EMPTY;
assertThat(PeerTable.distance(id1, id2)).isEqualTo(0); assertThat(distance(id1, id2)).isEqualTo(0);
} }
} }

@ -0,0 +1,331 @@
/*
* Copyright 2018 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.pantheon.ethereum.p2p.discovery.internal;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import tech.pegasys.pantheon.ethereum.p2p.discovery.DiscoveryPeer;
import tech.pegasys.pantheon.ethereum.p2p.peers.Endpoint;
import tech.pegasys.pantheon.ethereum.p2p.peers.PeerBlacklist;
import tech.pegasys.pantheon.util.bytes.Bytes32;
import tech.pegasys.pantheon.util.bytes.BytesValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
public class RecursivePeerRefreshStateTest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private RecursivePeerRefreshState recursivePeerRefreshState;
private final BytesValue target =
BytesValue.fromHexString(
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
private final RecursivePeerRefreshState.BondingAgent bondingAgent =
mock(RecursivePeerRefreshState.BondingAgent.class);
private final RecursivePeerRefreshState.NeighborFinder neighborFinder =
mock(RecursivePeerRefreshState.NeighborFinder.class);
private final List<TestPeer> aggregatePeerList = new ArrayList<>();
private NeighborsPacketData neighborsPacketData_000;
private NeighborsPacketData neighborsPacketData_010;
private NeighborsPacketData neighborsPacketData_011;
private NeighborsPacketData neighborsPacketData_012;
private NeighborsPacketData neighborsPacketData_013;
private TestPeer peer_000;
private TestPeer peer_010;
private TestPeer peer_020;
private TestPeer peer_021;
private TestPeer peer_022;
private TestPeer peer_023;
private TestPeer peer_011;
private TestPeer peer_120;
private TestPeer peer_121;
private TestPeer peer_122;
private TestPeer peer_123;
private TestPeer peer_012;
private TestPeer peer_220;
private TestPeer peer_221;
private TestPeer peer_222;
private TestPeer peer_223;
private TestPeer peer_013;
private TestPeer peer_320;
private TestPeer peer_321;
private TestPeer peer_322;
private TestPeer peer_323;
@Before
public void setup() throws Exception {
JsonNode peers =
MAPPER.readTree(RecursivePeerRefreshStateTest.class.getResource("/peers.json"));
recursivePeerRefreshState =
new RecursivePeerRefreshState(target, new PeerBlacklist(), bondingAgent, neighborFinder);
peer_000 = (TestPeer) generatePeer(peers);
peer_010 = (TestPeer) peer_000.getPeerTable().get(0);
peer_020 = (TestPeer) peer_010.getPeerTable().get(0);
peer_021 = (TestPeer) peer_010.getPeerTable().get(1);
peer_022 = (TestPeer) peer_010.getPeerTable().get(2);
peer_023 = (TestPeer) peer_010.getPeerTable().get(3);
peer_011 = (TestPeer) peer_000.getPeerTable().get(1);
peer_120 = (TestPeer) peer_011.getPeerTable().get(0);
peer_121 = (TestPeer) peer_011.getPeerTable().get(1);
peer_122 = (TestPeer) peer_011.getPeerTable().get(2);
peer_123 = (TestPeer) peer_011.getPeerTable().get(3);
peer_012 = (TestPeer) peer_000.getPeerTable().get(2);
peer_220 = (TestPeer) peer_012.getPeerTable().get(0);
peer_221 = (TestPeer) peer_012.getPeerTable().get(1);
peer_222 = (TestPeer) peer_012.getPeerTable().get(2);
peer_223 = (TestPeer) peer_012.getPeerTable().get(3);
peer_013 = (TestPeer) peer_000.getPeerTable().get(3);
peer_320 = (TestPeer) peer_013.getPeerTable().get(0);
peer_321 = (TestPeer) peer_013.getPeerTable().get(1);
peer_322 = (TestPeer) peer_013.getPeerTable().get(2);
peer_323 = (TestPeer) peer_013.getPeerTable().get(3);
neighborsPacketData_000 = NeighborsPacketData.create(peer_000.getPeerTable());
neighborsPacketData_010 = NeighborsPacketData.create(peer_010.getPeerTable());
neighborsPacketData_011 = NeighborsPacketData.create(peer_011.getPeerTable());
neighborsPacketData_012 = NeighborsPacketData.create(peer_012.getPeerTable());
neighborsPacketData_013 = NeighborsPacketData.create(peer_013.getPeerTable());
addPeersToAggregateListByOrdinalRank();
}
private void addPeersToAggregateListByOrdinalRank() {
aggregatePeerList.add(peer_323); // 1
aggregatePeerList.add(peer_011); // 2
aggregatePeerList.add(peer_012); // 3
aggregatePeerList.add(peer_013); // 4
aggregatePeerList.add(peer_020); // 5
aggregatePeerList.add(peer_021); // 6
aggregatePeerList.add(peer_022); // 7
aggregatePeerList.add(peer_023); // 8
aggregatePeerList.add(peer_120); // 9
aggregatePeerList.add(peer_121); // 10
aggregatePeerList.add(peer_122); // 11
aggregatePeerList.add(peer_123); // 12
aggregatePeerList.add(peer_220); // 13
aggregatePeerList.add(peer_221); // 14
aggregatePeerList.add(peer_222); // 15
aggregatePeerList.add(peer_223); // 16
aggregatePeerList.add(peer_320); // 17
aggregatePeerList.add(peer_321); // 18
aggregatePeerList.add(peer_322); // 19
aggregatePeerList.add(peer_010); // 20
aggregatePeerList.add(peer_000); // 21
}
@Test
public void shouldEstablishRelativeDistanceValues() {
for (int i = 0; i < aggregatePeerList.size() - 1; i++) {
int nodeOrdinalRank = aggregatePeerList.get(i).getOrdinalRank();
int neighborOrdinalRank = aggregatePeerList.get(i + 1).getOrdinalRank();
assertThat(nodeOrdinalRank).isLessThan(neighborOrdinalRank);
}
}
@Test
public void shouldConfirmPeersMatchCorrespondingPackets() {
assertThat(matchPeerToCorrespondingPacketData(peer_000, neighborsPacketData_000)).isTrue();
assertThat(matchPeerToCorrespondingPacketData(peer_010, neighborsPacketData_010)).isTrue();
assertThat(matchPeerToCorrespondingPacketData(peer_011, neighborsPacketData_011)).isTrue();
assertThat(matchPeerToCorrespondingPacketData(peer_012, neighborsPacketData_012)).isTrue();
assertThat(matchPeerToCorrespondingPacketData(peer_013, neighborsPacketData_013)).isTrue();
}
private boolean matchPeerToCorrespondingPacketData(
final TestPeer peer, final NeighborsPacketData neighborsPacketData) {
for (TestPeer neighbour :
neighborsPacketData.getNodes().stream().map(p -> (TestPeer) p).collect(toList())) {
if (neighbour.getParent() != peer.getIdentifier()) {
return false;
}
if (neighbour.getTier() != peer.getTier() + 1) {
return false;
}
}
return true;
}
@Test
public void shouldIssueRequestToPeerWithLesserDistanceGreaterHops() {
recursivePeerRefreshState.kickstartBootstrapPeers(Collections.singletonList(peer_000));
verify(bondingAgent).performBonding(peer_000, true);
verify(neighborFinder).issueFindNodeRequest(peer_000, target);
recursivePeerRefreshState.onNeighboursPacketReceived(neighborsPacketData_000, peer_000);
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
verify(bondingAgent).performBonding(peer_010, false);
verify(bondingAgent).performBonding(peer_011, false);
verify(bondingAgent).performBonding(peer_012, false);
verify(bondingAgent).performBonding(peer_013, false);
verify(neighborFinder, never()).issueFindNodeRequest(peer_010, target);
verify(neighborFinder).issueFindNodeRequest(peer_011, target);
verify(neighborFinder).issueFindNodeRequest(peer_012, target);
verify(neighborFinder).issueFindNodeRequest(peer_013, target);
recursivePeerRefreshState.onNeighboursPacketReceived(neighborsPacketData_011, peer_011);
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
verify(bondingAgent).performBonding(peer_120, false);
verify(bondingAgent).performBonding(peer_121, false);
verify(bondingAgent).performBonding(peer_122, false);
verify(bondingAgent).performBonding(peer_123, false);
recursivePeerRefreshState.onNeighboursPacketReceived(neighborsPacketData_012, peer_012);
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
verify(bondingAgent).performBonding(peer_220, false);
verify(bondingAgent).performBonding(peer_221, false);
verify(bondingAgent).performBonding(peer_222, false);
verify(bondingAgent).performBonding(peer_223, false);
recursivePeerRefreshState.onNeighboursPacketReceived(neighborsPacketData_013, peer_013);
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
verify(bondingAgent).performBonding(peer_320, false);
verify(bondingAgent).performBonding(peer_321, false);
verify(bondingAgent).performBonding(peer_322, false);
verify(bondingAgent).performBonding(peer_323, false);
verify(neighborFinder, never()).issueFindNodeRequest(peer_320, target);
verify(neighborFinder, never()).issueFindNodeRequest(peer_321, target);
verify(neighborFinder, never()).issueFindNodeRequest(peer_322, target);
verify(neighborFinder).issueFindNodeRequest(peer_323, target);
}
@Test
public void shouldIssueRequestToPeerWithGreaterDistanceOnExpirationOfLowerDistancePeerRequest() {
recursivePeerRefreshState.kickstartBootstrapPeers(Collections.singletonList(peer_000));
recursivePeerRefreshState.executeTimeoutEvaluation();
verify(bondingAgent).performBonding(peer_000, true);
verify(neighborFinder).issueFindNodeRequest(peer_000, target);
recursivePeerRefreshState.onNeighboursPacketReceived(neighborsPacketData_000, peer_000);
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
recursivePeerRefreshState.executeTimeoutEvaluation();
verify(neighborFinder, never()).issueFindNodeRequest(peer_010, target);
verify(neighborFinder).issueFindNodeRequest(peer_011, target);
verify(neighborFinder).issueFindNodeRequest(peer_012, target);
verify(neighborFinder).issueFindNodeRequest(peer_013, target);
recursivePeerRefreshState.executeTimeoutEvaluation();
assertThat(recursivePeerRefreshState.getOutstandingRequestList().size()).isLessThanOrEqualTo(3);
verify(neighborFinder).issueFindNodeRequest(peer_010, target);
}
private DiscoveryPeer generatePeer(final JsonNode peer) {
int parent = peer.get("parent").asInt();
int tier = peer.get("tier").asInt();
int identifier = peer.get("identifier").asInt();
int ordinalRank = peer.get("ordinalRank").asInt();
BytesValue id = BytesValue.fromHexString(peer.get("id").asText());
List<DiscoveryPeer> peerTable = new ArrayList<>();
if (peer.get("peerTable") != null) {
JsonNode peers = peer.get("peerTable");
for (JsonNode element : peers) {
peerTable.add(generatePeer(element));
}
} else {
peerTable = Collections.emptyList();
}
return new TestPeer(parent, tier, identifier, ordinalRank, id, peerTable);
}
static class TestPeer extends DiscoveryPeer {
int parent;
int tier;
int identifier;
int ordinalRank;
List<DiscoveryPeer> peerTable;
TestPeer(
final int parent,
final int tier,
final int identifier,
final int ordinalRank,
final BytesValue id,
final List<DiscoveryPeer> peerTable) {
super(id, "0.0.0.0", 1, 1);
this.parent = parent;
this.tier = tier;
this.identifier = identifier;
this.ordinalRank = ordinalRank;
this.peerTable = peerTable;
}
int getParent() {
return parent;
}
int getTier() {
return tier;
}
int getIdentifier() {
return identifier;
}
int getOrdinalRank() {
return ordinalRank;
}
List<DiscoveryPeer> getPeerTable() {
return peerTable;
}
@Override
public Bytes32 keccak256() {
return null;
}
@Override
public Endpoint getEndpoint() {
return null;
}
@Override
public String toString() {
return parent + "." + tier + "." + identifier;
}
}
}

@ -0,0 +1,173 @@
{
"parent": 0,
"tier": 0,
"identifier": 0,
"ordinalRank": 21,
"id": "0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
"peerTable": [
{
"parent": 0,
"tier": 1,
"identifier": 0,
"ordinalRank": 20,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000",
"peerTable": [
{
"parent": 0,
"tier": 2,
"identifier": 0,
"ordinalRank": 5,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000",
"peerTable": null
},
{
"parent": 0,
"tier": 2,
"identifier": 1,
"ordinalRank": 6,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000",
"peerTable": null
},
{
"parent": 0,
"tier": 2,
"identifier": 2,
"ordinalRank": 7,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000",
"peerTable": null
},
{
"parent": 0,
"tier": 2,
"identifier": 3,
"ordinalRank": 8,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000",
"peerTable": null
}
]
},
{
"parent": 0,
"tier": 1,
"identifier": 1,
"ordinalRank": 2,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010",
"peerTable": [
{
"parent": 1,
"tier": 2,
"identifier": 0,
"ordinalRank": 9,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000",
"peerTable": null
},
{
"parent": 1,
"tier": 2,
"identifier": 1,
"ordinalRank": 10,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000",
"peerTable": null
},
{
"parent": 1,
"tier": 2,
"identifier": 2,
"ordinalRank": 11,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000",
"peerTable": null
},
{
"parent": 1,
"tier": 2,
"identifier": 3,
"ordinalRank": 12,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000",
"peerTable": null
}
]
},
{
"parent": 0,
"tier": 1,
"identifier": 2,
"ordinalRank": 3,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100",
"peerTable": [
{
"parent": 2,
"tier": 2,
"identifier": 0,
"ordinalRank": 13,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000",
"peerTable": null
},
{
"parent": 2,
"tier": 2,
"identifier": 1,
"ordinalRank": 14,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000",
"peerTable": null
},
{
"parent": 2,
"tier": 2,
"identifier": 2,
"ordinalRank": 15,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000",
"peerTable": null
},
{
"parent": 2,
"tier": 2,
"identifier": 3,
"ordinalRank": 16,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000",
"peerTable": null
}
]
},
{
"parent": 0,
"tier": 1,
"identifier": 3,
"ordinalRank": 4,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000",
"peerTable": [
{
"parent": 3,
"tier": 2,
"identifier": 0,
"ordinalRank": 17,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000",
"peerTable": null
},
{
"parent": 3,
"tier": 2,
"identifier": 1,
"ordinalRank": 18,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000",
"peerTable": null
},
{
"parent": 3,
"tier": 2,
"identifier": 2,
"ordinalRank": 19,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000",
"peerTable": null
},
{
"parent": 3,
"tier": 2,
"identifier": 3,
"ordinalRank": 1,
"id": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
"peerTable": null
}
]
}
]
}
Loading…
Cancel
Save