com.quorum.tessera.p2p.partyinfo.PartyInfoBroadcaster Maven / Gradle / Ivy
package com.quorum.tessera.p2p.partyinfo;
import com.quorum.tessera.discovery.Discovery;
import com.quorum.tessera.discovery.NodeUri;
import com.quorum.tessera.partyinfo.P2pClient;
import com.quorum.tessera.partyinfo.model.PartyInfo;
import com.quorum.tessera.partyinfo.model.PartyInfoBuilder;
import com.quorum.tessera.partyinfo.node.NodeInfo;
import jakarta.ws.rs.ProcessingException;
import java.net.URI;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Polls every so often to all known nodes for any new discoverable nodes. This keeps all nodes
* up-to date and discoverable by other nodes
*/
public class PartyInfoBroadcaster implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(PartyInfoBroadcaster.class);
private final Discovery discovery;
private final PartyInfoParser partyInfoParser;
private final P2pClient p2pClient;
private final PartyStore partyStore;
public PartyInfoBroadcaster(final P2pClient p2pClient) {
this(Discovery.create(), PartyInfoParser.create(), p2pClient, PartyStore.getInstance());
}
public PartyInfoBroadcaster(
final Discovery discovery,
final PartyInfoParser partyInfoParser,
final P2pClient p2pClient,
final PartyStore partyStore) {
this.discovery = Objects.requireNonNull(discovery);
this.partyInfoParser = Objects.requireNonNull(partyInfoParser);
this.p2pClient = Objects.requireNonNull(p2pClient);
this.partyStore = Objects.requireNonNull(partyStore);
}
/**
* Iterates over all known parties and contacts them for the current state of their known node
* discovery list
*
* For Tessera 0.9 backwards, after contacting the known parties, this poller then updates this
* nodes list of data with any new information collected.
*
*
This behaviour is now deprecated since the /partyinfo API call now has been made more strict
* with node validation to prevent exploiting the API to attack the Tessera network.
*
*
This call is merely to let its parties know about this node existence, any recipients that
* want to be added to this node's PartyInfo will need to make their own partyinfo call and
* validation
*/
@Override
public void run() {
LOGGER.info("Started PartyInfo polling round");
partyStore.loadFromConfigIfEmpty();
final NodeInfo nodeInfo = discovery.getCurrent();
final NodeUri ourUrl = NodeUri.create(nodeInfo.getUrl());
final PartyInfo partyInfo =
PartyInfoBuilder.create()
.withUri(nodeInfo.getUrl())
.withRecipients(nodeInfo.getRecipientsAsMap())
.build();
final byte[] encodedPartyInfo = partyInfoParser.to(partyInfo);
LOGGER.debug("Contacting following peers with PartyInfo: {}", partyInfo.getParties());
LOGGER.debug("Sending party info {}", nodeInfo);
partyStore.getParties().stream()
.map(NodeUri::create)
.filter(url -> !ourUrl.equals(url))
.forEach(url -> pollSingleParty(url.asString(), encodedPartyInfo));
LOGGER.info("Finished PartyInfo polling round");
}
/**
* Sends a request for node information to a single target
*
* @param url the target URL to call
* @param encodedPartyInfo the encoded current party information
*/
protected void pollSingleParty(final String url, final byte[] encodedPartyInfo) {
final NodeUri nodeUri = NodeUri.create(url);
try {
LOGGER.debug("Sending party info to {}", nodeUri.asString());
p2pClient.sendPartyInfo(url, encodedPartyInfo);
LOGGER.debug("Sent party info to {}", nodeUri.asString());
} catch (Exception ex) {
Throwable cause = Optional.of(ex).map(Throwable::getCause).orElse(ex);
LOGGER.warn("Failed to connect to node {}, due to {}", url, cause.getMessage());
LOGGER.debug("Send failure exception", cause);
if (ProcessingException.class.isInstance(cause)) {
discovery.onDisconnect(URI.create(url));
partyStore.remove(URI.create(url));
}
}
}
}