diff --git a/.gitignore b/.gitignore index f3cb468aa75..17ffd1401a9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ database # doc doc + +# gossip +pernodedata.* +ringstate.* +shareddata.* \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6052546b613..71cd5d4e9c4 100644 --- a/build.gradle +++ b/build.gradle @@ -80,6 +80,12 @@ dependencies { compile group: 'com.alibaba', name: 'fastjson', version: '1.2.44' compile group: 'com.google.inject', name: 'guice', version: '4.1.0' + + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.5' + compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.5' + compile group: 'org.apache.commons', name: 'commons-math', version: '2.2' + + compile group: 'io.dropwizard.metrics', name: 'metrics-core', version: '3.1.2' } tasks.matching { it instanceof Test }.all { diff --git a/src/main/java/org/tron/gossip/GossipSettings.java b/src/main/java/org/tron/gossip/GossipSettings.java new file mode 100755 index 00000000000..85e4e1a3ddb --- /dev/null +++ b/src/main/java/org/tron/gossip/GossipSettings.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip; + +import org.tron.gossip.lock.LockManagerSettings; + +import java.util.HashMap; +import java.util.Map; + +/** + * In this object the settings used by the GossipService are held. + * + */ +public class GossipSettings { + + /** Time between gossip'ing in ms. Default is 1 second. */ + private int gossipInterval = 10; + + /** Time between cleanups in ms. Default is 10 seconds. */ + private int cleanupInterval = 5000; + + /** the minimum samples needed before reporting a result */ + private int minimumSamples = 5; + + /** the number of samples to keep per host */ + private int windowSize = 5000; + + /** the threshold for the detector */ + private double convictThreshold = 10; + + private String distribution = "normal"; + + private String activeGossipClass = "org.tron.gossip.manager.SimpleActiveGossiper"; + + private String transportManagerClass = "org.tron.gossip.transport.udp.UdpTransportManager"; + private String protocolManagerClass = "org.tron.gossip.protocol.json.JacksonProtocolManager"; + + private Map activeGossipProperties = new HashMap<>(); + + private String pathToRingState = "./"; + + private boolean persistRingState = true; + + private String pathToDataState = "./"; + + private boolean persistDataState = true; + + private String pathToKeyStore = "./keys"; + + private boolean signMessages = false; + + // Settings related to lock manager + private LockManagerSettings lockManagerSettings = LockManagerSettings + .getLockManagerDefaultSettings(); + + private boolean bulkTransfer = false; + + private int bulkTransferSize = StartupSettings.DEFAULT_BULK_TRANSFER_SIZE; + + /** + * Construct GossipSettings with default settings. + */ + public GossipSettings() { + } + + /** + * Construct GossipSettings with given settings. + * + * @param gossipInterval + * The gossip interval in ms. + * @param cleanupInterval + * The cleanup interval in ms. + */ + public GossipSettings(int gossipInterval, int cleanupInterval, int windowSize, int minimumSamples, + double convictThreshold, String distribution, boolean bulkTransfer) { + this.gossipInterval = gossipInterval; + this.cleanupInterval = cleanupInterval; + this.windowSize = windowSize; + this.minimumSamples = minimumSamples; + this.convictThreshold = convictThreshold; + this.distribution = distribution; + this.bulkTransfer = bulkTransfer; + } + + /** + * Set the gossip interval. This is the time between a gossip message is send. + * + * @param gossipInterval + * The gossip interval in ms. + */ + public void setGossipTimeout(int gossipInterval) { + this.gossipInterval = gossipInterval; + } + + /** + * Set the cleanup interval. This is the time between the last heartbeat received from a member + * and when it will be marked as dead. + * + * @param cleanupInterval + * The cleanup interval in ms. + */ + public void setCleanupInterval(int cleanupInterval) { + this.cleanupInterval = cleanupInterval; + } + + /** + * Get the gossip interval. + * + * @return The gossip interval in ms. + */ + public int getGossipInterval() { + return gossipInterval; + } + + /** + * Get the clean interval. + * + * @return The cleanup interval. + */ + public int getCleanupInterval() { + return cleanupInterval; + } + + public int getMinimumSamples() { + return minimumSamples; + } + + public void setMinimumSamples(int minimumSamples) { + this.minimumSamples = minimumSamples; + } + + public int getWindowSize() { + return windowSize; + } + + public void setWindowSize(int windowSize) { + this.windowSize = windowSize; + } + + public double getConvictThreshold() { + return convictThreshold; + } + + public void setConvictThreshold(double convictThreshold) { + this.convictThreshold = convictThreshold; + } + + public void setGossipInterval(int gossipInterval) { + this.gossipInterval = gossipInterval; + } + + public String getDistribution() { + return distribution; + } + + public void setDistribution(String distribution) { + this.distribution = distribution; + } + + public String getActiveGossipClass() { + return activeGossipClass; + } + + public void setActiveGossipClass(String activeGossipClass) { + this.activeGossipClass = activeGossipClass; + } + + public Map getActiveGossipProperties() { + return activeGossipProperties; + } + + public void setActiveGossipProperties(Map activeGossipProperties) { + this.activeGossipProperties = activeGossipProperties; + } + + public String getPathToRingState() { + return pathToRingState; + } + + public void setPathToRingState(String pathToRingState) { + this.pathToRingState = pathToRingState; + } + + public boolean isPersistRingState() { + return persistRingState; + } + + public void setPersistRingState(boolean persistRingState) { + this.persistRingState = persistRingState; + } + + public String getPathToDataState() { + return pathToDataState; + } + + public void setPathToDataState(String pathToDataState) { + this.pathToDataState = pathToDataState; + } + + public boolean isPersistDataState() { + return persistDataState; + } + + public void setPersistDataState(boolean persistDataState) { + this.persistDataState = persistDataState; + } + + public String getPathToKeyStore() { + return pathToKeyStore; + } + + public void setPathToKeyStore(String pathToKeyStore) { + this.pathToKeyStore = pathToKeyStore; + } + + public boolean isSignMessages() { + return signMessages; + } + + public void setSignMessages(boolean signMessages) { + this.signMessages = signMessages; + } + + public String getTransportManagerClass() { + return transportManagerClass; + } + + public void setTransportManagerClass(String transportManagerClass) { + this.transportManagerClass = transportManagerClass; + } + + public String getProtocolManagerClass() { + return protocolManagerClass; + } + + public void setProtocolManagerClass(String protocolManagerClass) { + this.protocolManagerClass = protocolManagerClass; + } + + public LockManagerSettings getLockManagerSettings() { + return lockManagerSettings; + } + + /** + * Set the lock settings use by the lock manager + * @param lockManagerSettings lock settings. This object cannot be null. + */ + public void setLockManagerSettings(LockManagerSettings lockManagerSettings) { + this.lockManagerSettings = lockManagerSettings; + } + + public boolean isBulkTransfer() { + return bulkTransfer; + } + + public void setBulkTransfer(boolean bulkTransfer) { + this.bulkTransfer = bulkTransfer; + } + + public int getBulkTransferSize() { + return bulkTransferSize; + } + + public void setBulkTransferSize(int bulkTransferSize) { + this.bulkTransferSize = bulkTransferSize; + } +} diff --git a/src/main/java/org/tron/gossip/LocalMember.java b/src/main/java/org/tron/gossip/LocalMember.java new file mode 100755 index 00000000000..e16688d7a8b --- /dev/null +++ b/src/main/java/org/tron/gossip/LocalMember.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip; + +import java.net.URI; +import java.util.Map; + +import org.tron.gossip.accrual.FailureDetector; + +/** + * This object represent a gossip member with the properties known locally. These objects are stored + * in the local list of gossip members. + * + */ +public class LocalMember extends Member { + /** The failure detector for this member */ + private transient FailureDetector detector; + + /** + * + * @param uri + * The uri of the member + * @param id + * id of the node + * @param heartbeat + * The current heartbeat + */ + public LocalMember(String clusterName, URI uri, String id, + long heartbeat, Map properties, int windowSize, int minSamples, String distribution) { + super(clusterName, uri, id, heartbeat, properties ); + detector = new FailureDetector(minSamples, windowSize, distribution); + } + + protected LocalMember(){ + + } + + public void recordHeartbeat(long now){ + detector.recordHeartbeat(now); + } + + public Double detect(long now) { + return detector.computePhiMeasure(now); + } + + @Override + public String toString() { + Double d = null; + try { + d = detect(System.nanoTime()); + } catch (RuntimeException ex) {} + return "LocalGossipMember [uri=" + uri + ", heartbeat=" + heartbeat + ", clusterName=" + + clusterName + ", id=" + id + ", currentdetect=" + d +" ]"; + } + +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/Member.java b/src/main/java/org/tron/gossip/Member.java new file mode 100755 index 00000000000..02cf4a737c9 --- /dev/null +++ b/src/main/java/org/tron/gossip/Member.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Map; + +/** + * An abstract class representing a gossip member. + * + */ +public abstract class Member implements Comparable { + + + protected URI uri; + + protected volatile long heartbeat; + + protected String clusterName; + + /** + * The purpose of the id field is to be able for nodes to identify themselves beyond their + * host/port. For example an application might generate a persistent id so if they rejoin the + * cluster at a different host and port we are aware it is the same node. + */ + protected String id; + + /* properties provided at startup time */ + protected Map properties; + + /** + * Constructor. + * + * @param clusterName + * The name of the cluster + * @param uri + * A URI object containing IP/hostname and port + * @param heartbeat + * The current heartbeat + * @param id + * An id that may be replaced after contact + */ + public Member(String clusterName, URI uri, String id, long heartbeat, Map properties) { + this.clusterName = clusterName; + this.id = id; + this.heartbeat = heartbeat; + this.uri = uri; + this.properties = properties; + } + + protected Member(){} + /** + * Get the name of the cluster the member belongs to. + * + * @return The cluster name + */ + public String getClusterName() { + return clusterName; + } + + + /** + * @return The member address in the form IP/host:port Similar to the toString in + * {@link InetSocketAddress} + */ + public String computeAddress() { + return uri.getHost() + ":" + uri.getPort(); + } + + /** + * Get the heartbeat of this gossip member. + * + * @return The current heartbeat. + */ + public long getHeartbeat() { + return heartbeat; + } + + /** + * Set the heartbeat of this gossip member. + * + * @param heartbeat + * The new heartbeat. + */ + public void setHeartbeat(long heartbeat) { + this.heartbeat = heartbeat; + } + + public String getId() { + return id; + } + + public void setId(String _id) { + this.id = _id; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public String toString() { + return "Member [address=" + computeAddress() + ", id=" + id + ", heartbeat=" + heartbeat + "]"; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + String address = computeAddress(); + result = prime * result + ((address == null) ? 0 : address.hashCode()) + (clusterName == null ? 0 + : clusterName.hashCode()); + return result; + } + + public URI getUri() { + return uri; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + System.err.println("equals(): obj is null."); + return false; + } + if (!(obj instanceof Member)) { + System.err.println("equals(): obj is not of type GossipMember."); + return false; + } + // The object is the same of they both have the same address (hostname and port). + return computeAddress().equals(((LocalMember) obj).computeAddress()) + && getClusterName().equals(((LocalMember) obj).getClusterName()); + } + + public int compareTo(Member other) { + return this.computeAddress().compareTo(other.computeAddress()); + } +} diff --git a/src/main/java/org/tron/gossip/RemoteMember.java b/src/main/java/org/tron/gossip/RemoteMember.java new file mode 100755 index 00000000000..03a1e8b0a48 --- /dev/null +++ b/src/main/java/org/tron/gossip/RemoteMember.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +/** + * The object represents a gossip member with the properties as received from a remote gossip + * member. + * + */ +public class RemoteMember extends Member { + + /** + * Constructor. + * + * @param uri + * A URI object containing IP/hostname and port + * @param heartbeat + * The current heartbeat + */ + public RemoteMember(String clusterName, URI uri, String id, long heartbeat, Map properties) { + super(clusterName, uri, id, heartbeat, properties); + } + + public RemoteMember(String clusterName, URI uri, String id) { + super(clusterName, uri, id, System.nanoTime(), new HashMap()); + } + +} diff --git a/src/main/java/org/tron/gossip/StartupSettings.java b/src/main/java/org/tron/gossip/StartupSettings.java new file mode 100755 index 00000000000..3a98542790c --- /dev/null +++ b/src/main/java/org/tron/gossip/StartupSettings.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.log4j.Logger; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * This object represents the settings used when starting the gossip service. + * + */ +public class StartupSettings { + private static final Logger log = Logger.getLogger(StartupSettings.class); + + /** The id to use fo the service */ + private String id; + + private URI uri; + + private String cluster; + + /** The gossip settings used at startup. */ + private final GossipSettings gossipSettings; + + /** The list with gossip members to start with. */ + private final List gossipMembers; + + /** Default setting values */ + private static final boolean DEFAULT_BULK_TRANSFER = false; + public static final int DEFAULT_BULK_TRANSFER_SIZE = 100; + + /** + * Constructor. + * + * @param id + * The id to be used for this service + * @param uri + * A URI object containing IP/hostname and port + * @param logLevel + * unused + */ + public StartupSettings(String id, URI uri, int logLevel, String cluster) { + this(id, uri, new GossipSettings(), cluster); + } + + public URI getUri() { + return uri; + } + + public void setUri(URI uri) { + this.uri = uri; + } + + /** + * Constructor. + * + * @param id + * The id to be used for this service + * @param uri + * A URI object containing IP/hostname and port + */ + public StartupSettings(String id, URI uri, GossipSettings gossipSettings, String cluster) { + this.id = id; + this.uri = uri; + this.gossipSettings = gossipSettings; + this.setCluster(cluster); + gossipMembers = new ArrayList<>(); + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getCluster() { + return cluster; + } + + /** + * Set the id to be used for this service. + * + * @param id + * The id for this service. + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get the id for this service. + * + * @return the service's id. + */ + public String getId() { + return id; + } + + /** + * Get the GossipSettings. + * + * @return The GossipSettings object. + */ + public GossipSettings getGossipSettings() { + return gossipSettings; + } + + /** + * Add a gossip member to the list of members to start with. + * + * @param member + * The member to add. + */ + public void addGossipMember(Member member) { + gossipMembers.add(member); + } + + /** + * Get the list with gossip members. + * + * @return The gossip members. + */ + public List getGossipMembers() { + return gossipMembers; + } + + /** + * Parse the settings for the gossip service from a JSON file. + * + * @param jsonFile + * The file object which refers to the JSON config file. + * @return The StartupSettings object with the settings from the config file. + * @throws FileNotFoundException + * Thrown when the file cannot be found. + * @throws IOException + * Thrown when reading the file gives problems. + * @throws URISyntaxException + */ + public static StartupSettings fromJSONFile(File jsonFile) throws + FileNotFoundException, IOException, URISyntaxException { + ObjectMapper om = new ObjectMapper(); + JsonNode root = om.readTree(jsonFile); + JsonNode jsonObject = root.get(0); + String uri = jsonObject.get("uri").textValue(); + String id = jsonObject.get("id").textValue(); + Map properties = new HashMap(); + JsonNode n = jsonObject.get("properties"); + Iterator> l = n.fields(); + while (l.hasNext()){ + Entry i = l.next(); + properties.put(i.getKey(), i.getValue().asText()); + } + //TODO constants as defaults? + // TODO setting keys as constants? + int gossipInterval = jsonObject.get("gossip_interval").intValue(); + int cleanupInterval = jsonObject.get("cleanup_interval").intValue(); + int windowSize = jsonObject.get("window_size").intValue(); + int minSamples = jsonObject.get("minimum_samples").intValue(); + double convictThreshold = jsonObject.get("convict_threshold").asDouble(); + String cluster = jsonObject.get("cluster").textValue(); + String distribution = jsonObject.get("distribution").textValue(); + boolean bulkTransfer = jsonObject.has("bulk_transfer") ? + jsonObject.get("bulk_transfer").booleanValue() : + DEFAULT_BULK_TRANSFER; + int bulkTransferSize = jsonObject.has("bulk_transfer_size") ? + jsonObject.get("bulk_transfer_size").intValue() : + DEFAULT_BULK_TRANSFER_SIZE; + if (cluster == null){ + throw new IllegalArgumentException("cluster was null. It is required"); + } + String transportClass = jsonObject.has("transport_manager_class") ? + jsonObject.get("transport_manager_class").textValue() : + null; + String protocolClass = jsonObject.has("protocol_manager_class") ? + jsonObject.get("protocol_manager_class").textValue() : + null; + URI uri2 = new URI(uri); + GossipSettings gossipSettings = new GossipSettings(gossipInterval, cleanupInterval, windowSize, + minSamples, convictThreshold, distribution, bulkTransfer); + gossipSettings.setBulkTransferSize(bulkTransferSize); + if (transportClass != null) { + gossipSettings.setTransportManagerClass(transportClass); + } + if (protocolClass != null) { + gossipSettings.setProtocolManagerClass(protocolClass); + } + StartupSettings settings = new StartupSettings(id, uri2, gossipSettings, cluster); + String configMembersDetails = "Config-members ["; + JsonNode membersJSON = jsonObject.get("members"); + Iterator it = membersJSON.iterator(); + while (it.hasNext()){ + JsonNode child = it.next(); + URI uri3 = new URI(child.get("uri").textValue()); + RemoteMember member = new RemoteMember(child.get("cluster").asText(), + uri3, "", 0, new HashMap()); + settings.addGossipMember(member); + configMembersDetails += member.computeAddress(); + configMembersDetails += ", "; + } + log.info(configMembersDetails + "]"); + return settings; + } +} diff --git a/src/main/java/org/tron/gossip/accrual/FailureDetector.java b/src/main/java/org/tron/gossip/accrual/FailureDetector.java new file mode 100755 index 00000000000..6e9d22bbd34 --- /dev/null +++ b/src/main/java/org/tron/gossip/accrual/FailureDetector.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.accrual; + +import org.apache.commons.math.MathException; +import org.apache.commons.math.distribution.ExponentialDistributionImpl; +import org.apache.commons.math.distribution.NormalDistributionImpl; +import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; +import org.apache.log4j.Logger; + +public class FailureDetector { + + public static final Logger LOGGER = Logger.getLogger(FailureDetector.class); + private final DescriptiveStatistics descriptiveStatistics; + private final long minimumSamples; + private volatile long latestHeartbeatMs = -1; + private final String distribution; + + public FailureDetector(long minimumSamples, int windowSize, String distribution) { + descriptiveStatistics = new DescriptiveStatistics(windowSize); + this.minimumSamples = minimumSamples; + this.distribution = distribution; + } + + /** + * Updates the statistics based on the delta between the last + * heartbeat and supplied time + * + * @param now the time of the heartbeat in milliseconds + */ + public synchronized void recordHeartbeat(long now) { + if (now <= latestHeartbeatMs) { + return; + } + if (latestHeartbeatMs != -1) { + descriptiveStatistics.addValue(now - latestHeartbeatMs); + } + latestHeartbeatMs = now; + } + + public synchronized Double computePhiMeasure(long now) { + if (latestHeartbeatMs == -1 || descriptiveStatistics.getN() < minimumSamples) { + return null; + } + long delta = now - latestHeartbeatMs; + try { + double probability; + if (distribution.equals("normal")) { + double standardDeviation = descriptiveStatistics.getStandardDeviation(); + standardDeviation = standardDeviation < 0.1 ? 0.1 : standardDeviation; + probability = new NormalDistributionImpl(descriptiveStatistics.getMean(), standardDeviation).cumulativeProbability(delta); + } else { + probability = new ExponentialDistributionImpl(descriptiveStatistics.getMean()).cumulativeProbability(delta); + } + final double eps = 1e-12; + if (1 - probability < eps) { + probability = 1.0; + } + return -1.0d * Math.log10(1.0d - probability); + } catch (MathException | IllegalArgumentException e) { + LOGGER.debug(e); + return null; + } + } +} diff --git a/src/main/java/org/tron/gossip/crdt/Crdt.java b/src/main/java/org/tron/gossip/crdt/Crdt.java new file mode 100755 index 00000000000..4047edb6a92 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/Crdt.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; +/** + * + * Immutable type + * + * @param + * @param + */ +public interface Crdt> { + + + MergeReturnType merge(MergeReturnType other); + SetType value(); + /** + * Called to self optimize. Some CRDTs may use some mechanism to clean up be + * removing obsolete data outside the scope of merging. IE this could clean up + * temporal values, old copies etc. + * @return the Crdt structure optimized + */ + MergeReturnType optimize(); + +} diff --git a/src/main/java/org/tron/gossip/crdt/CrdtAddRemoveSet.java b/src/main/java/org/tron/gossip/crdt/CrdtAddRemoveSet.java new file mode 100755 index 00000000000..0c7223d9636 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/CrdtAddRemoveSet.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Set; + +// Interface extends CrdtSet interface with add and remove operation that are guaranteed to be immutable. +// If your implementation provide immutable add/remove operations you can extend AbstractCRDTStringSetTest to check it in the most ways. + +public interface CrdtAddRemoveSet, R extends CrdtAddRemoveSet> extends CrdtSet { + R add(T element); + + R remove(T element); +} diff --git a/src/main/java/org/tron/gossip/crdt/CrdtBiFunctionMerge.java b/src/main/java/org/tron/gossip/crdt/CrdtBiFunctionMerge.java new file mode 100755 index 00000000000..c4c8b972696 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/CrdtBiFunctionMerge.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.function.BiFunction; + +@SuppressWarnings("rawtypes") +public class CrdtBiFunctionMerge implements BiFunction { + + @SuppressWarnings("unchecked") + @Override + public Crdt apply(Crdt t, Crdt u) { + if (t == null && u == null){ + return null; + } else if (t == null){ + return u; + } else if (u == null){ + return t; + } + if (! u.getClass().equals(t.getClass())){ + throw new IllegalArgumentException( "Can not merge " + t.getClass() + " "+ u.getClass()); + } + return t.merge(u); + } + + @SuppressWarnings("unchecked") + public static Crdt applyStatic(Crdt t, Crdt u){ + if (t == null && u == null){ + return null; + } else if (t == null){ + return u; + } else if (u == null){ + return t; + } + if (! u.getClass().equals(t.getClass())){ + throw new IllegalArgumentException( "Can not merge " + t.getClass() + " "+ u.getClass()); + } + return t.merge(u); + } +} diff --git a/src/main/java/org/tron/gossip/crdt/CrdtCounter.java b/src/main/java/org/tron/gossip/crdt/CrdtCounter.java new file mode 100755 index 00000000000..d38b23a0ab4 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/CrdtCounter.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +public interface CrdtCounter> + extends Crdt { + +} + diff --git a/src/main/java/org/tron/gossip/crdt/CrdtModule.java b/src/main/java/org/tron/gossip/crdt/CrdtModule.java new file mode 100755 index 00000000000..d41d2c01f85 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/CrdtModule.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.tron.gossip.LocalMember; +import org.tron.gossip.lock.vote.MajorityVote; +import org.tron.gossip.lock.vote.Vote; +import org.tron.gossip.lock.vote.VoteCandidate; +import org.tron.gossip.replication.BlackListReplicable; +import org.tron.gossip.replication.Replicable; +import org.tron.gossip.replication.WhiteListReplicable; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +abstract class OrSetMixin { + @JsonCreator + OrSetMixin(@JsonProperty("elements") Map> w, @JsonProperty("tombstones") Map> h) { } + @JsonProperty("elements") abstract Map> getElements(); + @JsonProperty("tombstones") abstract Map> getTombstones(); + @JsonIgnore abstract boolean isEmpty(); +} + +abstract class LWWSetMixin { + @JsonCreator + LWWSetMixin(@JsonProperty("data") Map struct) { } + @JsonProperty("data") abstract Map getStruct(); +} + +abstract class LWWSetTimestampsMixin { + @JsonCreator + LWWSetTimestampsMixin(@JsonProperty("add") long latestAdd, @JsonProperty("remove") long latestRemove) { } + @JsonProperty("add") abstract long getLatestAdd(); + @JsonProperty("remove") abstract long getLatestRemove(); +} + +abstract class MaxChangeSetMixin { + @JsonCreator + MaxChangeSetMixin(@JsonProperty("data") Map struct) { } + @JsonProperty("data") abstract Map getStruct(); +} + +abstract class TwoPhaseSetMixin { + @JsonCreator + TwoPhaseSetMixin(@JsonProperty("added") Set added, @JsonProperty("removed") Set removed) { } + @JsonProperty("added") abstract Set getAdded(); + @JsonProperty("removed") abstract Set getRemoved(); +} + +abstract class GrowOnlySetMixin{ + @JsonCreator + GrowOnlySetMixin(@JsonProperty("elements") Set elements){ } + @JsonProperty("elements") abstract Set getElements(); + @JsonIgnore abstract boolean isEmpty(); +} + +abstract class GrowOnlyCounterMixin { + @JsonCreator + GrowOnlyCounterMixin(@JsonProperty("counters") Map counters) { } + @JsonProperty("counters") abstract Map getCounters(); +} + +abstract class PNCounterMixin { + @JsonCreator + PNCounterMixin(@JsonProperty("p-counters") Map up, @JsonProperty("n-counters") Map down) { } + @JsonProperty("p-counters") abstract Map getPCounters(); + @JsonProperty("n-counters") abstract Map getNCounters(); +} + +@JsonTypeInfo( + use = JsonTypeInfo.Id.CLASS, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +abstract class ReplicableMixin { + +} + +abstract class WhiteListReplicableMixin { + @JsonCreator + WhiteListReplicableMixin(@JsonProperty("whiteListMembers") List whiteListMembers) { } + @JsonProperty("whiteListMembers") abstract List getWhiteListMembers(); +} + +abstract class BlackListReplicableMixin { + @JsonCreator + BlackListReplicableMixin(@JsonProperty("blackListMembers") List blackListMembers) { } + @JsonProperty("blackListMembers") abstract List getBlackListMembers(); +} + +abstract class VoteCandidateMixin { + @JsonCreator + VoteCandidateMixin( + @JsonProperty("candidateNodeId") String candidateNodeId, + @JsonProperty("votingKey") String votingKey, + @JsonProperty("votes") Map votes + ) { } +} + +abstract class VoteMixin { + @JsonCreator + VoteMixin( + @JsonProperty("votingNode") String votingNode, + @JsonProperty("voteValue") Boolean voteValue, + @JsonProperty("voteExchange") Boolean voteExchange, + @JsonProperty("liveMembers") List liveMembers, + @JsonProperty("deadMembers") List deadMembers + ) { } +} + +abstract class MajorityVoteMixin{ + @JsonCreator + MajorityVoteMixin(@JsonProperty("voteCandidates") Map voteCandidateMap){ } +} + +//If anyone wants to take a stab at this. please have at it +//https://github.com/FasterXML/jackson-datatype-guava/blob/master/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java +public class CrdtModule extends SimpleModule { + + private static final long serialVersionUID = 6134836523275023418L; + + public CrdtModule() { + super("CrdtModule", new Version(0, 0, 0, "0.0.0", "org.tron.gossip", "gossip")); + } + + @Override + public void setupModule(SetupContext context) { + context.setMixInAnnotations(OrSet.class, OrSetMixin.class); + context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class); + context.setMixInAnnotations(GrowOnlyCounter.class, GrowOnlyCounterMixin.class); + context.setMixInAnnotations(PNCounter.class, PNCounterMixin.class); + context.setMixInAnnotations(LwwSet.class, LWWSetMixin.class); + context.setMixInAnnotations(LwwSet.Timestamps.class, LWWSetTimestampsMixin.class); + context.setMixInAnnotations(MaxChangeSet.class, MaxChangeSetMixin.class); + context.setMixInAnnotations(TwoPhaseSet.class, TwoPhaseSetMixin.class); + context.setMixInAnnotations(Replicable.class, ReplicableMixin.class); + context.setMixInAnnotations(WhiteListReplicable.class, WhiteListReplicableMixin.class); + context.setMixInAnnotations(BlackListReplicable.class, BlackListReplicableMixin.class); + context.setMixInAnnotations(MajorityVote.class, MajorityVoteMixin.class); + context.setMixInAnnotations(VoteCandidate.class, VoteCandidateMixin.class); + context.setMixInAnnotations(Vote.class, VoteMixin.class); + } + +} + diff --git a/src/main/java/org/tron/gossip/crdt/CrdtSet.java b/src/main/java/org/tron/gossip/crdt/CrdtSet.java new file mode 100755 index 00000000000..da502b1b7c5 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/CrdtSet.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Set; + +public interface CrdtSet, R extends CrdtSet> +extends Crdt { + +} + diff --git a/src/main/java/org/tron/gossip/crdt/GrowOnlyCounter.java b/src/main/java/org/tron/gossip/crdt/GrowOnlyCounter.java new file mode 100755 index 00000000000..fcb4f48aba0 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/GrowOnlyCounter.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import org.tron.gossip.manager.GossipManager; + +import java.util.HashMap; +import java.util.Map; + +public class GrowOnlyCounter implements CrdtCounter { + + private final Map counters = new HashMap<>(); + + GrowOnlyCounter(Map counters) { + this.counters.putAll(counters); + } + + public GrowOnlyCounter(GrowOnlyCounter growOnlyCounter, Builder builder) { + counters.putAll(growOnlyCounter.counters); + if (counters.containsKey(builder.myId)) { + Long newValue = counters.get(builder.myId) + builder.counter; + counters.replace(builder.myId, newValue); + } else { + counters.put(builder.myId, builder.counter); + } + } + + public GrowOnlyCounter(Builder builder) { + counters.put(builder.myId, builder.counter); + } + + public GrowOnlyCounter(GossipManager manager) { + counters.put(manager.getMyself().getId(), 0L); + } + + public GrowOnlyCounter(GrowOnlyCounter growOnlyCounter, GrowOnlyCounter other) { + counters.putAll(growOnlyCounter.counters); + for (Map.Entry entry : other.counters.entrySet()) { + String otherKey = entry.getKey(); + Long otherValue = entry.getValue(); + + if (counters.containsKey(otherKey)) { + Long newValue = Math.max(counters.get(otherKey), otherValue); + counters.replace(otherKey, newValue); + } else { + counters.put(otherKey, otherValue); + } + } + } + + @Override + public GrowOnlyCounter merge(GrowOnlyCounter other) { + return new GrowOnlyCounter(this, other); + } + + @Override + public Long value() { + Long globalCount = 0L; + for (Long increment : counters.values()) { + globalCount += increment; + } + return globalCount; + } + + @Override + public GrowOnlyCounter optimize() { + return new GrowOnlyCounter(counters); + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + GrowOnlyCounter other = (GrowOnlyCounter) obj; + return value().longValue() == other.value().longValue(); + } + + @Override + public String toString() { + return "GrowOnlyCounter [counters= " + counters + ", Value=" + value() + "]"; + } + + Map getCounters() { + return counters; + } + + public static class Builder { + + private final String myId; + + private Long counter; + + public Builder(GossipManager gossipManager) { + myId = gossipManager.getMyself().getId(); + counter = 0L; + } + + public Builder increment(Long count) { + counter += count; + return this; + } + } +} diff --git a/src/main/java/org/tron/gossip/crdt/GrowOnlySet.java b/src/main/java/org/tron/gossip/crdt/GrowOnlySet.java new file mode 100755 index 00000000000..05d9976ee61 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/GrowOnlySet.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +public class GrowOnlySet implements CrdtSet, GrowOnlySet>{ + + private final Set hidden = new LinkedHashSet<>(); + + @SuppressWarnings("unused") + /* + * Used by SerDe + */ + private GrowOnlySet(){ + + } + + public GrowOnlySet(Set c){ + hidden.addAll(c); + } + + public GrowOnlySet(Collection c){ + hidden.addAll(c); + } + + public GrowOnlySet(GrowOnlySet first, GrowOnlySet second){ + hidden.addAll(first.value()); + hidden.addAll(second.value()); + } + + @Override + public GrowOnlySet merge(GrowOnlySet other) { + return new GrowOnlySet<>(this, other); + } + + @Override + public Set value() { + Set copy = new LinkedHashSet<>(); + copy.addAll(hidden); + return Collections.unmodifiableSet(copy); + } + + @Override + public GrowOnlySet optimize() { + return new GrowOnlySet<>(hidden); + } + + public int size() { + return hidden.size(); + } + + public boolean isEmpty() { + return hidden.isEmpty(); + } + + public boolean contains(Object o) { + return hidden.contains(o); + } + + public Iterator iterator() { + Set copy = new HashSet<>(); + copy.addAll(hidden); + return copy.iterator(); + } + + public Object[] toArray() { + return hidden.toArray(); + } + + public T[] toArray(T[] a) { + return hidden.toArray(a); + } + + public boolean add(ElementType e) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + public boolean containsAll(Collection c) { + return hidden.containsAll(c); + } + + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "GrowOnlySet [hidden=" + hidden + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((hidden == null) ? 0 : hidden.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("rawtypes") + GrowOnlySet other = (GrowOnlySet) obj; + if (hidden == null) { + if (other.hidden != null) + return false; + } else if (!hidden.equals(other.hidden)) + return false; + return true; + } + + Set getElements(){ + return hidden; + } +} diff --git a/src/main/java/org/tron/gossip/crdt/LwwSet.java b/src/main/java/org/tron/gossip/crdt/LwwSet.java new file mode 100755 index 00000000000..51d081a4a43 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/LwwSet.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import org.tron.gossip.manager.Clock; +import org.tron.gossip.manager.SystemClock; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/* + Last write wins CrdtSet + Each operation has timestamp: when you add or remove SystemClock is used to get current time in nanoseconds. + When all add/remove operations are within the only node LWWSet is guaranteed to work like a Set. + If you have multiple nodes with ideally synchronized clocks: + You will observe operations on all machines later than on the initiator, but the last operations on cluster will win. + If you have some significant clock drift you will suffer from data loss. + + Read more: https://github.com/aphyr/meangirls#lww-element-set + + You can view examples of usage in tests: + LwwSetTest - unit tests + DataTest - integration test with 2 nodes, LWWSet was serialized/deserialized, sent between nodes, merged +*/ + +public class LwwSet implements CrdtAddRemoveSet, LwwSet> { + static private Clock clock = new SystemClock(); + + private final Map struct; + + static class Timestamps { + private final long latestAdd; + private final long latestRemove; + + Timestamps(){ + latestAdd = 0; + latestRemove = 0; + } + + Timestamps(long add, long remove){ + latestAdd = add; + latestRemove = remove; + } + + long getLatestAdd(){ + return latestAdd; + } + + long getLatestRemove(){ + return latestRemove; + } + + // consider element present when addTime >= removeTime, so we prefer add to remove + boolean isPresent(){ + return latestAdd >= latestRemove; + } + + Timestamps updateAdd(){ + return new Timestamps(clock.nanoTime(), latestRemove); + } + + Timestamps updateRemove(){ + return new Timestamps(latestAdd, clock.nanoTime()); + } + + Timestamps merge(Timestamps other){ + if (other == null){ + return this; + } + return new Timestamps(Math.max(latestAdd, other.latestAdd), Math.max(latestRemove, other.latestRemove)); + } + } + + + public LwwSet(){ + struct = new HashMap<>(); + } + + @SafeVarargs + public LwwSet(ElementType... elements){ + this(new HashSet<>(Arrays.asList(elements))); + } + + public LwwSet(Set set){ + struct = new HashMap<>(); + for (ElementType e : set){ + struct.put(e, new Timestamps().updateAdd()); + } + } + + public LwwSet(LwwSet first, LwwSet second){ + Function timestampsFor = p -> { + Timestamps firstTs = first.struct.get(p); + Timestamps secondTs = second.struct.get(p); + if (firstTs == null){ + return secondTs; + } + return firstTs.merge(secondTs); + }; + struct = Stream.concat(first.struct.keySet().stream(), second.struct.keySet().stream()) + .distinct().collect(Collectors.toMap(p -> p, timestampsFor)); + } + + public LwwSet add(ElementType e){ + return this.merge(new LwwSet<>(e)); + } + + // for serialization + LwwSet(Map struct){ + this.struct = struct; + } + + Map getStruct(){ + return struct; + } + + + public LwwSet remove(ElementType e){ + Timestamps eTimestamps = struct.get(e); + if (eTimestamps == null || !eTimestamps.isPresent()){ + return this; + } + Map changeMap = new HashMap<>(); + changeMap.put(e, eTimestamps.updateRemove()); + return this.merge(new LwwSet<>(changeMap)); + } + + @Override + public LwwSet merge(LwwSet other){ + return new LwwSet<>(this, other); + } + + @Override + public Set value(){ + return struct.entrySet().stream() + .filter(entry -> entry.getValue().isPresent()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + @Override + public LwwSet optimize(){ + return this; + } + + @Override + public boolean equals(Object obj){ + return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((LwwSet) obj).value())); + } +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/crdt/MaxChangeSet.java b/src/main/java/org/tron/gossip/crdt/MaxChangeSet.java new file mode 100755 index 00000000000..0ec86c3c975 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/MaxChangeSet.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/* + Max Change Set CrdtSet. Value which has changed the most wins. + You cannot delete an element which is not present, and cannot add an element which is already present. + MC-sets are compact and do the right thing when changes to elements are infrequent compared to the gossiping period. + + Read more: https://github.com/aphyr/meangirls#max-change-sets + You can view examples of usage in tests: + MaxChangeSetTest - unit tests + DataTest - integration test with 2 nodes, MaxChangeSet was serialized/deserialized, sent between nodes, merged +*/ + +public class MaxChangeSet implements CrdtAddRemoveSet, MaxChangeSet> { + private final Map struct; + + public MaxChangeSet(){ + struct = new HashMap<>(); + } + + @SafeVarargs + public MaxChangeSet(ElementType... elements){ + this(new HashSet<>(Arrays.asList(elements))); + } + + public MaxChangeSet(Set set){ + struct = new HashMap<>(); + for (ElementType e : set){ + struct.put(e, 1); + } + } + + public MaxChangeSet(MaxChangeSet first, MaxChangeSet second){ + Function valueFor = element -> + Math.max(first.struct.getOrDefault(element, 0), second.struct.getOrDefault(element, 0)); + struct = Stream.concat(first.struct.keySet().stream(), second.struct.keySet().stream()) + .distinct().collect(Collectors.toMap(p -> p, valueFor)); + } + + // for serialization + MaxChangeSet(Map struct){ + this.struct = struct; + } + + Map getStruct(){ + return struct; + } + + private MaxChangeSet increment(ElementType e){ + Map changeMap = new HashMap<>(); + changeMap.put(e, struct.getOrDefault(e, 0) + 1); + return this.merge(new MaxChangeSet<>(changeMap)); + } + + public MaxChangeSet add(ElementType e){ + if (struct.getOrDefault(e, 0) % 2 == 1){ + return this; + } + return increment(e); + } + + public MaxChangeSet remove(ElementType e){ + if (struct.getOrDefault(e, 0) % 2 == 0){ + return this; + } + return increment(e); + } + + @Override + public MaxChangeSet merge(MaxChangeSet other){ + return new MaxChangeSet<>(this, other); + } + + @Override + public Set value(){ + return struct.entrySet().stream() + .filter(entry -> (entry.getValue() % 2 == 1)) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + @Override + public MaxChangeSet optimize(){ + return this; + } + + @Override + public boolean equals(Object obj){ + return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((MaxChangeSet) obj).value())); + } +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/crdt/OrSet.java b/src/main/java/org/tron/gossip/crdt/OrSet.java new file mode 100755 index 00000000000..e391edf1536 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/OrSet.java @@ -0,0 +1,307 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiConsumer; + +import org.tron.gossip.crdt.OrSet.Builder.Operation; + +/* + * A immutable set + */ +public class OrSet implements CrdtAddRemoveSet, OrSet> { + + private final Map> elements = new HashMap<>(); + private final Map> tombstones = new HashMap<>(); + private final transient Set val; + + public OrSet(){ + val = computeValue(); + } + + OrSet(Map> elements, Map> tombstones){ + this.elements.putAll(elements); + this.tombstones.putAll(tombstones); + val = computeValue(); + } + + @SafeVarargs + public OrSet(E ... elements){ + this(new HashSet<>(Arrays.asList(elements))); + } + + public OrSet(Set elements) { + for (E e: elements){ + internalAdd(e); + } + val = computeValue(); + } + + public OrSet(Builderbuilder){ + for (Builder.OrSetElement e: builder.elements){ + if (e.operation == Builder.Operation.ADD){ + internalAdd(e.element); + } else { + internalRemove(e.element); + } + } + val = computeValue(); + } + + /** + * This constructor is the way to remove elements from an existing set + * @param set + * @param builder + */ + public OrSet(OrSet set, Builder builder){ + elements.putAll(set.elements); + tombstones.putAll(set.tombstones); + for (Builder.OrSetElement e: builder.elements){ + if (e.operation == Builder.Operation.ADD){ + internalAdd(e.element); + } else { + internalRemove(e.element); + } + } + val = computeValue(); + } + + static Set mergeSets(Set a, Set b) { + if ((a == null || a.isEmpty()) && (b == null || b.isEmpty())) { + return null; + } + Set res = new HashSet<>(a); + res.addAll(b); + return res; + } + + private void internalSetMerge(Map> map, E key, Set value) { + if (value == null) { + return; + } + map.merge(key, value, OrSet::mergeSets); + } + + public OrSet(OrSet left, OrSet right){ + BiConsumer>, Map>> internalMerge = (items, other) -> { + for (Entry> l : other.entrySet()){ + internalSetMerge(items, l.getKey(), l.getValue()); + } + }; + + internalMerge.accept(elements, left.elements); + internalMerge.accept(elements, right.elements); + internalMerge.accept(tombstones, left.tombstones); + internalMerge.accept(tombstones, right.tombstones); + + val = computeValue(); + } + + public OrSet add(E e) { + return this.merge(new OrSet<>(e)); + } + + public OrSet remove(E e) { + return new OrSet<>(this, new Builder().remove(e)); + } + + public Builder builder(){ + return new Builder<>(); + } + + @Override + public OrSet merge(OrSet other) { + return new OrSet(this, other); + } + + private void internalAdd(E element) { + Set toMerge = new HashSet<>(); + toMerge.add(UUID.randomUUID()); + internalSetMerge(elements, element, toMerge); + } + + private void internalRemove(E element){ + internalSetMerge(tombstones, element, elements.get(element)); + } + + /* + * Computes the live values by analyzing the elements and tombstones + */ + private Set computeValue(){ + Set values = new HashSet<>(); + for (Entry> entry: elements.entrySet()){ + Set deleteIds = tombstones.get(entry.getKey()); + // if not all tokens for current element are in tombstones + if (deleteIds == null || !deleteIds.containsAll(entry.getValue())) { + values.add(entry.getKey()); + } + } + return values; + } + + @Override + public Set value() { + return val; + } + + @Override + public OrSet optimize() { + return this; + } + + public static class Builder { + public static enum Operation { + ADD, REMOVE + }; + + private class OrSetElement { + EL element; + Operation operation; + + private OrSetElement(EL element, Operation operation) { + this.element = element; + this.operation = operation; + } + } + + private List> elements = new ArrayList<>(); + + public Builder add(E element) { + elements.add(new OrSetElement(element, Operation.ADD)); + return this; + } + + public Builder remove(E element) { + elements.add(new OrSetElement(element, Operation.REMOVE)); + return this; + } + + public Builder mutate(E element, Operation operation) { + elements.add(new OrSetElement(element, operation)); + return this; + } + } + + + public int size() { + return value().size(); + } + + + public boolean isEmpty() { + return value().size() == 0; + } + + + public boolean contains(Object o) { + return value().contains(o); + } + + + public Iterator iterator() { + Iterator managed = value().iterator(); + return new Iterator() { + + @Override + public void remove() { + throw new IllegalArgumentException(); + } + + @Override + public boolean hasNext() { + return managed.hasNext(); + } + + @Override + public E next() { + return managed.next(); + } + + }; + } + + public Object[] toArray() { + return value().toArray(); + } + + public T[] toArray(T[] a) { + return value().toArray(a); + } + + public boolean containsAll(Collection c) { + return this.value().containsAll(c); + } + + public boolean addAll(Collection c) { + throw new IllegalArgumentException(); + } + + public boolean retainAll(Collection c) { + throw new IllegalArgumentException(); + } + + public boolean removeAll(Collection c) { + throw new IllegalArgumentException(); + } + + public void clear() { + throw new IllegalArgumentException(); + } + + @Override + public String toString() { + return "OrSet [elements=" + elements + ", tombstones=" + tombstones + "]" ; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value() == null) ? 0 : value().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + @SuppressWarnings("rawtypes") + OrSet other = (OrSet) obj; + if (elements == null) { + if (other.elements != null) + return false; + } else if (!value().equals(other.value())) + return false; + return true; + } + + Map> getElements() { + return elements; + } + + Map> getTombstones() { + return tombstones; + } + +} diff --git a/src/main/java/org/tron/gossip/crdt/PNCounter.java b/src/main/java/org/tron/gossip/crdt/PNCounter.java new file mode 100755 index 00000000000..863b528b584 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/PNCounter.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Map; + +import org.tron.gossip.manager.GossipManager; + +public class PNCounter implements CrdtCounter { + + private final GrowOnlyCounter pCount; + + private final GrowOnlyCounter nCount; + + PNCounter(Map pCounters, Map nCounters) { + pCount = new GrowOnlyCounter(pCounters); + nCount = new GrowOnlyCounter(nCounters); + } + + public PNCounter(PNCounter starter, Builder builder) { + GrowOnlyCounter.Builder pBuilder = builder.makeGrowOnlyCounterBuilder(builder.pCount()); + pCount = new GrowOnlyCounter(starter.pCount, pBuilder); + GrowOnlyCounter.Builder nBuilder = builder.makeGrowOnlyCounterBuilder(builder.nCount()); + nCount = new GrowOnlyCounter(starter.nCount, nBuilder); + } + + public PNCounter(Builder builder) { + GrowOnlyCounter.Builder pBuilder = builder.makeGrowOnlyCounterBuilder(builder.pCount()); + pCount = new GrowOnlyCounter(pBuilder); + GrowOnlyCounter.Builder nBuilder = builder.makeGrowOnlyCounterBuilder(builder.nCount()); + nCount = new GrowOnlyCounter(nBuilder); + } + + public PNCounter(GossipManager manager) { + pCount = new GrowOnlyCounter(manager); + nCount = new GrowOnlyCounter(manager); + } + + public PNCounter(PNCounter starter, PNCounter other) { + pCount = new GrowOnlyCounter(starter.pCount, other.pCount); + nCount = new GrowOnlyCounter(starter.nCount, other.nCount); + } + + @Override + public PNCounter merge(PNCounter other) { + return new PNCounter(this, other); + } + + @Override + public Long value() { + long pValue = (long) pCount.value(); + long nValue = (long) nCount.value(); + return pValue - nValue; + } + + @Override + public PNCounter optimize() { + return new PNCounter(pCount.getCounters(), nCount.getCounters()); + } + + @Override + public boolean equals(Object obj) { + if (getClass() != obj.getClass()) + return false; + PNCounter other = (PNCounter) obj; + return value().longValue() == other.value().longValue(); + } + + @Override + public String toString() { + return "PnCounter [pCount=" + pCount + ", nCount=" + nCount + ", value=" + value() + "]"; + } + + Map getPCounters() { + return pCount.getCounters(); + } + + Map getNCounters() { + return nCount.getCounters(); + } + + public static class Builder { + + private final GossipManager myManager; + + private long value = 0L; + + public Builder(GossipManager gossipManager) { + myManager = gossipManager; + } + + public long pCount() { + if (value > 0) { + return value; + } + return 0; + } + + public long nCount() { + if (value < 0) { + return -value; + } + return 0; + } + + public org.tron.gossip.crdt.GrowOnlyCounter.Builder makeGrowOnlyCounterBuilder(long value) { + org.tron.gossip.crdt.GrowOnlyCounter.Builder ret = new org.tron.gossip.crdt.GrowOnlyCounter.Builder( + myManager); + ret.increment(value); + return ret; + } + + public Builder increment(long delta) { + value += delta; + return this; + } + + public Builder decrement(long delta) { + value -= delta; + return this; + } + } + +} diff --git a/src/main/java/org/tron/gossip/crdt/TwoPhaseSet.java b/src/main/java/org/tron/gossip/crdt/TwoPhaseSet.java new file mode 100755 index 00000000000..226e88c0557 --- /dev/null +++ b/src/main/java/org/tron/gossip/crdt/TwoPhaseSet.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.crdt; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/* + Two-Phase CrdtSet. + You can add element only once and remove only once. + You cannot remove element which is not present. + + Read more: https://github.com/aphyr/meangirls#2p-set + You can view examples of usage in tests: + TwoPhaseSetTest - unit tests + DataTest - integration test with 2 nodes, TwoPhaseSet was serialized/deserialized, sent between nodes, merged +*/ + +public class TwoPhaseSet implements CrdtAddRemoveSet, TwoPhaseSet> { + private final Set added; + private final Set removed; + + public TwoPhaseSet(){ + added = new HashSet<>(); + removed = new HashSet<>(); + } + + @SafeVarargs + public TwoPhaseSet(ElementType... elements){ + this(new HashSet<>(Arrays.asList(elements))); + } + + public TwoPhaseSet(Set set){ + this(); + for (ElementType e : set){ + added.add(e); + } + } + + public TwoPhaseSet(TwoPhaseSet first, TwoPhaseSet second){ + BiFunction, Set, Set> mergeSets = (f, s) -> + Stream.concat(f.stream(), s.stream()).collect(Collectors.toSet()); + + added = mergeSets.apply(first.added, second.added); + removed = mergeSets.apply(first.removed, second.removed); + } + + TwoPhaseSet(Set added, Set removed){ + this.added = added; + this.removed = removed; + } + + Set getAdded(){ + return added; + } + + Set getRemoved(){ + return removed; + } + + public TwoPhaseSet add(ElementType e){ + if (removed.contains(e) || added.contains(e)){ + return this; + } + return this.merge(new TwoPhaseSet<>(e)); + } + + public TwoPhaseSet remove(ElementType e){ + if (removed.contains(e) || !added.contains(e)){ + return this; + } + Set eSet = new HashSet<>(Collections.singletonList(e)); + return this.merge(new TwoPhaseSet<>(eSet, eSet)); + } + + @Override + public TwoPhaseSet merge(TwoPhaseSet other){ + return new TwoPhaseSet<>(this, other); + } + + @Override + public Set value(){ + return added.stream().filter(e -> !removed.contains(e)).collect(Collectors.toSet()); + } + + @Override + public TwoPhaseSet optimize(){ + return new TwoPhaseSet<>(value(), removed); + } + + @Override + public boolean equals(Object obj){ + return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((TwoPhaseSet) obj).value())); + } +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/event/GossipListener.java b/src/main/java/org/tron/gossip/event/GossipListener.java new file mode 100755 index 00000000000..ebf11375824 --- /dev/null +++ b/src/main/java/org/tron/gossip/event/GossipListener.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event; + +import org.tron.gossip.Member; + +public interface GossipListener { + void gossipEvent(Member member, GossipState state); +} diff --git a/src/main/java/org/tron/gossip/event/GossipState.java b/src/main/java/org/tron/gossip/event/GossipState.java new file mode 100755 index 00000000000..60acdd69a26 --- /dev/null +++ b/src/main/java/org/tron/gossip/event/GossipState.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event; + +public enum GossipState { + UP("up"), DOWN("down"); + @SuppressWarnings("unused") + private final String state; + + private GossipState(String state) { + this.state = state; + } +} diff --git a/src/main/java/org/tron/gossip/event/data/DataEventConstants.java b/src/main/java/org/tron/gossip/event/data/DataEventConstants.java new file mode 100755 index 00000000000..cdefbb62224 --- /dev/null +++ b/src/main/java/org/tron/gossip/event/data/DataEventConstants.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event.data; + +public class DataEventConstants { + + // MetricRegistry + public static final String PER_NODE_DATA_SUBSCRIBERS_SIZE + = "gossip.event.data.pernode.subscribers.size"; + public static final String PER_NODE_DATA_SUBSCRIBERS_QUEUE_SIZE + = "gossip.event.data.pernode.subscribers.queue.size"; + public static final String SHARED_DATA_SUBSCRIBERS_SIZE + = "gossip.event.data.shared.subscribers.size"; + public static final String SHARED_DATA_SUBSCRIBERS_QUEUE_SIZE + = "gossip.event.data.shared.subscribers.queue.size"; + + // Thread pool + public static final int PER_NODE_DATA_QUEUE_SIZE = 64; + public static final int PER_NODE_DATA_CORE_POOL_SIZE = 1; + public static final int PER_NODE_DATA_MAX_POOL_SIZE = 30; + public static final int PER_NODE_DATA_KEEP_ALIVE_TIME_SECONDS = 1; + public static final int SHARED_DATA_QUEUE_SIZE = 64; + public static final int SHARED_DATA_CORE_POOL_SIZE = 1; + public static final int SHARED_DATA_MAX_POOL_SIZE = 30; + public static final int SHARED_DATA_KEEP_ALIVE_TIME_SECONDS = 1; + +} diff --git a/src/main/java/org/tron/gossip/event/data/DataEventManager.java b/src/main/java/org/tron/gossip/event/data/DataEventManager.java new file mode 100755 index 00000000000..4ed121eca43 --- /dev/null +++ b/src/main/java/org/tron/gossip/event/data/DataEventManager.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event.data; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class DataEventManager { + + private final List perNodeDataHandlers; + private final BlockingQueue perNodeDataHandlerQueue; + private final ExecutorService perNodeDataEventExecutor; + private final List sharedDataHandlers; + private final BlockingQueue sharedDataHandlerQueue; + private final ExecutorService sharedDataEventExecutor; + + public DataEventManager(MetricRegistry metrics) { + perNodeDataHandlers = new CopyOnWriteArrayList<>(); + perNodeDataHandlerQueue = new ArrayBlockingQueue<>(DataEventConstants.PER_NODE_DATA_QUEUE_SIZE); + perNodeDataEventExecutor = new ThreadPoolExecutor( + DataEventConstants.PER_NODE_DATA_CORE_POOL_SIZE, + DataEventConstants.PER_NODE_DATA_MAX_POOL_SIZE, + DataEventConstants.PER_NODE_DATA_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS, + perNodeDataHandlerQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + + sharedDataHandlers = new CopyOnWriteArrayList<>(); + sharedDataHandlerQueue = new ArrayBlockingQueue<>(DataEventConstants.SHARED_DATA_QUEUE_SIZE); + sharedDataEventExecutor = new ThreadPoolExecutor(DataEventConstants.SHARED_DATA_CORE_POOL_SIZE, + DataEventConstants.SHARED_DATA_MAX_POOL_SIZE, + DataEventConstants.SHARED_DATA_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS, + sharedDataHandlerQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + + metrics.register(DataEventConstants.PER_NODE_DATA_SUBSCRIBERS_SIZE, + (Gauge) () -> perNodeDataHandlers.size()); + metrics.register(DataEventConstants.PER_NODE_DATA_SUBSCRIBERS_QUEUE_SIZE, + (Gauge) () -> perNodeDataHandlerQueue.size()); + metrics.register(DataEventConstants.SHARED_DATA_SUBSCRIBERS_SIZE, + (Gauge) () -> sharedDataHandlers.size()); + metrics.register(DataEventConstants.SHARED_DATA_SUBSCRIBERS_QUEUE_SIZE, + (Gauge) () -> sharedDataHandlerQueue.size()); + + } + + public void notifySharedData(final String key, final Object newValue, final Object oldValue) { + sharedDataHandlers.forEach(handler -> sharedDataEventExecutor + .execute(() -> handler.onUpdate(key, oldValue, newValue))); + } + + public void notifyPerNodeData(final String nodeId, final String key, final Object newValue, + final Object oldValue) { + perNodeDataHandlers.forEach(handler -> perNodeDataEventExecutor + .execute(() -> handler.onUpdate(nodeId, key, oldValue, newValue))); + } + + public void registerPerNodeDataSubscriber(UpdateNodeDataEventHandler handler) { + perNodeDataHandlers.add(handler); + } + + public void unregisterPerNodeDataSubscriber(UpdateNodeDataEventHandler handler) { + perNodeDataHandlers.remove(handler); + } + + public int getPerNodeSubscribersSize() { + return perNodeDataHandlers.size(); + } + + public void registerSharedDataSubscriber(UpdateSharedDataEventHandler handler) { + sharedDataHandlers.add(handler); + } + + public void unregisterSharedDataSubscriber(UpdateSharedDataEventHandler handler) { + sharedDataHandlers.remove(handler); + } + + public int getSharedDataSubscribersSize() { + return sharedDataHandlers.size(); + } + +} diff --git a/src/main/java/org/tron/gossip/event/data/UpdateNodeDataEventHandler.java b/src/main/java/org/tron/gossip/event/data/UpdateNodeDataEventHandler.java new file mode 100755 index 00000000000..7001be9a23c --- /dev/null +++ b/src/main/java/org/tron/gossip/event/data/UpdateNodeDataEventHandler.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event.data; + +/** + * Event handler interface for the per node data items. + * Classes which implement this interface get notifications when per node data item get changed. + */ +public interface UpdateNodeDataEventHandler { + + /** + * This method get called when a per node datum get changed. + * + * @param nodeId id of the node that change the value + * @param key key of the datum + * @param oldValue previous value of the datum or null if the datum is discovered + * for the first time + * @param newValue updated value of the datum + */ + void onUpdate(String nodeId, String key, Object oldValue, Object newValue); + +} diff --git a/src/main/java/org/tron/gossip/event/data/UpdateSharedDataEventHandler.java b/src/main/java/org/tron/gossip/event/data/UpdateSharedDataEventHandler.java new file mode 100755 index 00000000000..71ef93b5908 --- /dev/null +++ b/src/main/java/org/tron/gossip/event/data/UpdateSharedDataEventHandler.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.event.data; + +/** + * Event handler interface for shared data items. + * Classes which implement this interface get notifications when shared data get changed. + */ +public interface UpdateSharedDataEventHandler { + /** + * This method get called when shared data get changed. + * + * @param key key of the shared data item + * @param oldValue previous value or null if the data is discovered for the first time + * @param newValue updated value of the data item + */ + void onUpdate(String key, Object oldValue, Object newValue); + +} diff --git a/src/main/java/org/tron/gossip/example/StandNode.java b/src/main/java/org/tron/gossip/example/StandNode.java new file mode 100644 index 00000000000..ae3720e4a66 --- /dev/null +++ b/src/main/java/org/tron/gossip/example/StandNode.java @@ -0,0 +1,146 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.tron.gossip.example; + +import org.tron.gossip.GossipSettings; +import org.tron.gossip.Member; +import org.tron.gossip.RemoteMember; +import org.tron.gossip.crdt.OrSet; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.manager.GossipManagerBuilder; +import org.tron.gossip.model.SharedDataMessage; +import org.tron.overlay.Net; +import org.tron.overlay.message.Message; +import org.tron.overlay.message.Type; + +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import static org.tron.core.Constant.TOPIC_BLOCK; +import static org.tron.core.Constant.TOPIC_TRANSACTION; + +public class StandNode implements Net { + public static final String INDEX_KEY_FOR_SET = "block"; + + // is the name of the cluster + private String cluster; + + // is a URI object containing IP/hostname and port to use on the default adapter on the + // node's machine + private String uri; + + // is a unique id for this node(you can use any string) + private String id; + + private GossipManager gossipManager = null; + + public StandNode(String cluster, String uri, String id) { + setCluster(cluster); + setUri(uri); + setId(id); + + initGossipManager(cluster, uri, id); + initGossipService(); + } + + public void initGossipManager(String cluster, String uri, String id) { + GossipSettings s = new GossipSettings(); + s.setWindowSize(1000); + s.setGossipInterval(100); + GossipManager gossipService = GossipManagerBuilder.newBuilder().cluster(cluster) + .uri(URI.create(uri)).id(id) + .gossipMembers(getGossipMembers()) + .gossipSettings(s).build(); + setGossipManager(gossipService); + } + + public void initGossipService() { + gossipManager.init(); + } + + public static void addData(String val, GossipManager gossipService) { + SharedDataMessage m = new SharedDataMessage(); + m.setExpireAt(Long.MAX_VALUE); + m.setKey(INDEX_KEY_FOR_SET); + m.setPayload(new OrSet(val)); + m.setTimestamp(System.currentTimeMillis()); + gossipService.merge(m); + } + + public static SharedDataMessage sharedNodeData(String key, String value) { + SharedDataMessage g = new SharedDataMessage(); + g.setExpireAt(Long.MAX_VALUE); + g.setKey(key); + g.setPayload(value); + g.setTimestamp(System.currentTimeMillis()); + return g; + } + + private List getGossipMembers() { + return Collections.singletonList(new RemoteMember(cluster, URI.create("udp://localhost:10000"), "0")); + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public GossipManager getGossipManager() { + return gossipManager; + } + + public void setGossipManager(GossipManager gossipManager) { + this.gossipManager = gossipManager; + } + + @Override + public void broadcast(Message message) { + String topic = ""; + String value = message.getMessage(); + + if (message.getType() == Type.BLOCK) { + topic = TOPIC_BLOCK; + } else if (message.getType() == Type.TRANSACTION) { + topic = TOPIC_TRANSACTION; + } + + getGossipManager().gossipSharedData(StandNode.sharedNodeData(topic, value)); + } + + @Override + public void deliver(Message message) { + + } +} diff --git a/src/main/java/org/tron/gossip/lock/LockManager.java b/src/main/java/org/tron/gossip/lock/LockManager.java new file mode 100755 index 00000000000..11f1ed3983f --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/LockManager.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import org.tron.gossip.Member; +import org.tron.gossip.lock.exceptions.VoteFailedException; +import org.tron.gossip.lock.vote.MajorityVote; +import org.tron.gossip.lock.vote.Vote; +import org.tron.gossip.lock.vote.VoteCandidate; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.SharedDataMessage; +import org.apache.log4j.Logger; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class LockManager { + + public static final Logger LOGGER = Logger.getLogger(LockManager.class); + + private final GossipManager gossipManager; + private final LockManagerSettings lockSettings; + private final ScheduledExecutorService voteService; + private final AtomicInteger numberOfNodes; + private final Set lockKeys; + // For MetricRegistry + public static final String LOCK_KEY_SET_SIZE = "gossip.lock.key_set_size"; + public static final String LOCK_TIME = "gossip.lock.time"; + private final Timer lockTimeMetric; + + public LockManager(GossipManager gossipManager, final LockManagerSettings lockManagerSettings, + MetricRegistry metrics) { + this.gossipManager = gossipManager; + this.lockSettings = lockManagerSettings; + this.numberOfNodes = new AtomicInteger(lockSettings.getNumberOfNodes()); + this.lockKeys = new CopyOnWriteArraySet<>(); + metrics.register(LOCK_KEY_SET_SIZE, (Gauge) lockKeys::size); + lockTimeMetric = metrics.timer(LOCK_TIME); + // Register listener for lock keys + gossipManager.registerSharedDataSubscriber((key, oldValue, newValue) -> { + if (key.contains("lock/")) { + lockKeys.add(key); + } + }); + voteService = Executors.newScheduledThreadPool(2); + voteService.scheduleAtFixedRate(this::updateVotes, 0, lockSettings.getVoteUpdateInterval(), + TimeUnit.MILLISECONDS); + } + + public void acquireSharedDataLock(String key) throws VoteFailedException { + final Timer.Context context = lockTimeMetric.time(); + gossipManager.merge(generateLockMessage(key)); + int deadlockDetectCount = 0; + while (true) { + SharedDataMessage message = gossipManager.findSharedGossipData(generateLockKey(key)); + if (message == null || !(message.getPayload() instanceof MajorityVote)) { + continue; + } + MajorityVote majorityVoteResult = (MajorityVote) message.getPayload(); + final Map voteCandidatesMap = majorityVoteResult.value(); + final Map voteResultMap = new HashMap<>(); + // Store the vote result for each vote candidate nodes + voteCandidatesMap.forEach((candidateId, voteCandidate) -> voteResultMap + .put(candidateId, isVoteSuccess(voteCandidate))); + + long passedCandidates = voteResultMap.values().stream().filter(aBoolean -> aBoolean).count(); + String myNodeId = gossipManager.getMyself().getId(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("NodeId=" + myNodeId + ", VoteMap=" + voteResultMap + ", WinnerCount=" + + passedCandidates); + } + // Check for possible dead lock when no candidates were won + if (passedCandidates == 0) { + if (isDeadLock(voteCandidatesMap)) { + deadlockDetectCount++; + // Testing for deadlock is not always correct, therefore test for continues deadlocks + if (deadlockDetectCount >= lockSettings.getDeadlockDetectionThreshold()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Deadlock detected from node " + myNodeId + ". VoteCandidatesMap=" + + voteCandidatesMap); + } + preventDeadLock(voteCandidatesMap); + } + } else { + deadlockDetectCount = 0; + } + } else if (passedCandidates == 1 && voteResultMap.containsKey(myNodeId)) { + context.stop(); + if (voteResultMap.get(myNodeId)) { + // There is one winner and that is my node, therefore break the while loop and continue + break; + } else { + throw new VoteFailedException("Node " + myNodeId + " failed to lock on key: " + key); + } + } else if (passedCandidates > 1) { + // Multiple winners are not possible + context.stop(); + throw new IllegalStateException("Multiple nodes get voted."); + } + + try { + Thread.sleep(lockSettings.getResultCalculationDelay()); + } catch (InterruptedException e) { + throw new VoteFailedException("Node " + myNodeId + " failed to lock on key: " + key, e); + } + } + } + + // Generate Crdt lock message for voting + private SharedDataMessage generateLockMessage(String key) { + VoteCandidate voteCandidate = new VoteCandidate(gossipManager.getMyself().getId(), key, + new ConcurrentHashMap<>()); + voteCandidate.addVote(new Vote(gossipManager.getMyself().getId(), true, false, + gossipManager.getLiveMembers().stream().map(Member::getId).collect(Collectors.toList()), + gossipManager.getDeadMembers().stream().map(Member::getId) + .collect(Collectors.toList()))); + Map voteCandidateMap = new ConcurrentHashMap<>(); + voteCandidateMap.put(voteCandidate.getCandidateNodeId(), voteCandidate); + MajorityVote majorityVote = new MajorityVote(voteCandidateMap); + SharedDataMessage lockMessage = new SharedDataMessage(); + lockMessage.setKey(generateLockKey(key)); + lockMessage.setPayload(majorityVote); + lockMessage.setExpireAt(Long.MAX_VALUE); + lockMessage.setTimestamp(System.currentTimeMillis()); + return lockMessage; + } + + // This method will run periodically to vote the other nodes + private void updateVotes() { + for (String lockKey : lockKeys) { + SharedDataMessage message = gossipManager.findSharedGossipData(lockKey); + if (message == null || !(message.getPayload() instanceof MajorityVote)) { + continue; + } + MajorityVote majorityVote = (MajorityVote) message.getPayload(); + Map voteCandidateMap = majorityVote.value(); + String myNodeId = gossipManager.getMyself().getId(); + // No need to vote if my node is already voted to every node for the key + if (isVotedToAll(myNodeId, voteCandidateMap)) { + continue; + } + String myVoteCandidate = getVotedCandidateNodeId(myNodeId, voteCandidateMap); + + if (myVoteCandidate == null) { + myVoteCandidate = lockSettings.getVoteSelector().getVoteCandidateId(voteCandidateMap.keySet()); + } + for (VoteCandidate voteCandidate : voteCandidateMap.values()) { + if (voteCandidate.getCandidateNodeId().equals(myNodeId)) { + continue; + } + // Vote for selected candidate + boolean voteResult = voteCandidate.getCandidateNodeId().equals(myVoteCandidate); + voteCandidate.addVote(new Vote(gossipManager.getMyself().getId(), voteResult, false, + gossipManager.getLiveMembers().stream().map(Member::getId) + .collect(Collectors.toList()), + gossipManager.getDeadMembers().stream().map(Member::getId) + .collect(Collectors.toList()))); + } + } + } + + // Return true if every node has a vote from given node id. + private boolean isVotedToAll(String nodeId, final Map voteCandidates) { + int voteCount = 0; + for (VoteCandidate voteCandidate : voteCandidates.values()) { + if (voteCandidate.getVotes().containsKey(nodeId)) { + voteCount++; + } + } + return voteCount == voteCandidates.size(); + } + + // Returns true if there is a deadlock for given vote candidates + private boolean isDeadLock(final Map voteCandidates) { + boolean result = true; + int numberOfLiveNodes; + if (numberOfNodes.get() > 0) { + numberOfLiveNodes = numberOfNodes.get(); + } else { + // numberOfNodes is not set by the user, therefore calculate it. + Set liveNodes = voteCandidates.values().stream() + .map(voteCandidate -> voteCandidate.getVotes().values()).flatMap(Collection::stream) + .map(Vote::getLiveMembers).flatMap(List::stream).collect(Collectors.toSet()); + numberOfLiveNodes = liveNodes.size(); + } + for (VoteCandidate voteCandidate : voteCandidates.values()) { + result = result && voteCandidate.getVotes().size() == numberOfLiveNodes; + } + return result; + } + + // Prevent the deadlock by giving up the votes + private void preventDeadLock(Map voteCandidates) { + String myNodeId = gossipManager.getMyself().getId(); + VoteCandidate myResults = voteCandidates.get(myNodeId); + if (myResults == null) { + return; + } + // Set of nodes that is going to receive this nodes votes + List donateCandidateIds = voteCandidates.keySet().stream() + .filter(s -> s.compareTo(myNodeId) < 0).collect(Collectors.toList()); + if (donateCandidateIds.size() == 0) { + return; + } + // Select a random node to donate + Random randomizer = new Random(); + String selectedCandidateId = donateCandidateIds + .get(randomizer.nextInt(donateCandidateIds.size())); + VoteCandidate selectedCandidate = voteCandidates.get(selectedCandidateId); + + Set myVotes = new HashSet<>(myResults.getVotes().values()); + Set selectedCandidateVotes = new HashSet<>(selectedCandidate.getVotes().values()); + // Exchange the votes + for (Vote myVote : myVotes) { + for (Vote candidateVote : selectedCandidateVotes) { + if (myVote.getVoteValue() && myVote.getVotingNode().equals(candidateVote.getVotingNode())) { + myVote.setVoteExchange(true); + candidateVote.setVoteExchange(true); + selectedCandidate.getVotes().put(myVote.getVotingNode(), myVote); + myResults.getVotes().put(candidateVote.getVotingNode(), candidateVote); + } + } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Node " + myNodeId + " give up votes to node " + selectedCandidateId); + } + } + + private String getVotedCandidateNodeId(String nodeId, + final Map voteCandidates) { + for (VoteCandidate voteCandidate : voteCandidates.values()) { + Vote vote = voteCandidate.getVotes().get(nodeId); + if (vote != null && vote.getVoteValue()) { + return voteCandidate.getCandidateNodeId(); + } + } + return null; + } + + // Return true if the given candidate has passed the vote + private boolean isVoteSuccess(VoteCandidate voteCandidate) { + Set liveNodes = new HashSet<>(); + int voteCount = 0; + for (Vote vote : voteCandidate.getVotes().values()) { + liveNodes.addAll(vote.getLiveMembers()); + if (vote.getVoteValue()) { + voteCount++; + } + } + int numberOfLiveNodes; + if (numberOfNodes.get() > 0) { + numberOfLiveNodes = numberOfNodes.get(); + } else { + numberOfLiveNodes = liveNodes.size(); + } + return numberOfLiveNodes > 0 && voteCount >= (numberOfLiveNodes / 2 + 1); + } + + private String generateLockKey(String key){ + return "lock/" + key; + } + + public void shutdown(){ + voteService.shutdown(); + } + /** + * Get the voted node id from this node for a given key + * @param key key of the data object + * @return Voted node id + */ + public String getVotedCandidateNodeId(String key) { + SharedDataMessage message = gossipManager.findSharedGossipData(generateLockKey(key)); + if (message == null || !(message.getPayload() instanceof MajorityVote)) { + return null; + } + MajorityVote majorityVote = (MajorityVote) message.getPayload(); + return getVotedCandidateNodeId(gossipManager.getMyself().getId(), majorityVote.value()); + } + + /** + * Set the number of live nodes. If this value is negative, live nodes will be calculated + * @param numberOfNodes live node count or negative to calculate. + */ + public void setNumberOfNodes(int numberOfNodes) { + this.numberOfNodes.set(numberOfNodes); + } + +} diff --git a/src/main/java/org/tron/gossip/lock/LockManagerSettings.java b/src/main/java/org/tron/gossip/lock/LockManagerSettings.java new file mode 100755 index 00000000000..af7820bc5e6 --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/LockManagerSettings.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock; + +import org.tron.gossip.lock.vote.RandomVoteSelector; +import org.tron.gossip.lock.vote.VoteSelector; + +/** + * Stores the lock manager related settings. + */ +public class LockManagerSettings { + // Time between vote updates in ms. Default is 1 second. + private final int voteUpdateInterval; + // Vote selection algorithm. Default is random voting + private final VoteSelector voteSelector; + // Number of nodes available for voting. Default is -1 (Auto calculate) + private final int numberOfNodes; + // Number of times to test for deadlock before preventing. Default is 3 + private final int deadlockDetectionThreshold; + // Wait time between vote result calculation. Default is 1000 + private final int resultCalculationDelay; + + /** + * Construct LockManagerSettings with default settings. + */ + public static LockManagerSettings getLockManagerDefaultSettings() { + return new LockManagerSettings(1000, new RandomVoteSelector(), -1, 3, 1000); + } + + /** + * Construct a custom LockManagerSettings + * + * @param voteUpdateInterval Time between vote updates in milliseconds. + * @param voteSelector Vote selection algorithm. Cannot be null + * @param numberOfNodes Number of nodes available for voting. Set to negative value for auto calculate + * @param deadlockDetectionThreshold Number of times to test for deadlock before preventing + * @param resultCalculationDelay Wait time between vote result calculation + */ + public LockManagerSettings(int voteUpdateInterval, VoteSelector voteSelector, int numberOfNodes, + int deadlockDetectionThreshold, int resultCalculationDelay) { + this.voteUpdateInterval = voteUpdateInterval; + this.voteSelector = voteSelector; + this.numberOfNodes = numberOfNodes; + this.deadlockDetectionThreshold = deadlockDetectionThreshold; + this.resultCalculationDelay = resultCalculationDelay; + + } + + public int getVoteUpdateInterval() { + return voteUpdateInterval; + } + + public VoteSelector getVoteSelector() { + return voteSelector; + } + + public int getNumberOfNodes() { + return numberOfNodes; + } + + public int getDeadlockDetectionThreshold() { + return deadlockDetectionThreshold; + } + + public int getResultCalculationDelay() { + return resultCalculationDelay; + } +} diff --git a/src/main/java/org/tron/gossip/lock/exceptions/VoteFailedException.java b/src/main/java/org/tron/gossip/lock/exceptions/VoteFailedException.java new file mode 100755 index 00000000000..9ba6642cc2c --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/exceptions/VoteFailedException.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.exceptions; + +/** + * This exception is thrown when the lock based voting is failed. + */ +public class VoteFailedException extends Exception { + /** + * Constructs a new VoteFailedException with the specified detail message. + * + * @param message the detail message. + */ + public VoteFailedException(String message) { + super(message); + } + + /** + * Constructs a new VoteFailedException with the specified detail message and + * cause. + * + * @param message the detail message + * @param cause the cause for this exception + */ + public VoteFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/tron/gossip/lock/vote/MajorityVote.java b/src/main/java/org/tron/gossip/lock/vote/MajorityVote.java new file mode 100755 index 00000000000..7e437ba1f7a --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/vote/MajorityVote.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.vote; + +import org.tron.gossip.crdt.Crdt; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * CRDT which used for distribute a votes for a given key. + */ +public class MajorityVote implements Crdt, MajorityVote> { + + private final Map voteCandidates = new ConcurrentHashMap<>(); + + public MajorityVote(Map voteCandidateMap) { + voteCandidates.putAll(voteCandidateMap); + } + + @Override + public MajorityVote merge(MajorityVote other) { + Map mergedCandidates = new ConcurrentHashMap<>(); + Set firstKeySet = this.voteCandidates.keySet(); + Set secondKeySet = other.voteCandidates.keySet(); + Set sameCandidatesSet = getIntersection(firstKeySet, secondKeySet); + Set differentCandidatesSet = getIntersectionCompliment(firstKeySet, secondKeySet); + // Merge different vote candidates by combining votes + for (String differentCandidateId : differentCandidatesSet) { + if (this.voteCandidates.containsKey(differentCandidateId)) { + mergedCandidates.put(differentCandidateId, this.voteCandidates.get(differentCandidateId)); + } else if (other.voteCandidates.containsKey(differentCandidateId)) { + mergedCandidates.put(differentCandidateId, other.voteCandidates.get(differentCandidateId)); + } + } + // Merge votes for the same candidate + for (String sameCandidateId : sameCandidatesSet) { + if (this.voteCandidates.containsKey(sameCandidateId) && other.voteCandidates + .containsKey(sameCandidateId)) { + mergedCandidates.put(sameCandidateId, + mergeCandidate(this.voteCandidates.get(sameCandidateId), + other.voteCandidates.get(sameCandidateId))); + } + } + + return new MajorityVote(mergedCandidates); + } + + // Merge different votes for same candidate + private VoteCandidate mergeCandidate(VoteCandidate firstCandidate, + VoteCandidate secondCandidate) { + VoteCandidate mergeResult = new VoteCandidate(firstCandidate.getCandidateNodeId(), + firstCandidate.getVotingKey(), new ConcurrentHashMap<>()); + Set firstKeySet = firstCandidate.getVotes().keySet(); + Set secondKeySet = secondCandidate.getVotes().keySet(); + Set sameVoteNodeSet = getIntersection(firstKeySet, secondKeySet); + Set differentVoteNodeSet = getIntersectionCompliment(firstKeySet, secondKeySet); + // Merge different voters by combining their votes + for (String differentCandidateId : differentVoteNodeSet) { + if (firstCandidate.getVotes().containsKey(differentCandidateId)) { + mergeResult.getVotes() + .put(differentCandidateId, firstCandidate.getVotes().get(differentCandidateId)); + } else if (secondCandidate.getVotes().containsKey(differentCandidateId)) { + mergeResult.getVotes() + .put(differentCandidateId, secondCandidate.getVotes().get(differentCandidateId)); + } + } + // Merge vote for same voter + for (String sameVoteNodeId : sameVoteNodeSet) { + if (firstCandidate.getVotes().containsKey(sameVoteNodeId) && secondCandidate.getVotes() + .containsKey(sameVoteNodeId)) { + mergeResult.getVotes().put(sameVoteNodeId, + mergeVote(firstCandidate.getVotes().get(sameVoteNodeId), + secondCandidate.getVotes().get(sameVoteNodeId))); + } + } + + return mergeResult; + } + + // Merge two votes from same voter + private Vote mergeVote(Vote firstVote, Vote secondVote) { + if (firstVote.getVoteValue().booleanValue() != secondVote.getVoteValue().booleanValue()) { + if (firstVote.getVoteExchange()) { + return firstVote; + } else if (secondVote.getVoteExchange()) { + return secondVote; + } else { + return secondVote; + } + } else { + return secondVote; + } + } + + private Set getIntersection(Set first, Set second) { + Set intersection = new HashSet<>(first); + intersection.retainAll(second); + return intersection; + } + + private Set getIntersectionCompliment(Set first, Set second) { + Set union = new HashSet<>(); + union.addAll(first); + union.addAll(second); + Set intersectionCompliment = new HashSet<>(union); + intersectionCompliment.removeAll(getIntersection(first, second)); + return intersectionCompliment; + } + + @Override + public Map value() { + Map copy = new ConcurrentHashMap<>(); + copy.putAll(voteCandidates); + return Collections.unmodifiableMap(copy); + + } + + @Override + public int hashCode() { + return voteCandidates.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (!(obj instanceof MajorityVote)) + return false; + MajorityVote other = (MajorityVote) obj; + return Objects.equals(voteCandidates, other.voteCandidates); + } + + @Override + public String toString() { + return voteCandidates.toString(); + } + + @Override + public MajorityVote optimize() { + return new MajorityVote(voteCandidates); + } + + public Map getVoteCandidates() { + return new ConcurrentHashMap<>(voteCandidates); + } + +} diff --git a/src/main/java/org/tron/gossip/lock/vote/RandomVoteSelector.java b/src/main/java/org/tron/gossip/lock/vote/RandomVoteSelector.java new file mode 100755 index 00000000000..0d9cc557e38 --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/vote/RandomVoteSelector.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.vote; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +/** + * VoteSelector implementation which randomly select a voting node. + */ +public class RandomVoteSelector implements VoteSelector { + + @Override + public String getVoteCandidateId(Set voteCandidateIds) { + List voteCandidatesIds = new ArrayList<>(voteCandidateIds); + return voteCandidatesIds.get(new Random().nextInt(voteCandidatesIds.size())); + } +} diff --git a/src/main/java/org/tron/gossip/lock/vote/Vote.java b/src/main/java/org/tron/gossip/lock/vote/Vote.java new file mode 100755 index 00000000000..9a5955c0865 --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/vote/Vote.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.vote; + +import java.util.List; + +/** + * Store a voter details. + */ +public class Vote { + private final String votingNode; + private final Boolean voteValue; // TODO: 7/16/17 weight? + private Boolean voteExchange; + private final List liveMembers; + private final List deadMembers; + + public Vote(String votingNode, Boolean voteValue, Boolean voteExchange, List liveMembers, + List deadMembers) { + this.votingNode = votingNode; + this.voteValue = voteValue; + this.voteExchange = voteExchange; + this.liveMembers = liveMembers; + this.deadMembers = deadMembers; + } + + public String getVotingNode() { + return votingNode; + } + + public Boolean getVoteValue() { + return voteValue; + } + + public Boolean getVoteExchange() { + return voteExchange; + } + + public void setVoteExchange(Boolean voteExchange) { + this.voteExchange = voteExchange; + } + + public List getLiveMembers() { + return liveMembers; + } + + public List getDeadMembers() { + return deadMembers; + } + + @Override + public String toString() { + return "votingNode=" + votingNode + ", voteValue=" + voteValue + ", liveMembers=" + liveMembers + + ", deadMembers= " + deadMembers; + } +} diff --git a/src/main/java/org/tron/gossip/lock/vote/VoteCandidate.java b/src/main/java/org/tron/gossip/lock/vote/VoteCandidate.java new file mode 100755 index 00000000000..7037d6b0991 --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/vote/VoteCandidate.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.vote; + +import java.util.Map; +import java.util.Objects; + +/** + * Stores the vote candidate details and its votes. + */ +public class VoteCandidate { + + private final String candidateNodeId; + private final String votingKey; + private final Map votes; + + public VoteCandidate(String candidateNodeId, String votingKey, Map votes) { + + this.candidateNodeId = candidateNodeId; + this.votingKey = votingKey; + this.votes = votes; + } + + public String getCandidateNodeId() { + return candidateNodeId; + } + + public String getVotingKey() { + return votingKey; + } + + public Map getVotes() { + return votes; + } + + public void addVote(Vote vote) { + votes.put(vote.getVotingNode(), vote); + } + + @Override + public int hashCode() { + return Objects.hash(candidateNodeId, votingKey); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof VoteCandidate)) + return false; + if (obj == this) + return true; + VoteCandidate other = (VoteCandidate) obj; + return this.candidateNodeId.equals(other.candidateNodeId) && this.votingKey + .equals(other.votingKey); + } + + @Override + public String toString() { + return "candidateNodeId=" + candidateNodeId + ", votingKey=" + votingKey + ", votes= " + votes; + } +} diff --git a/src/main/java/org/tron/gossip/lock/vote/VoteSelector.java b/src/main/java/org/tron/gossip/lock/vote/VoteSelector.java new file mode 100755 index 00000000000..e15440b54ca --- /dev/null +++ b/src/main/java/org/tron/gossip/lock/vote/VoteSelector.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.lock.vote; + +import java.util.Set; + +/** + * This interface defines vote selection algorithm for the vote based locking. + */ +public interface VoteSelector { + /** + * This method get call by the lock manager of a node to decide which candidate need to be choose for voting. + * + * @param voteCandidateIds node id set for the vote candidates + * @return selected node id to vote from the given vote candidate set. + */ + String getVoteCandidateId(Set voteCandidateIds); +} diff --git a/src/main/java/org/tron/gossip/manager/AbstractActiveGossiper.java b/src/main/java/org/tron/gossip/manager/AbstractActiveGossiper.java new file mode 100755 index 00000000000..0a847d097f3 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/AbstractActiveGossiper.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import java.util.Map.Entry; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import org.slf4j.LoggerFactory; +import org.tron.gossip.GossipSettings; +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.ActiveGossipOk; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.Member; +import org.tron.gossip.model.Response; +import org.tron.gossip.model.SharedDataMessage; +import org.tron.gossip.model.ShutdownMessage; +import org.tron.gossip.udp.*; +import org.apache.log4j.Logger; + +import static com.codahale.metrics.MetricRegistry.name; + +/** + * The ActiveGossipThread sends information. Pick a random partner and send the membership list to that partner + */ +public abstract class AbstractActiveGossiper { + + protected static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("AbstractActiveGossiper"); + + protected final GossipManager gossipManager; + protected final GossipCore gossipCore; + private final Histogram sharedDataHistogram; + private final Histogram sendPerNodeDataHistogram; + private final Histogram sendMembershipHistogram; + private final Random random; + private final GossipSettings gossipSettings; + + public AbstractActiveGossiper(GossipManager gossipManager, GossipCore gossipCore, MetricRegistry registry) { + this.gossipManager = gossipManager; + this.gossipCore = gossipCore; + sharedDataHistogram = registry.histogram(name(AbstractActiveGossiper.class, "sharedDataHistogram-time")); + sendPerNodeDataHistogram = registry.histogram(name(AbstractActiveGossiper.class, "sendPerNodeDataHistogram-time")); + sendMembershipHistogram = registry.histogram(name(AbstractActiveGossiper.class, "sendMembershipHistogram-time")); + random = new Random(); + gossipSettings = gossipManager.getSettings(); + } + + public void init() { + + } + + public void shutdown() { + + } + + public final void sendShutdownMessage(LocalMember me, LocalMember target){ + if (target == null){ + return; + } + ShutdownMessage m = new ShutdownMessage(); + m.setNodeId(me.getId()); + m.setShutdownAtNanos(gossipManager.getClock().nanoTime()); + gossipCore.sendOneWay(m, target.getUri()); + } + + public final void sendSharedData(LocalMember me, LocalMember member) { + if (member == null) { + return; + } + long startTime = System.currentTimeMillis(); + if (gossipSettings.isBulkTransfer()) { + sendSharedDataInBulkInternal(me, member); + } else { + sendSharedDataInternal(me, member); + } + sharedDataHistogram.update(System.currentTimeMillis() - startTime); + } + + /** Send shared data one entry at a time. */ + private void sendSharedDataInternal(LocalMember me, LocalMember member) { + for (Entry innerEntry : gossipCore.getSharedData().entrySet()){ + if (innerEntry.getValue().getReplicable() != null && !innerEntry.getValue().getReplicable() + .shouldReplicate(me, member, innerEntry.getValue())) { + continue; + } + UdpSharedDataMessage message = new UdpSharedDataMessage(); + message.setUuid(UUID.randomUUID().toString()); + message.setUriFrom(me.getId()); + copySharedDataMessage(innerEntry.getValue(), message); + gossipCore.sendOneWay(message, member.getUri()); + } + } + + /** Send shared data by batching together several entries. */ + private void sendSharedDataInBulkInternal(LocalMember me, LocalMember member) { + UdpSharedDataBulkMessage udpMessage = new UdpSharedDataBulkMessage(); + udpMessage.setUuid(UUID.randomUUID().toString()); + udpMessage.setUriFrom(me.getId()); + for (Entry innerEntry : gossipCore.getSharedData().entrySet()) { + if (innerEntry.getValue().getReplicable() != null && !innerEntry.getValue().getReplicable() + .shouldReplicate(me, member, innerEntry.getValue())) { + continue; + } + SharedDataMessage message = new SharedDataMessage(); + copySharedDataMessage(innerEntry.getValue(), message); + udpMessage.addMessage(message); + if (udpMessage.getMessages().size() == gossipSettings.getBulkTransferSize()) { + gossipCore.sendOneWay(udpMessage, member.getUri()); + udpMessage = new UdpSharedDataBulkMessage(); + udpMessage.setUuid(UUID.randomUUID().toString()); + udpMessage.setUriFrom(me.getId()); + } + } + if (udpMessage.getMessages().size() > 0) { + gossipCore.sendOneWay(udpMessage, member.getUri()); + } + } + + private void copySharedDataMessage(SharedDataMessage original, SharedDataMessage copy) { + copy.setExpireAt(original.getExpireAt()); + copy.setKey(original.getKey()); + copy.setNodeId(original.getNodeId()); + copy.setTimestamp(original.getTimestamp()); + copy.setPayload(original.getPayload()); + copy.setReplicable(original.getReplicable()); + } + + public final void sendPerNodeData(LocalMember me, LocalMember member){ + if (member == null){ + return; + } + long startTime = System.currentTimeMillis(); + if (gossipSettings.isBulkTransfer()) { + sendPerNodeDataInBulkInternal(me, member); + } else { + sendPerNodeDataInternal(me, member); + } + sendPerNodeDataHistogram.update(System.currentTimeMillis() - startTime); + } + + /** Send per node data one entry at a time. */ + private void sendPerNodeDataInternal(LocalMember me, LocalMember member) { + for (Entry> entry : gossipCore.getPerNodeData().entrySet()){ + for (Entry innerEntry : entry.getValue().entrySet()){ + if (innerEntry.getValue().getReplicable() != null && !innerEntry.getValue().getReplicable() + .shouldReplicate(me, member, innerEntry.getValue())) { + continue; + } + UdpPerNodeDataMessage message = new UdpPerNodeDataMessage(); + message.setUuid(UUID.randomUUID().toString()); + message.setUriFrom(me.getId()); + copyPerNodeDataMessage(innerEntry.getValue(), message); + gossipCore.sendOneWay(message, member.getUri()); + } + } + + } + + /** Send per node data by batching together several entries. */ + private void sendPerNodeDataInBulkInternal(LocalMember me, LocalMember member) { + for (Entry> entry : gossipCore.getPerNodeData().entrySet()){ + UdpPerNodeDataBulkMessage udpMessage = new UdpPerNodeDataBulkMessage(); + udpMessage.setUuid(UUID.randomUUID().toString()); + udpMessage.setUriFrom(me.getId()); + for (Entry innerEntry : entry.getValue().entrySet()){ + if (innerEntry.getValue().getReplicable() != null && !innerEntry.getValue().getReplicable() + .shouldReplicate(me, member, innerEntry.getValue())) { + continue; + } + PerNodeDataMessage message = new PerNodeDataMessage(); + copyPerNodeDataMessage(innerEntry.getValue(), message); + udpMessage.addMessage(message); + if (udpMessage.getMessages().size() == gossipSettings.getBulkTransferSize()) { + gossipCore.sendOneWay(udpMessage, member.getUri()); + udpMessage = new UdpPerNodeDataBulkMessage(); + udpMessage.setUuid(UUID.randomUUID().toString()); + udpMessage.setUriFrom(me.getId()); + } + } + if (udpMessage.getMessages().size() > 0) { + gossipCore.sendOneWay(udpMessage, member.getUri()); + } + } + } + + private void copyPerNodeDataMessage(PerNodeDataMessage original, PerNodeDataMessage copy) { + copy.setExpireAt(original.getExpireAt()); + copy.setKey(original.getKey()); + copy.setNodeId(original.getNodeId()); + copy.setTimestamp(original.getTimestamp()); + copy.setPayload(original.getPayload()); + copy.setReplicable(original.getReplicable()); + } + + /** + * Performs the sending of the membership list, after we have incremented our own heartbeat. + */ + protected void sendMembershipList(LocalMember me, LocalMember member) { + if (member == null){ + return; + } + long startTime = System.currentTimeMillis(); + me.setHeartbeat(System.nanoTime()); + UdpActiveGossipMessage message = new UdpActiveGossipMessage(); + message.setUriFrom(gossipManager.getMyself().getUri().toASCIIString()); + message.setUuid(UUID.randomUUID().toString()); + message.getMembers().add(convert(me)); + for (LocalMember other : gossipManager.getMembers().keySet()) { + message.getMembers().add(convert(other)); + } + Response r = gossipCore.send(message, member.getUri()); + if (r instanceof ActiveGossipOk){ + //maybe count metrics here + } else { + LOGGER.debug("Message " + message + " generated response " + r); + } + sendMembershipHistogram.update(System.currentTimeMillis() - startTime); + } + + protected final Member convert(LocalMember member){ + Member gm = new Member(); + gm.setCluster(member.getClusterName()); + gm.setHeartbeat(member.getHeartbeat()); + gm.setUri(member.getUri().toASCIIString()); + gm.setId(member.getId()); + gm.setProperties(member.getProperties()); + return gm; + } + + /** + * + * @param memberList + * An immutable list + * @return The chosen LocalGossipMember to gossip with. + */ + protected LocalMember selectPartner(List memberList) { + LocalMember member = null; + if (memberList.size() > 0) { + int randomNeighborIndex = random.nextInt(memberList.size()); + member = memberList.get(randomNeighborIndex); + } + return member; + } +} diff --git a/src/main/java/org/tron/gossip/manager/Clock.java b/src/main/java/org/tron/gossip/manager/Clock.java new file mode 100755 index 00000000000..eb04272791f --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/Clock.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +public interface Clock { + + long currentTimeMillis(); + long nanoTime(); + +} diff --git a/src/main/java/org/tron/gossip/manager/DataReaper.java b/src/main/java/org/tron/gossip/manager/DataReaper.java new file mode 100755 index 00000000000..9a41a8a5ac9 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/DataReaper.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.SharedDataMessage; + +/** + * We wish to periodically sweep user data and remove entries past their timestamp. This + * implementation periodically sweeps through the data and removes old entries. While it might make + * sense to use a more specific high performance data-structure to handle eviction, keep in mind + * that we are not looking to store a large quantity of data as we currently have to transmit this + * data cluster wide. + */ +public class DataReaper { + + private final GossipCore gossipCore; + private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1); + private final Clock clock; + + public DataReaper(GossipCore gossipCore, Clock clock){ + this.gossipCore = gossipCore; + this.clock = clock; + } + + public void init(){ + Runnable reapPerNodeData = () -> { + runPerNodeOnce(); + runSharedOnce(); + }; + scheduledExecutor.scheduleAtFixedRate(reapPerNodeData, 0, 5, TimeUnit.SECONDS); + } + + void runSharedOnce(){ + for (Entry entry : gossipCore.getSharedData().entrySet()){ + if (entry.getValue().getExpireAt() < clock.currentTimeMillis()){ + gossipCore.getSharedData().remove(entry.getKey(), entry.getValue()); + } + } + } + + void runPerNodeOnce(){ + for (Entry> node : gossipCore.getPerNodeData().entrySet()){ + reapData(node.getValue()); + } + } + + void reapData(ConcurrentHashMap concurrentHashMap){ + for (Entry entry : concurrentHashMap.entrySet()){ + if (entry.getValue().getExpireAt() < clock.currentTimeMillis()){ + concurrentHashMap.remove(entry.getKey(), entry.getValue()); + } + } + } + + public void close(){ + scheduledExecutor.shutdown(); + try { + scheduledExecutor.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + + } + } +} diff --git a/src/main/java/org/tron/gossip/manager/DatacenterRackAwareActiveGossiper.java b/src/main/java/org/tron/gossip/manager/DatacenterRackAwareActiveGossiper.java new file mode 100755 index 00000000000..176a0e56d43 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/DatacenterRackAwareActiveGossiper.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.tron.gossip.LocalMember; + +import com.codahale.metrics.MetricRegistry; + +/** + * Sends gossip traffic at different rates to other racks and data-centers. + * This implementation controls the rate at which gossip traffic is shared. + * There are two constructs Datacenter and Rack. It is assumed that bandwidth and latency is higher + * in the rack than in the the datacenter. We can adjust the rate at which we send messages to each group. + * + */ +public class DatacenterRackAwareActiveGossiper extends AbstractActiveGossiper { + + public static final String DATACENTER = "datacenter"; + public static final String RACK = "rack"; + + private int sameRackGossipIntervalMs = 100; + private int sameDcGossipIntervalMs = 500; + private int differentDatacenterGossipIntervalMs = 1000; + private int randomDeadMemberSendIntervalMs = 250; + + private ScheduledExecutorService scheduledExecutorService; + private final BlockingQueue workQueue; + private ThreadPoolExecutor threadService; + + public DatacenterRackAwareActiveGossiper(GossipManager gossipManager, GossipCore gossipCore, + MetricRegistry registry) { + super(gossipManager, gossipCore, registry); + scheduledExecutorService = Executors.newScheduledThreadPool(2); + workQueue = new ArrayBlockingQueue(1024); + threadService = new ThreadPoolExecutor(1, 30, 1, TimeUnit.SECONDS, workQueue, + new ThreadPoolExecutor.DiscardOldestPolicy()); + try { + sameRackGossipIntervalMs = Integer.parseInt(gossipManager.getSettings() + .getActiveGossipProperties().get("sameRackGossipIntervalMs")); + } catch (RuntimeException ex) { } + try { + sameDcGossipIntervalMs = Integer.parseInt(gossipManager.getSettings() + .getActiveGossipProperties().get("sameDcGossipIntervalMs")); + } catch (RuntimeException ex) { } + try { + differentDatacenterGossipIntervalMs = Integer.parseInt(gossipManager.getSettings() + .getActiveGossipProperties().get("differentDatacenterGossipIntervalMs")); + } catch (RuntimeException ex) { } + try { + randomDeadMemberSendIntervalMs = Integer.parseInt(gossipManager.getSettings() + .getActiveGossipProperties().get("randomDeadMemberSendIntervalMs")); + } catch (RuntimeException ex) { } + } + + @Override + public void init() { + super.init(); + //same rack + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sendToSameRackMember()), + 0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sendToSameRackMemberPerNode()), + 0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sendToSameRackShared()), + 0, sameRackGossipIntervalMs, TimeUnit.MILLISECONDS); + + //same dc different rack + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sameDcDiffernetRackMember()), + 0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sameDcDiffernetRackPerNode()), + 0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sameDcDiffernetRackShared()), + 0, sameDcGossipIntervalMs, TimeUnit.MILLISECONDS); + + //different dc + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> differentDcMember()), + 0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> differentDcPerNode()), + 0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS); + + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> differentDcShared()), + 0, differentDatacenterGossipIntervalMs, TimeUnit.MILLISECONDS); + + //the dead + scheduledExecutorService.scheduleAtFixedRate(() -> + threadService.execute(() -> sendToDeadMember()), + 0, randomDeadMemberSendIntervalMs, TimeUnit.MILLISECONDS); + + } + + private void sendToDeadMember() { + sendMembershipList(gossipManager.getMyself(), selectPartner(gossipManager.getDeadMembers())); + } + + private List differentDataCenter(){ + String myDc = gossipManager.getMyself().getProperties().get(DATACENTER); + String rack = gossipManager.getMyself().getProperties().get(RACK); + if (myDc == null|| rack == null){ + return Collections.emptyList(); + } + List notMyDc = new ArrayList(10); + for (LocalMember i : gossipManager.getLiveMembers()){ + if (!myDc.equals(i.getProperties().get(DATACENTER))){ + notMyDc.add(i); + } + } + return notMyDc; + } + + private List sameDatacenterDifferentRack(){ + String myDc = gossipManager.getMyself().getProperties().get(DATACENTER); + String rack = gossipManager.getMyself().getProperties().get(RACK); + if (myDc == null|| rack == null){ + return Collections.emptyList(); + } + List notMyDc = new ArrayList(10); + for (LocalMember i : gossipManager.getLiveMembers()){ + if (myDc.equals(i.getProperties().get(DATACENTER)) && !rack.equals(i.getProperties().get(RACK))){ + notMyDc.add(i); + } + } + return notMyDc; + } + + private List sameRackNodes(){ + String myDc = gossipManager.getMyself().getProperties().get(DATACENTER); + String rack = gossipManager.getMyself().getProperties().get(RACK); + if (myDc == null|| rack == null){ + return Collections.emptyList(); + } + List sameDcAndRack = new ArrayList(10); + for (LocalMember i : gossipManager.getLiveMembers()){ + if (myDc.equals(i.getProperties().get(DATACENTER)) + && rack.equals(i.getProperties().get(RACK))){ + sameDcAndRack.add(i); + } + } + return sameDcAndRack; + } + + private void sendToSameRackMember() { + LocalMember i = selectPartner(sameRackNodes()); + sendMembershipList(gossipManager.getMyself(), i); + } + + private void sendToSameRackMemberPerNode() { + sendPerNodeData(gossipManager.getMyself(), selectPartner(sameRackNodes())); + } + + private void sendToSameRackShared() { + sendSharedData(gossipManager.getMyself(), selectPartner(sameRackNodes())); + } + + private void differentDcMember() { + sendMembershipList(gossipManager.getMyself(), selectPartner(differentDataCenter())); + } + + private void differentDcPerNode() { + sendPerNodeData(gossipManager.getMyself(), selectPartner(differentDataCenter())); + } + + private void differentDcShared() { + sendSharedData(gossipManager.getMyself(), selectPartner(differentDataCenter())); + } + + private void sameDcDiffernetRackMember() { + sendMembershipList(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack())); + } + + private void sameDcDiffernetRackPerNode() { + sendPerNodeData(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack())); + } + + private void sameDcDiffernetRackShared() { + sendSharedData(gossipManager.getMyself(), selectPartner(sameDatacenterDifferentRack())); + } + + @Override + public void shutdown() { + super.shutdown(); + scheduledExecutorService.shutdown(); + try { + scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + sendShutdownMessage(); + threadService.shutdown(); + try { + threadService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + } + + /** + * sends an optimistic shutdown message to several clusters nodes + */ + protected void sendShutdownMessage(){ + List l = gossipManager.getLiveMembers(); + int sendTo = l.size() < 3 ? 1 : l.size() / 3; + for (int i = 0; i < sendTo; i++) { + threadService.execute(() -> sendShutdownMessage(gossipManager.getMyself(), selectPartner(l))); + } + } +} diff --git a/src/main/java/org/tron/gossip/manager/GossipCore.java b/src/main/java/org/tron/gossip/manager/GossipCore.java new file mode 100755 index 00000000000..9ccbd055d13 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/GossipCore.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import org.slf4j.LoggerFactory; +import org.tron.gossip.LocalMember; +import org.tron.gossip.Member; +import org.tron.gossip.RemoteMember; +import org.tron.gossip.crdt.Crdt; +import org.tron.gossip.event.GossipState; +import org.tron.gossip.event.data.DataEventManager; +import org.tron.gossip.event.data.UpdateNodeDataEventHandler; +import org.tron.gossip.event.data.UpdateSharedDataEventHandler; +import org.tron.gossip.model.Base; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.Response; +import org.tron.gossip.model.SharedDataMessage; +import org.tron.gossip.udp.Trackable; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.*; + +public class GossipCore implements GossipCoreConstants { + + class LatchAndBase { + private final CountDownLatch latch; + private volatile Base base; + + LatchAndBase(){ + latch = new CountDownLatch(1); + } + + } + + public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("GossipCore"); + + private final GossipManager gossipManager; + private ConcurrentHashMap requests; + private final ConcurrentHashMap> perNodeData; + private final ConcurrentHashMap sharedData; + private final Meter messageSerdeException; + private final Meter transmissionException; + private final Meter transmissionSuccess; + private final DataEventManager eventManager; + + public GossipCore(GossipManager manager, MetricRegistry metrics){ + this.gossipManager = manager; + requests = new ConcurrentHashMap<>(); + perNodeData = new ConcurrentHashMap<>(); + sharedData = new ConcurrentHashMap<>(); + eventManager = new DataEventManager(metrics); + metrics.register(PER_NODE_DATA_SIZE, (Gauge)() -> perNodeData.size()); + metrics.register(SHARED_DATA_SIZE, (Gauge)() -> sharedData.size()); + metrics.register(REQUEST_SIZE, (Gauge)() -> requests.size()); + messageSerdeException = metrics.meter(MESSAGE_SERDE_EXCEPTION); + transmissionException = metrics.meter(MESSAGE_TRANSMISSION_EXCEPTION); + transmissionSuccess = metrics.meter(MESSAGE_TRANSMISSION_SUCCESS); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void addSharedData(SharedDataMessage message) { + while (true){ + SharedDataMessage previous = sharedData.putIfAbsent(message.getKey(), message); + if (previous == null){ + eventManager.notifySharedData(message.getKey(), message.getPayload(), null); + return; + } + if (message.getPayload() instanceof Crdt){ + SharedDataMessage merged = new SharedDataMessage(); + merged.setExpireAt(message.getExpireAt()); + merged.setKey(message.getKey()); + merged.setNodeId(message.getNodeId()); + merged.setTimestamp(message.getTimestamp()); + Crdt mergedCrdt = ((Crdt) previous.getPayload()).merge((Crdt) message.getPayload()); + merged.setPayload(mergedCrdt); + boolean replaced = sharedData.replace(message.getKey(), previous, merged); + if (replaced){ + if(!merged.getPayload().equals(previous.getPayload())) { + eventManager + .notifySharedData(message.getKey(), merged.getPayload(), previous.getPayload()); + } + return; + } + } else { + if (previous.getTimestamp() < message.getTimestamp()){ + boolean result = sharedData.replace(message.getKey(), previous, message); + if (result){ + eventManager.notifySharedData(message.getKey(), message.getPayload(), previous.getPayload()); + return; + } + } else { + return; + } + } + } + } + + public void addPerNodeData(PerNodeDataMessage message){ + ConcurrentHashMap nodeMap = new ConcurrentHashMap<>(); + nodeMap.put(message.getKey(), message); + nodeMap = perNodeData.putIfAbsent(message.getNodeId(), nodeMap); + if (nodeMap != null){ + PerNodeDataMessage current = nodeMap.get(message.getKey()); + if (current == null){ + nodeMap.putIfAbsent(message.getKey(), message); + eventManager.notifyPerNodeData(message.getNodeId(), message.getKey(), message.getPayload(), null); + } else { + if (current.getTimestamp() < message.getTimestamp()){ + nodeMap.replace(message.getKey(), current, message); + eventManager.notifyPerNodeData(message.getNodeId(), message.getKey(), message.getPayload(), + current.getPayload()); + } + } + } else { + eventManager.notifyPerNodeData(message.getNodeId(), message.getKey(), message.getPayload(), null); + } + } + + public ConcurrentHashMap> getPerNodeData(){ + return perNodeData; + } + + public ConcurrentHashMap getSharedData() { + return sharedData; + } + + public void shutdown(){ + } + + public void receive(Base base) { + if (!gossipManager.getMessageHandler().invoke(this, gossipManager, base)) { + LOGGER.warn("received message can not be handled"); + } + } + + /** + * Sends a blocking message. + * todo: move functionality to TransportManager layer. + * @param message + * @param uri + * @throws RuntimeException if data can not be serialized or in transmission error + */ + private void sendInternal(Base message, URI uri) { + byte[] json_bytes; + try { + json_bytes = gossipManager.getProtocolManager().write(message); + } catch (IOException e) { + messageSerdeException.mark(); + throw new RuntimeException(e); + } + try { + gossipManager.getTransportManager().send(uri, json_bytes); + transmissionSuccess.mark(); + } catch (IOException e) { + transmissionException.mark(); + throw new RuntimeException(e); + } + } + + public Response send(Base message, URI uri){ + if (LOGGER.isDebugEnabled()){ + LOGGER.debug("Sending " + message); + LOGGER.debug("Current request queue " + requests); + } + + final Trackable t; + LatchAndBase latchAndBase = null; + if (message instanceof Trackable){ + t = (Trackable) message; + latchAndBase = new LatchAndBase(); + requests.put(t.getUuid() + "/" + t.getUriFrom(), latchAndBase); + } else { + t = null; + } + sendInternal(message, uri); + if (latchAndBase == null){ + return null; + } + + try { + boolean complete = latchAndBase.latch.await(1, TimeUnit.SECONDS); + if (complete){ + return (Response) latchAndBase.base; + } else{ + return null; + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + if (latchAndBase != null){ + requests.remove(t.getUuid() + "/" + t.getUriFrom()); + } + } + } + + /** + * Sends a message across the network while blocking. Catches and ignores IOException in transmission. Used + * when the protocol for the message is not to wait for a response + * @param message the message to send + * @param u the uri to send it to + */ + public void sendOneWay(Base message, URI u) { + try { + sendInternal(message, u); + } catch (RuntimeException ex) { + LOGGER.debug("Send one way failed", ex); + } + } + + public void handleResponse(String k, Base v) { + LatchAndBase latch = requests.get(k); + latch.base = v; + latch.latch.countDown(); + } + + /** + * Merge lists from remote members and update heartbeats + * + * @param senderMember + * @param remoteList + * + */ + public void mergeLists(RemoteMember senderMember, List remoteList) { + if (LOGGER.isDebugEnabled()){ + debugState(senderMember, remoteList); + } + for (LocalMember i : gossipManager.getDeadMembers()) { + if (i.getId().equals(senderMember.getId())) { + LOGGER.debug(gossipManager.getMyself() + " contacted by dead member " + senderMember.getUri()); + i.recordHeartbeat(senderMember.getHeartbeat()); + i.setHeartbeat(senderMember.getHeartbeat()); + //TODO consider forcing an UP here + } + } + for (Member remoteMember : remoteList) { + if (remoteMember.getId().equals(gossipManager.getMyself().getId())) { + continue; + } + LocalMember aNewMember = new LocalMember(remoteMember.getClusterName(), + remoteMember.getUri(), + remoteMember.getId(), + remoteMember.getHeartbeat(), + remoteMember.getProperties(), + gossipManager.getSettings().getWindowSize(), + gossipManager.getSettings().getMinimumSamples(), + gossipManager.getSettings().getDistribution()); + aNewMember.recordHeartbeat(remoteMember.getHeartbeat()); + Object result = gossipManager.getMembers().putIfAbsent(aNewMember, GossipState.UP); + if (result != null){ + for (Entry localMember : gossipManager.getMembers().entrySet()){ + if (localMember.getKey().getId().equals(remoteMember.getId())){ + localMember.getKey().recordHeartbeat(remoteMember.getHeartbeat()); + localMember.getKey().setHeartbeat(remoteMember.getHeartbeat()); + localMember.getKey().setProperties(remoteMember.getProperties()); + } + } + } + } + if (LOGGER.isDebugEnabled()){ + debugState(senderMember, remoteList); + } + } + + private void debugState(RemoteMember senderMember, + List remoteList){ + LOGGER.warn( + "-----------------------\n" + + "Me " + gossipManager.getMyself() + "\n" + + "Sender " + senderMember + "\n" + + "RemoteList " + remoteList + "\n" + + "Live " + gossipManager.getLiveMembers()+ "\n" + + "Dead " + gossipManager.getDeadMembers()+ "\n" + + "======================="); + } + + @SuppressWarnings("rawtypes") + public Crdt merge(SharedDataMessage message) { + for (;;){ + SharedDataMessage previous = sharedData.putIfAbsent(message.getKey(), message); + if (previous == null){ + return (Crdt) message.getPayload(); + } + SharedDataMessage copy = new SharedDataMessage(); + copy.setExpireAt(message.getExpireAt()); + copy.setKey(message.getKey()); + copy.setNodeId(message.getNodeId()); + copy.setTimestamp(message.getTimestamp()); + @SuppressWarnings("unchecked") + Crdt merged = ((Crdt) previous.getPayload()).merge((Crdt) message.getPayload()); + copy.setPayload(merged); + boolean replaced = sharedData.replace(message.getKey(), previous, copy); + if (replaced){ + return merged; + } + } + } + + void registerPerNodeDataSubscriber(UpdateNodeDataEventHandler handler){ + eventManager.registerPerNodeDataSubscriber(handler); + } + + void registerSharedDataSubscriber(UpdateSharedDataEventHandler handler){ + eventManager.registerSharedDataSubscriber(handler); + } + + void unregisterPerNodeDataSubscriber(UpdateNodeDataEventHandler handler){ + eventManager.unregisterPerNodeDataSubscriber(handler); + } + + void unregisterSharedDataSubscriber(UpdateSharedDataEventHandler handler){ + eventManager.unregisterSharedDataSubscriber(handler); + } +} diff --git a/src/main/java/org/tron/gossip/manager/GossipCoreConstants.java b/src/main/java/org/tron/gossip/manager/GossipCoreConstants.java new file mode 100755 index 00000000000..ddefe52200e --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/GossipCoreConstants.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +public interface GossipCoreConstants { + String PER_NODE_DATA_SIZE = "gossip.core.pernodedata.size"; + String SHARED_DATA_SIZE = "gossip.core.shareddata.size"; + String REQUEST_SIZE = "gossip.core.requests.size"; + String THREADPOOL_ACTIVE = "gossip.core.threadpool.active"; + String THREADPOOL_SIZE = "gossip.core.threadpool.size"; + String MESSAGE_SERDE_EXCEPTION = "gossip.core.message_serde_exception"; + String MESSAGE_TRANSMISSION_EXCEPTION = "gossip.core.message_transmission_exception"; + String MESSAGE_TRANSMISSION_SUCCESS = "gossip.core.message_transmission_success"; +} diff --git a/src/main/java/org/tron/gossip/manager/GossipManager.java b/src/main/java/org/tron/gossip/manager/GossipManager.java new file mode 100755 index 00000000000..31f6572172d --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/GossipManager.java @@ -0,0 +1,401 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.tron.gossip.GossipSettings; +import org.tron.gossip.LocalMember; +import org.tron.gossip.Member; +import org.tron.gossip.crdt.Crdt; +import org.tron.gossip.event.GossipListener; +import org.tron.gossip.event.GossipState; +import org.tron.gossip.event.data.UpdateNodeDataEventHandler; +import org.tron.gossip.event.data.UpdateSharedDataEventHandler; +import org.tron.gossip.lock.LockManager; +import org.tron.gossip.lock.exceptions.VoteFailedException; +import org.tron.gossip.manager.handlers.MessageHandler; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.SharedDataMessage; +import org.tron.gossip.protocol.ProtocolManager; +import org.tron.gossip.transport.TransportManager; +import org.tron.gossip.utils.ReflectionUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +public abstract class GossipManager { + private static final Logger LOGGER = LoggerFactory.getLogger("GossipManager"); + + // this mapper is used for ring and user-data persistence only. NOT messages. + public static final ObjectMapper metdataObjectMapper = new ObjectMapper() { + private static final long serialVersionUID = 1L; + { + enableDefaultTyping(); + configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, false); + }}; + + private final ConcurrentSkipListMap members; + private final LocalMember me; + private final GossipSettings settings; + private final AtomicBoolean gossipServiceRunning; + + private TransportManager transportManager; + private ProtocolManager protocolManager; + + private final GossipCore gossipCore; + private final DataReaper dataReaper; + private final Clock clock; + private final ScheduledExecutorService scheduledServiced; + private final MetricRegistry registry; + private final RingStatePersister ringState; + private final UserDataPersister userDataState; + private final GossipMemberStateRefresher memberStateRefresher; + + private final MessageHandler messageHandler; + private final LockManager lockManager; + + public GossipManager(String cluster, + URI uri, String id, Map properties, GossipSettings settings, + List gossipMembers, GossipListener listener, MetricRegistry registry, + MessageHandler messageHandler) { + this.settings = settings; + this.messageHandler = messageHandler; + + clock = new SystemClock(); + me = new LocalMember(cluster, uri, id, clock.nanoTime(), properties, + settings.getWindowSize(), settings.getMinimumSamples(), settings.getDistribution()); + gossipCore = new GossipCore(this, registry); + this.lockManager = new LockManager(this, settings.getLockManagerSettings(), registry); + dataReaper = new DataReaper(gossipCore, clock); + members = new ConcurrentSkipListMap<>(); + for (Member startupMember : gossipMembers) { + if (!startupMember.equals(me)) { + LocalMember member = new LocalMember(startupMember.getClusterName(), + startupMember.getUri(), startupMember.getId(), + clock.nanoTime(), startupMember.getProperties(), settings.getWindowSize(), + settings.getMinimumSamples(), settings.getDistribution()); + //TODO should members start in down state? + members.put(member, GossipState.DOWN); + } + } + gossipServiceRunning = new AtomicBoolean(true); + this.scheduledServiced = Executors.newScheduledThreadPool(1); + this.registry = registry; + this.ringState = new RingStatePersister(GossipManager.buildRingStatePath(this), this); + this.userDataState = new UserDataPersister( + gossipCore, + GossipManager.buildPerNodeDataPath(this), + GossipManager.buildSharedDataPath(this)); + this.memberStateRefresher = new GossipMemberStateRefresher(members, settings, listener, this::findPerNodeGossipData); + readSavedRingState(); + readSavedDataState(); + } + + public MessageHandler getMessageHandler() { + return messageHandler; + } + + public ConcurrentSkipListMap getMembers() { + return members; + } + + public GossipSettings getSettings() { + return settings; + } + + /** + * @return a read only list of members found in the DOWN state. + */ + public List getDeadMembers() { + return Collections.unmodifiableList( + members.entrySet() + .stream() + .filter(entry -> GossipState.DOWN.equals(entry.getValue())) + .map(Entry::getKey).collect(Collectors.toList())); + } + + /** + * + * @return a read only list of members found in the UP state + */ + public List getLiveMembers() { + return Collections.unmodifiableList( + members.entrySet() + .stream() + .filter(entry -> GossipState.UP.equals(entry.getValue())) + .map(Entry::getKey).collect(Collectors.toList())); + } + + public LocalMember getMyself() { + return me; + } + + /** + * Starts the client. Specifically, start the various cycles for this protocol. Start the gossip + * thread and start the receiver thread. + */ + public void init() { + + // protocol manager and transport managers are specified in settings. + // construct them here via reflection. + + protocolManager = ReflectionUtils.constructWithReflection( + settings.getProtocolManagerClass(), + new Class[] { GossipSettings.class, String.class, MetricRegistry.class }, + new Object[] { settings, me.getId(), this.getRegistry() } + ); + + transportManager = ReflectionUtils.constructWithReflection( + settings.getTransportManagerClass(), + new Class[] { GossipManager.class, GossipCore.class}, + new Object[] { this, gossipCore } + ); + + // start processing gossip messages. + transportManager.startEndpoint(); + transportManager.startActiveGossiper(); + + dataReaper.init(); + if (settings.isPersistRingState()) { + scheduledServiced.scheduleAtFixedRate(ringState, 60, 60, TimeUnit.SECONDS); + } + if (settings.isPersistDataState()) { + scheduledServiced.scheduleAtFixedRate(userDataState, 60, 60, TimeUnit.SECONDS); + } + memberStateRefresher.init(); + LOGGER.debug("The GossipManager is started."); + } + + private void readSavedRingState() { + if (settings.isPersistRingState()) { + for (LocalMember l : ringState.readFromDisk()) { + LocalMember member = new LocalMember(l.getClusterName(), + l.getUri(), l.getId(), + clock.nanoTime(), l.getProperties(), settings.getWindowSize(), + settings.getMinimumSamples(), settings.getDistribution()); + members.putIfAbsent(member, GossipState.DOWN); + } + } + } + + private void readSavedDataState() { + if (settings.isPersistDataState()) { + for (Entry> l : userDataState.readPerNodeFromDisk().entrySet()) { + for (Entry j : l.getValue().entrySet()) { + gossipCore.addPerNodeData(j.getValue()); + } + } + } + if (settings.isPersistRingState()) { + for (Entry l : userDataState.readSharedDataFromDisk().entrySet()) { + gossipCore.addSharedData(l.getValue()); + } + } + } + + /** + * Shutdown the gossip service. + */ + public void shutdown() { + gossipServiceRunning.set(false); + lockManager.shutdown(); + gossipCore.shutdown(); + transportManager.shutdown(); + dataReaper.close(); + memberStateRefresher.shutdown(); + scheduledServiced.shutdown(); + try { + scheduledServiced.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.error(e.toString()); + } + scheduledServiced.shutdownNow(); + } + + public void gossipPerNodeData(PerNodeDataMessage message){ + Objects.nonNull(message.getKey()); + Objects.nonNull(message.getTimestamp()); + Objects.nonNull(message.getPayload()); + message.setNodeId(me.getId()); + gossipCore.addPerNodeData(message); + } + + public void gossipSharedData(SharedDataMessage message){ + Objects.nonNull(message.getKey()); + Objects.nonNull(message.getTimestamp()); + Objects.nonNull(message.getPayload()); + message.setNodeId(me.getId()); + gossipCore.addSharedData(message); + } + + @SuppressWarnings("rawtypes") + public Crdt findCrdt(String key){ + SharedDataMessage l = gossipCore.getSharedData().get(key); + if (l == null){ + return null; + } + if (l.getExpireAt() < clock.currentTimeMillis()){ + return null; + } else { + return (Crdt) l.getPayload(); + } + } + + @SuppressWarnings("rawtypes") + public Crdt merge(SharedDataMessage message){ + Objects.nonNull(message.getKey()); + Objects.nonNull(message.getTimestamp()); + Objects.nonNull(message.getPayload()); + message.setNodeId(me.getId()); + if (! (message.getPayload() instanceof Crdt)){ + throw new IllegalArgumentException("Not a subclass of CRDT " + message.getPayload()); + } + return gossipCore.merge(message); + } + + public PerNodeDataMessage findPerNodeGossipData(String nodeId, String key){ + ConcurrentHashMap j = gossipCore.getPerNodeData().get(nodeId); + if (j == null){ + return null; + } else { + PerNodeDataMessage l = j.get(key); + if (l == null){ + return null; + } + if (l.getExpireAt() != null && l.getExpireAt() < clock.currentTimeMillis()) { + return null; + } + return l; + } + } + + public SharedDataMessage findSharedGossipData(String key){ + SharedDataMessage l = gossipCore.getSharedData().get(key); + if (l == null){ + return null; + } + if (l.getExpireAt() < clock.currentTimeMillis()){ + return null; + } else { + return l; + } + } + + public DataReaper getDataReaper() { + return dataReaper; + } + + public RingStatePersister getRingState() { + return ringState; + } + + public UserDataPersister getUserDataState() { + return userDataState; + } + + public GossipMemberStateRefresher getMemberStateRefresher() { + return memberStateRefresher; + } + + public Clock getClock() { + return clock; + } + + public MetricRegistry getRegistry() { + return registry; + } + + public ProtocolManager getProtocolManager() { + return protocolManager; + } + + public TransportManager getTransportManager() { + return transportManager; + } + + // todo: consider making these path methods part of GossipSettings + + public static File buildRingStatePath(GossipManager manager) { + return new File(manager.getSettings().getPathToRingState(), "ringstate." + manager.getMyself().getClusterName() + "." + + manager.getMyself().getId() + ".json"); + } + + public static File buildSharedDataPath(GossipManager manager){ + return new File(manager.getSettings().getPathToDataState(), "shareddata." + + manager.getMyself().getClusterName() + "." + manager.getMyself().getId() + ".json"); + } + + public static File buildPerNodeDataPath(GossipManager manager) { + return new File(manager.getSettings().getPathToDataState(), "pernodedata." + + manager.getMyself().getClusterName() + "." + manager.getMyself().getId() + ".json"); + } + + public void registerPerNodeDataSubscriber(UpdateNodeDataEventHandler handler){ + gossipCore.registerPerNodeDataSubscriber(handler); + } + + public void registerSharedDataSubscriber(UpdateSharedDataEventHandler handler){ + gossipCore.registerSharedDataSubscriber(handler); + } + + public void unregisterPerNodeDataSubscriber(UpdateNodeDataEventHandler handler){ + gossipCore.unregisterPerNodeDataSubscriber(handler); + } + + public void unregisterSharedDataSubscriber(UpdateSharedDataEventHandler handler){ + gossipCore.unregisterSharedDataSubscriber(handler); + } + + public void registerGossipListener(GossipListener listener) { + memberStateRefresher.register(listener); + } + + /** + * Get the lock manager specified with this GossipManager. + * @return lock manager object. + */ + public LockManager getLockManager() { + return lockManager; + } + + /** + * Try to acquire a lock on given shared data key. + * @param key key of tha share data object. + * @throws VoteFailedException if the locking is failed. + */ + public void acquireSharedDataLock(String key) throws VoteFailedException{ + lockManager.acquireSharedDataLock(key); + } +} diff --git a/src/main/java/org/tron/gossip/manager/GossipManagerBuilder.java b/src/main/java/org/tron/gossip/manager/GossipManagerBuilder.java new file mode 100755 index 00000000000..65d4bac55a1 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/GossipManagerBuilder.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import com.codahale.metrics.MetricRegistry; +import org.tron.gossip.GossipSettings; +import org.tron.gossip.Member; +import org.tron.gossip.StartupSettings; +import org.tron.gossip.event.GossipListener; +import org.tron.gossip.event.GossipState; +import org.tron.gossip.manager.handlers.MessageHandler; +import org.tron.gossip.manager.handlers.MessageHandlerFactory; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GossipManagerBuilder { + + public static ManagerBuilder newBuilder() { + return new ManagerBuilder(); + } + + public static final class ManagerBuilder { + private String cluster; + private URI uri; + private String id; + private GossipSettings settings; + private List gossipMembers; + private GossipListener listener; + private MetricRegistry registry; + private Map properties; + private MessageHandler messageHandler; + + private ManagerBuilder() {} + + private void checkArgument(boolean check, String msg) { + if (!check) { + throw new IllegalArgumentException(msg); + } + } + + public ManagerBuilder cluster(String cluster) { + this.cluster = cluster; + return this; + } + + public ManagerBuilder properties(Map properties) { + this.properties = properties; + return this; + } + + public ManagerBuilder id(String id) { + this.id = id; + return this; + } + + public ManagerBuilder gossipSettings(GossipSettings settings) { + this.settings = settings; + return this; + } + + public ManagerBuilder startupSettings(StartupSettings startupSettings) { + this.cluster = startupSettings.getCluster(); + this.id = startupSettings.getId(); + this.settings = startupSettings.getGossipSettings(); + this.gossipMembers = startupSettings.getGossipMembers(); + this.uri = startupSettings.getUri(); + return this; + } + + public ManagerBuilder gossipMembers(List members) { + this.gossipMembers = members; + return this; + } + + public ManagerBuilder listener(GossipListener listener) { + this.listener = listener; + return this; + } + + public ManagerBuilder registry(MetricRegistry registry) { + this.registry = registry; + return this; + } + + public ManagerBuilder uri(URI uri){ + this.uri = uri; + return this; + } + + public ManagerBuilder messageHandler(MessageHandler messageHandler) { + this.messageHandler = messageHandler; + return this; + } + + public GossipManager build() { + checkArgument(id != null, "You must specify an id"); + checkArgument(cluster != null, "You must specify a cluster name"); + checkArgument(settings != null, "You must specify gossip settings"); + checkArgument(uri != null, "You must specify a uri"); + if (registry == null){ + registry = new MetricRegistry(); + } + if (properties == null){ + properties = new HashMap(); + } + if (listener == null){ + listener((a,b) -> {}); + } + if (gossipMembers == null) { + gossipMembers = new ArrayList<>(); + } + + if (messageHandler == null) { + messageHandler = MessageHandlerFactory.defaultHandler(); + } + return new GossipManager(cluster, uri, id, properties, settings, gossipMembers, listener, registry, messageHandler) {} ; + } + } + +} diff --git a/src/main/java/org/tron/gossip/manager/GossipMemberStateRefresher.java b/src/main/java/org/tron/gossip/manager/GossipMemberStateRefresher.java new file mode 100755 index 00000000000..29f870eac9c --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/GossipMemberStateRefresher.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import org.tron.gossip.GossipSettings; +import org.tron.gossip.LocalMember; +import org.tron.gossip.event.GossipListener; +import org.tron.gossip.event.GossipState; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.ShutdownMessage; +import org.apache.log4j.Logger; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.function.BiFunction; + +public class GossipMemberStateRefresher { + public static final Logger LOGGER = Logger.getLogger(GossipMemberStateRefresher.class); + + private final Map members; + private final GossipSettings settings; + private final List listeners = new CopyOnWriteArrayList<>(); + private final Clock clock; + private final BiFunction findPerNodeGossipData; + private final ExecutorService listenerExecutor; + private final ScheduledExecutorService scheduledExecutor; + private final BlockingQueue workQueue; + + public GossipMemberStateRefresher(Map members, GossipSettings settings, + GossipListener listener, + BiFunction findPerNodeGossipData) { + this.members = members; + this.settings = settings; + listeners.add(listener); + this.findPerNodeGossipData = findPerNodeGossipData; + clock = new SystemClock(); + workQueue = new ArrayBlockingQueue<>(1024); + listenerExecutor = new ThreadPoolExecutor(1, 20, 1, TimeUnit.SECONDS, workQueue, + new ThreadPoolExecutor.DiscardOldestPolicy()); + scheduledExecutor = Executors.newScheduledThreadPool(1); + } + + public void init() { + scheduledExecutor.scheduleAtFixedRate(() -> run(), 0, 100, TimeUnit.MILLISECONDS); + } + + public void run() { + try { + runOnce(); + } catch (RuntimeException ex) { + LOGGER.warn("scheduled state had exception", ex); + } + } + + public void runOnce() { + for (Entry entry : members.entrySet()) { + boolean userDown = processOptimisticShutdown(entry); + if (userDown) + continue; + + Double phiMeasure = entry.getKey().detect(clock.nanoTime()); + GossipState requiredState; + + if (phiMeasure != null) { + requiredState = calcRequiredState(phiMeasure); + } else { + requiredState = calcRequiredStateCleanupInterval(entry.getKey(), entry.getValue()); + } + + if (entry.getValue() != requiredState) { + members.put(entry.getKey(), requiredState); + /* Call listeners asynchronously */ + for (GossipListener listener: listeners) + listenerExecutor.execute(() -> listener.gossipEvent(entry.getKey(), requiredState)); + } + } + } + + public GossipState calcRequiredState(Double phiMeasure) { + if (phiMeasure > settings.getConvictThreshold()) + return GossipState.DOWN; + else + return GossipState.UP; + } + + public GossipState calcRequiredStateCleanupInterval(LocalMember member, GossipState state) { + long now = clock.nanoTime(); + long nowInMillis = TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); + if (nowInMillis - settings.getCleanupInterval() > member.getHeartbeat()) { + return GossipState.DOWN; + } else { + return state; + } + } + + /** + * If we have a special key the per-node data that means that the node has sent us + * a pre-emptive shutdown message. We process this so node is seen down sooner + * + * @param l member to consider + * @return true if node forced down + */ + public boolean processOptimisticShutdown(Entry l) { + PerNodeDataMessage m = findPerNodeGossipData.apply(l.getKey().getId(), ShutdownMessage.PER_NODE_KEY); + if (m == null) { + return false; + } + ShutdownMessage s = (ShutdownMessage) m.getPayload(); + if (s.getShutdownAtNanos() > l.getKey().getHeartbeat()) { + members.put(l.getKey(), GossipState.DOWN); + if (l.getValue() == GossipState.UP) { + for (GossipListener listener: listeners) + listenerExecutor.execute(() -> listener.gossipEvent(l.getKey(), GossipState.DOWN)); + } + return true; + } + return false; + } + + public void register(GossipListener listener) { + listeners.add(listener); + } + + public void shutdown() { + scheduledExecutor.shutdown(); + try { + scheduledExecutor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + listenerExecutor.shutdown(); + try { + listenerExecutor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + listenerExecutor.shutdownNow(); + } +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/manager/PassiveGossipConstants.java b/src/main/java/org/tron/gossip/manager/PassiveGossipConstants.java new file mode 100755 index 00000000000..fe507c37ac4 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/PassiveGossipConstants.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +public interface PassiveGossipConstants { + String SIGNED_MESSAGE = "gossip.passive.signed_message"; + String UNSIGNED_MESSAGE = "gossip.passive.unsigned_message"; +} diff --git a/src/main/java/org/tron/gossip/manager/RingStatePersister.java b/src/main/java/org/tron/gossip/manager/RingStatePersister.java new file mode 100755 index 00000000000..5333de5c30a --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/RingStatePersister.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.tron.gossip.LocalMember; +import org.apache.log4j.Logger; + +public class RingStatePersister implements Runnable { + + private static final Logger LOGGER = Logger.getLogger(RingStatePersister.class); + private final File path; + // NOTE: this is a different instance than what gets used for message marshalling. + private final ObjectMapper objectMapper; + private final GossipManager manager; + + public RingStatePersister(File path, GossipManager manager){ + this.path = path; + this.objectMapper = GossipManager.metdataObjectMapper; + this.manager = manager; + } + + @Override + public void run() { + writeToDisk(); + } + + void writeToDisk() { + NavigableSet i = manager.getMembers().keySet(); + try (FileOutputStream fos = new FileOutputStream(path)){ + objectMapper.writeValue(fos, i); + } catch (IOException e) { + LOGGER.debug(e); + } + } + + @SuppressWarnings("unchecked") + List readFromDisk() { + if (!path.exists()) { + return new ArrayList<>(); + } + try (FileInputStream fos = new FileInputStream(path)){ + return objectMapper.readValue(fos, ArrayList.class); + } catch (IOException e) { + LOGGER.debug(e); + } + return new ArrayList<>(); + } +} diff --git a/src/main/java/org/tron/gossip/manager/SimpleActiveGossiper.java b/src/main/java/org/tron/gossip/manager/SimpleActiveGossiper.java new file mode 100755 index 00000000000..cd688627490 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/SimpleActiveGossiper.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.tron.gossip.LocalMember; + +import com.codahale.metrics.MetricRegistry; + +/** + * Base implementation gossips randomly to live nodes periodically gossips to dead ones + * + */ +public class SimpleActiveGossiper extends AbstractActiveGossiper { + + private ScheduledExecutorService scheduledExecutorService; + private final BlockingQueue workQueue; + private ThreadPoolExecutor threadService; + + public SimpleActiveGossiper(GossipManager gossipManager, GossipCore gossipCore, + MetricRegistry registry) { + super(gossipManager, gossipCore, registry); + scheduledExecutorService = Executors.newScheduledThreadPool(2); + workQueue = new ArrayBlockingQueue(1024); + threadService = new ThreadPoolExecutor(1, 30, 1, TimeUnit.SECONDS, workQueue, + new ThreadPoolExecutor.DiscardOldestPolicy()); + } + + @Override + public void init() { + super.init(); + scheduledExecutorService.scheduleAtFixedRate(() -> { + threadService.execute(() -> { + sendToALiveMember(); + }); + }, 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS); + scheduledExecutorService.scheduleAtFixedRate(() -> { + sendToDeadMember(); + }, 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS); + scheduledExecutorService.scheduleAtFixedRate( + () -> sendPerNodeData(gossipManager.getMyself(), + selectPartner(gossipManager.getLiveMembers())), + 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS); + scheduledExecutorService.scheduleAtFixedRate( + () -> sendSharedData(gossipManager.getMyself(), + selectPartner(gossipManager.getLiveMembers())), + 0, gossipManager.getSettings().getGossipInterval(), TimeUnit.MILLISECONDS); + } + + @Override + public void shutdown() { + super.shutdown(); + scheduledExecutorService.shutdown(); + try { + scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + sendShutdownMessage(); + threadService.shutdown(); + try { + threadService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + LOGGER.debug("Issue during shutdown", e); + } + } + + protected void sendToALiveMember(){ + LocalMember member = selectPartner(gossipManager.getLiveMembers()); + sendMembershipList(gossipManager.getMyself(), member); + } + + protected void sendToDeadMember(){ + LocalMember member = selectPartner(gossipManager.getDeadMembers()); + sendMembershipList(gossipManager.getMyself(), member); + } + + /** + * sends an optimistic shutdown message to several clusters nodes + */ + protected void sendShutdownMessage(){ + List l = gossipManager.getLiveMembers(); + int sendTo = l.size() < 3 ? 1 : l.size() / 2; + for (int i = 0; i < sendTo; i++) { + threadService.execute(() -> sendShutdownMessage(gossipManager.getMyself(), selectPartner(l))); + } + } +} diff --git a/src/main/java/org/tron/gossip/manager/SystemClock.java b/src/main/java/org/tron/gossip/manager/SystemClock.java new file mode 100755 index 00000000000..1fdb7d5ee13 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/SystemClock.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +public class SystemClock implements Clock { + + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + @Override + public long nanoTime() { + return System.nanoTime(); + } + +} diff --git a/src/main/java/org/tron/gossip/manager/UserDataPersister.java b/src/main/java/org/tron/gossip/manager/UserDataPersister.java new file mode 100755 index 00000000000..c72f086df40 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/UserDataPersister.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.LoggerFactory; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.SharedDataMessage; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +public class UserDataPersister implements Runnable { + public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("UserDataPersister"); + + private final GossipCore gossipCore; + + private final File perNodePath; + private final File sharedPath; + private final ObjectMapper objectMapper; + + UserDataPersister(GossipCore gossipCore, File perNodePath, File sharedPath) { + this.gossipCore = gossipCore; + this.objectMapper = GossipManager.metdataObjectMapper; + this.perNodePath = perNodePath; + this.sharedPath = sharedPath; + } + + @SuppressWarnings("unchecked") + ConcurrentHashMap> readPerNodeFromDisk() { + if (!perNodePath.exists()) { + return new ConcurrentHashMap>(); + } + try (FileInputStream fos = new FileInputStream(perNodePath)) { + return objectMapper.readValue(fos, ConcurrentHashMap.class); + } catch (IOException e) { + LOGGER.debug(e.toString()); + } + return new ConcurrentHashMap>(); + } + + void writePerNodeToDisk() { + try (FileOutputStream fos = new FileOutputStream(perNodePath)) { + objectMapper.writeValue(fos, gossipCore.getPerNodeData()); + } catch (IOException e) { + LOGGER.warn(e.toString()); + } + } + + void writeSharedToDisk() { + try (FileOutputStream fos = new FileOutputStream(sharedPath)) { + objectMapper.writeValue(fos, gossipCore.getSharedData()); + } catch (IOException e) { + LOGGER.warn(e.toString()); + } + } + + @SuppressWarnings("unchecked") + ConcurrentHashMap readSharedDataFromDisk() { + if (!sharedPath.exists()) { + return new ConcurrentHashMap<>(); + } + try (FileInputStream fos = new FileInputStream(sharedPath)) { + return objectMapper.readValue(fos, ConcurrentHashMap.class); + } catch (IOException e) { + LOGGER.debug(e.toString()); + } + return new ConcurrentHashMap(); + } + + /** + * Writes all pernode and shared data to disk + */ + @Override + public void run() { + writePerNodeToDisk(); + writeSharedToDisk(); + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/ActiveGossipMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/ActiveGossipMessageHandler.java new file mode 100755 index 00000000000..16a101d1958 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/ActiveGossipMessageHandler.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.Member; +import org.tron.gossip.RemoteMember; +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.udp.UdpActiveGossipMessage; +import org.tron.gossip.udp.UdpActiveGossipOk; +import org.tron.gossip.udp.UdpNotAMemberFault; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +public class ActiveGossipMessageHandler implements MessageHandler { + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + List remoteGossipMembers = new ArrayList<>(); + RemoteMember senderMember = null; + UdpActiveGossipMessage activeGossipMessage = (UdpActiveGossipMessage) base; + for (int i = 0; i < activeGossipMessage.getMembers().size(); i++) { + URI u; + try { + u = new URI(activeGossipMessage.getMembers().get(i).getUri()); + } catch (URISyntaxException e) { + GossipCore.LOGGER.debug("Gossip message with faulty URI", e); + continue; + } + RemoteMember member = new RemoteMember( + activeGossipMessage.getMembers().get(i).getCluster(), + u, + activeGossipMessage.getMembers().get(i).getId(), + activeGossipMessage.getMembers().get(i).getHeartbeat(), + activeGossipMessage.getMembers().get(i).getProperties()); + if (i == 0) { + senderMember = member; + } + if (!(member.getClusterName().equals(gossipManager.getMyself().getClusterName()))) { + UdpNotAMemberFault f = new UdpNotAMemberFault(); + f.setException("Not a member of this cluster " + i); + f.setUriFrom(activeGossipMessage.getUriFrom()); + f.setUuid(activeGossipMessage.getUuid()); + GossipCore.LOGGER.warn(f.toString()); + gossipCore.sendOneWay(f, member.getUri()); + continue; + } + remoteGossipMembers.add(member); + } + UdpActiveGossipOk o = new UdpActiveGossipOk(); + o.setUriFrom(activeGossipMessage.getUriFrom()); + o.setUuid(activeGossipMessage.getUuid()); + gossipCore.sendOneWay(o, senderMember.getUri()); + gossipCore.mergeLists(senderMember, remoteGossipMembers); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/MessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/MessageHandler.java new file mode 100755 index 00000000000..a356d79f6a0 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/MessageHandler.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; + +public interface MessageHandler { + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base); +} \ No newline at end of file diff --git a/src/main/java/org/tron/gossip/manager/handlers/MessageHandlerFactory.java b/src/main/java/org/tron/gossip/manager/handlers/MessageHandlerFactory.java new file mode 100755 index 00000000000..ca7eafd3be6 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/MessageHandlerFactory.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.*; + +import java.util.Arrays; + +public class MessageHandlerFactory { + + public static MessageHandler defaultHandler() { + return concurrentHandler( + new TypedMessageHandler(Response.class, new ResponseHandler()), + new TypedMessageHandler(ShutdownMessage.class, new ShutdownMessageHandler()), + new TypedMessageHandler(PerNodeDataMessage.class, new PerNodeDataMessageHandler()), + new TypedMessageHandler(SharedDataMessage.class, new SharedDataMessageHandler()), + new TypedMessageHandler(ActiveGossipMessage.class, new ActiveGossipMessageHandler()), + new TypedMessageHandler(PerNodeDataBulkMessage.class, new PerNodeDataBulkMessageHandler()), + new TypedMessageHandler(SharedDataBulkMessage.class, new SharedDataBulkMessageHandler()) + ); + } + + public static MessageHandler concurrentHandler(MessageHandler... handlers) { + if (handlers == null) + throw new NullPointerException("handlers cannot be null"); + if (Arrays.asList(handlers).stream().filter(i -> i != null).count() != handlers.length) { + throw new NullPointerException("found at least one null handler"); + } + return new MessageHandler() { + @Override public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, + Base base) { + // return true if at least one of the component handlers return true. + return Arrays.asList(handlers).stream() + .filter((mi) -> mi.invoke(gossipCore, gossipManager, base)).count() > 0; + } + }; + } +} + diff --git a/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataBulkMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataBulkMessageHandler.java new file mode 100755 index 00000000000..a12e23daa5b --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataBulkMessageHandler.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.udp.UdpPerNodeDataBulkMessage; + +public class PerNodeDataBulkMessageHandler implements MessageHandler { + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + UdpPerNodeDataBulkMessage udpMessage = (UdpPerNodeDataBulkMessage) base; + for (PerNodeDataMessage dataMsg: udpMessage.getMessages()) + gossipCore.addPerNodeData(dataMsg); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataMessageHandler.java new file mode 100755 index 00000000000..9fddb35b441 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/PerNodeDataMessageHandler.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.udp.UdpPerNodeDataMessage; + +public class PerNodeDataMessageHandler implements MessageHandler { + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + UdpPerNodeDataMessage message = (UdpPerNodeDataMessage) base; + gossipCore.addPerNodeData(message); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/ResponseHandler.java b/src/main/java/org/tron/gossip/manager/handlers/ResponseHandler.java new file mode 100755 index 00000000000..86d91cd34a4 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/ResponseHandler.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.udp.Trackable; + +public class ResponseHandler implements MessageHandler { + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + if (base instanceof Trackable) { + Trackable t = (Trackable) base; + gossipCore.handleResponse(t.getUuid() + "/" + t.getUriFrom(), (Base) t); + return true; + } + return false; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/SharedDataBulkMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/SharedDataBulkMessageHandler.java new file mode 100755 index 00000000000..a7bf152c34f --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/SharedDataBulkMessageHandler.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.model.SharedDataMessage; +import org.tron.gossip.udp.UdpSharedDataBulkMessage; + +public class SharedDataBulkMessageHandler implements MessageHandler{ + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + UdpSharedDataBulkMessage udpMessage = (UdpSharedDataBulkMessage) base; + for (SharedDataMessage dataMsg: udpMessage.getMessages()) + gossipCore.addSharedData(dataMsg); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/SharedDataMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/SharedDataMessageHandler.java new file mode 100755 index 00000000000..68ccd758a94 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/SharedDataMessageHandler.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.udp.UdpSharedDataMessage; + +public class SharedDataMessageHandler implements MessageHandler{ + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + UdpSharedDataMessage message = (UdpSharedDataMessage) base; + gossipCore.addSharedData(message); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/ShutdownMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/ShutdownMessageHandler.java new file mode 100755 index 00000000000..03aa1ded7cd --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/ShutdownMessageHandler.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.model.PerNodeDataMessage; +import org.tron.gossip.model.ShutdownMessage; + +public class ShutdownMessageHandler implements MessageHandler { + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return boolean indicating success. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + ShutdownMessage s = (ShutdownMessage) base; + PerNodeDataMessage m = new PerNodeDataMessage(); + m.setKey(ShutdownMessage.PER_NODE_KEY); + m.setNodeId(s.getNodeId()); + m.setPayload(base); + m.setTimestamp(System.currentTimeMillis()); + m.setExpireAt(System.currentTimeMillis() + 30L * 1000L); + gossipCore.addPerNodeData(m); + return true; + } +} diff --git a/src/main/java/org/tron/gossip/manager/handlers/TypedMessageHandler.java b/src/main/java/org/tron/gossip/manager/handlers/TypedMessageHandler.java new file mode 100755 index 00000000000..c111e0a5f92 --- /dev/null +++ b/src/main/java/org/tron/gossip/manager/handlers/TypedMessageHandler.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.manager.handlers; + +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; + +public class TypedMessageHandler implements MessageHandler { + final private Class messageClass; + final private MessageHandler messageHandler; + + public TypedMessageHandler(Class messageClass, MessageHandler messageHandler) { + if (messageClass == null || messageHandler == null) { + throw new NullPointerException(); + } + this.messageClass = messageClass; + this.messageHandler = messageHandler; + } + + /** + * @param gossipCore context. + * @param gossipManager context. + * @param base message reference. + * @return true if types match, false otherwise. + */ + @Override + public boolean invoke(GossipCore gossipCore, GossipManager gossipManager, Base base) { + if (messageClass.isAssignableFrom(base.getClass())) { + messageHandler.invoke(gossipCore, gossipManager, base); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/tron/gossip/model/ActiveGossipMessage.java b/src/main/java/org/tron/gossip/model/ActiveGossipMessage.java new file mode 100755 index 00000000000..e32a13aa2b2 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/ActiveGossipMessage.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import java.util.ArrayList; +import java.util.List; + +public class ActiveGossipMessage extends Base { + + private List members = new ArrayList<>(); + + public ActiveGossipMessage(){ + + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + +} diff --git a/src/main/java/org/tron/gossip/model/ActiveGossipOk.java b/src/main/java/org/tron/gossip/model/ActiveGossipOk.java new file mode 100755 index 00000000000..01a10b20d85 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/ActiveGossipOk.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public class ActiveGossipOk extends Response { + +} diff --git a/src/main/java/org/tron/gossip/model/Base.java b/src/main/java/org/tron/gossip/model/Base.java new file mode 100755 index 00000000000..7abc75d1a41 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/Base.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import org.tron.gossip.udp.UdpActiveGossipMessage; +import org.tron.gossip.udp.UdpActiveGossipOk; +import org.tron.gossip.udp.UdpPerNodeDataBulkMessage; +import org.tron.gossip.udp.UdpNotAMemberFault; +import org.tron.gossip.udp.UdpSharedDataBulkMessage; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; + + +@JsonTypeInfo( + use = JsonTypeInfo.Id.CLASS, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @Type(value = ActiveGossipMessage.class, name = "ActiveGossipMessage"), + @Type(value = Fault.class, name = "Fault"), + @Type(value = ActiveGossipOk.class, name = "ActiveGossipOk"), + @Type(value = UdpActiveGossipOk.class, name = "UdpActiveGossipOk"), + @Type(value = UdpActiveGossipMessage.class, name = "UdpActiveGossipMessage"), + @Type(value = UdpNotAMemberFault.class, name = "UdpNotAMemberFault"), + @Type(value = PerNodeDataMessage.class, name = "PerNodeDataMessage"), + @Type(value = UdpPerNodeDataBulkMessage.class, name = "UdpPerNodeDataMessage"), + @Type(value = SharedDataMessage.class, name = "SharedDataMessage"), + @Type(value = UdpSharedDataBulkMessage.class, name = "UdpSharedDataMessage") + }) +public class Base { + +} diff --git a/src/main/java/org/tron/gossip/model/Fault.java b/src/main/java/org/tron/gossip/model/Fault.java new file mode 100755 index 00000000000..279df5dd123 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/Fault.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public abstract class Fault extends Response { + + private String exception; + + public Fault(){} + + public String getException() { + return exception; + } + + public void setException(String exception) { + this.exception = exception; + } + + @Override + public String toString() { + return "Fault [exception=" + exception + "]"; + } + +} + diff --git a/src/main/java/org/tron/gossip/model/Member.java b/src/main/java/org/tron/gossip/model/Member.java new file mode 100755 index 00000000000..f037b0b9ba2 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/Member.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import java.util.Map; + +public class Member { + + private String cluster; + private String uri; + private String id; + private Long heartbeat; + private Map properties; + + public Member(){ + + } + + public Member(String cluster, String uri, String id, Long heartbeat){ + this.cluster = cluster; + this.uri = uri; + this.id = id; + this.heartbeat = heartbeat; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Long getHeartbeat() { + return heartbeat; + } + + public void setHeartbeat(Long heartbeat) { + this.heartbeat = heartbeat; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + @Override + public String toString() { + return "Member [cluster=" + cluster + ", uri=" + uri + ", id=" + id + ", heartbeat=" + + heartbeat + ", properties=" + properties + "]"; + } + +} diff --git a/src/main/java/org/tron/gossip/model/Message.java b/src/main/java/org/tron/gossip/model/Message.java new file mode 100755 index 00000000000..405c3345578 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/Message.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public class Message extends Base { + +} diff --git a/src/main/java/org/tron/gossip/model/NotAMemberFault.java b/src/main/java/org/tron/gossip/model/NotAMemberFault.java new file mode 100755 index 00000000000..d70e3830381 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/NotAMemberFault.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public class NotAMemberFault extends Fault { + + public NotAMemberFault(){ + + } + + public NotAMemberFault(String message){ + this.setException(message); + } +} diff --git a/src/main/java/org/tron/gossip/model/PerNodeDataBulkMessage.java b/src/main/java/org/tron/gossip/model/PerNodeDataBulkMessage.java new file mode 100755 index 00000000000..7bf7663aed6 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/PerNodeDataBulkMessage.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class PerNodeDataBulkMessage extends Base { + private List messages = new ArrayList<>(); + + public void addMessage(PerNodeDataMessage msg) { + messages.add(msg); + } + + public List getMessages() { + return messages; + } + + @Override public String toString() { + return "GossipDataBulkMessage[" + messages.stream().map(Object::toString) + .collect(Collectors.joining(",")) + "]"; + } +} diff --git a/src/main/java/org/tron/gossip/model/PerNodeDataMessage.java b/src/main/java/org/tron/gossip/model/PerNodeDataMessage.java new file mode 100755 index 00000000000..f3c50cc0685 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/PerNodeDataMessage.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import org.tron.gossip.replication.Replicable; + +public class PerNodeDataMessage extends Base { + + private String nodeId; + private String key; + private Object payload; + private Long timestamp; + private Long expireAt; + private Replicable replicable; + + public String getNodeId() { + return nodeId; + } + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public Object getPayload() { + return payload; + } + public void setPayload(Object payload) { + this.payload = payload; + } + public Long getTimestamp() { + return timestamp; + } + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + public Long getExpireAt() { + return expireAt; + } + public void setExpireAt(Long expireAt) { + this.expireAt = expireAt; + } + + public Replicable getReplicable() { + return replicable; + } + + public void setReplicable(Replicable replicable) { + this.replicable = replicable; + } + + @Override + public String toString() { + return "GossipDataMessage [nodeId=" + nodeId + ", key=" + key + ", payload=" + payload + + ", timestamp=" + timestamp + ", expireAt=" + expireAt + + ", replicable=" + replicable + "]"; + } + + + +} diff --git a/src/main/java/org/tron/gossip/model/Response.java b/src/main/java/org/tron/gossip/model/Response.java new file mode 100755 index 00000000000..d6f1cee193a --- /dev/null +++ b/src/main/java/org/tron/gossip/model/Response.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public abstract class Response extends Base { + +} diff --git a/src/main/java/org/tron/gossip/model/SharedDataBulkMessage.java b/src/main/java/org/tron/gossip/model/SharedDataBulkMessage.java new file mode 100755 index 00000000000..0ca9da7b17d --- /dev/null +++ b/src/main/java/org/tron/gossip/model/SharedDataBulkMessage.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class SharedDataBulkMessage extends Base { + private List messages = new ArrayList<>(); + + public void addMessage(SharedDataMessage msg) { + messages.add(msg); + } + + public List getMessages() { + return messages; + } + + @Override public String toString() { + return "SharedGossipDataBulkMessage[" + messages.stream().map(Object::toString) + .collect(Collectors.joining(",")) + "]"; + } +} diff --git a/src/main/java/org/tron/gossip/model/SharedDataMessage.java b/src/main/java/org/tron/gossip/model/SharedDataMessage.java new file mode 100755 index 00000000000..be61ea06e24 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/SharedDataMessage.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +import org.tron.gossip.replication.AllReplicable; +import org.tron.gossip.replication.Replicable; + +public class SharedDataMessage extends Base { + + private String nodeId; + private String key; + private Object payload; + private Long timestamp; + private Long expireAt; + private Replicable replicable; + + public String getNodeId() { + return nodeId; + } + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public Object getPayload() { + return payload; + } + public void setPayload(Object payload) { + this.payload = payload; + } + public Long getTimestamp() { + return timestamp; + } + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + public Long getExpireAt() { + return expireAt; + } + public void setExpireAt(Long expireAt) { + this.expireAt = expireAt; + } + + public Replicable getReplicable() { + return replicable; + } + + public void setReplicable(Replicable replicable) { + this.replicable = replicable; + } + + @Override + public String toString() { + return "SharedGossipDataMessage [nodeId=" + nodeId + ", key=" + key + ", payload=" + payload + + ", timestamp=" + timestamp + ", expireAt=" + expireAt + + ", replicable=" + replicable + "]"; + } +} + diff --git a/src/main/java/org/tron/gossip/model/ShutdownMessage.java b/src/main/java/org/tron/gossip/model/ShutdownMessage.java new file mode 100755 index 00000000000..6eea03103f4 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/ShutdownMessage.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public class ShutdownMessage extends Message { + + public static final String PER_NODE_KEY = "gossipcore.shutdowmessage"; + private long shutdownAtNanos; + private String nodeId; + + public ShutdownMessage(){ + + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public long getShutdownAtNanos() { + return shutdownAtNanos; + } + + public void setShutdownAtNanos(long shutdownAtNanos) { + this.shutdownAtNanos = shutdownAtNanos; + } + + @Override + public String toString() { + return "ShutdownMessage [shutdownAtNanos=" + shutdownAtNanos + ", nodeId=" + nodeId + "]"; + } + +} diff --git a/src/main/java/org/tron/gossip/model/SignedPayload.java b/src/main/java/org/tron/gossip/model/SignedPayload.java new file mode 100755 index 00000000000..d562f7cf3a9 --- /dev/null +++ b/src/main/java/org/tron/gossip/model/SignedPayload.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.model; + +public class SignedPayload extends Base{ + private byte [] data; + private byte [] signature; + public byte[] getData() { + return data; + } + public void setData(byte[] data) { + this.data = data; + } + public byte[] getSignature() { + return signature; + } + public void setSignature(byte[] signature) { + this.signature = signature; + } + +} diff --git a/src/main/java/org/tron/gossip/protocol/ProtocolManager.java b/src/main/java/org/tron/gossip/protocol/ProtocolManager.java new file mode 100755 index 00000000000..97470616fbb --- /dev/null +++ b/src/main/java/org/tron/gossip/protocol/ProtocolManager.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.protocol; + +import org.tron.gossip.model.Base; + +import java.io.IOException; + +/** interface for managing message marshaling. */ +public interface ProtocolManager { + + /** serialize a message + * @param message + * @return serialized message. + * @throws IOException + */ + byte[] write(Base message) throws IOException; + + /** + * Reads the next message from a byte source. + * @param buf + * @return a gossip message. + * @throws IOException + */ + Base read(byte[] buf) throws IOException; +} diff --git a/src/main/java/org/tron/gossip/protocol/json/JacksonProtocolManager.java b/src/main/java/org/tron/gossip/protocol/json/JacksonProtocolManager.java new file mode 100755 index 00000000000..33ee53fee23 --- /dev/null +++ b/src/main/java/org/tron/gossip/protocol/json/JacksonProtocolManager.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.protocol.json; + +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.tron.gossip.GossipSettings; +import org.tron.gossip.crdt.CrdtModule; +import org.tron.gossip.manager.PassiveGossipConstants; +import org.tron.gossip.model.Base; +import org.tron.gossip.model.SignedPayload; +import org.tron.gossip.protocol.ProtocolManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; + +// this class is constructed by reflection in GossipManager. +public class JacksonProtocolManager implements ProtocolManager { + + private final ObjectMapper objectMapper; + private final PrivateKey privKey; + private final Meter signed; + private final Meter unsigned; + + /** required for reflection to work! */ + public JacksonProtocolManager(GossipSettings settings, String id, MetricRegistry registry) { + // set up object mapper. + objectMapper = buildObjectMapper(settings); + + // set up message signing. + if (settings.isSignMessages()){ + File privateKey = new File(settings.getPathToKeyStore(), id); + File publicKey = new File(settings.getPathToKeyStore(), id + ".pub"); + if (!privateKey.exists()){ + throw new IllegalArgumentException("private key not found " + privateKey); + } + if (!publicKey.exists()){ + throw new IllegalArgumentException("public key not found " + publicKey); + } + try (FileInputStream keyfis = new FileInputStream(privateKey)) { + byte[] encKey = new byte[keyfis.available()]; + keyfis.read(encKey); + keyfis.close(); + PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encKey); + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + privKey = keyFactory.generatePrivate(privKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) { + throw new RuntimeException("failed hard", e); + } + } else { + privKey = null; + } + + signed = registry.meter(PassiveGossipConstants.SIGNED_MESSAGE); + unsigned = registry.meter(PassiveGossipConstants.UNSIGNED_MESSAGE); + } + + @Override + public byte[] write(Base message) throws IOException { + byte[] json_bytes; + if (privKey == null){ + json_bytes = objectMapper.writeValueAsBytes(message); + } else { + SignedPayload p = new SignedPayload(); + p.setData(objectMapper.writeValueAsString(message).getBytes()); + p.setSignature(sign(p.getData(), privKey)); + json_bytes = objectMapper.writeValueAsBytes(p); + } + return json_bytes; + } + + @Override + public Base read(byte[] buf) throws IOException { + Base activeGossipMessage = objectMapper.readValue(buf, Base.class); + if (activeGossipMessage instanceof SignedPayload){ + SignedPayload s = (SignedPayload) activeGossipMessage; + signed.mark(); + return objectMapper.readValue(s.getData(), Base.class); + } else { + unsigned.mark(); + return activeGossipMessage; + } + } + + public static ObjectMapper buildObjectMapper(GossipSettings settings) { + ObjectMapper om = new ObjectMapper(); + om.enableDefaultTyping(); + // todo: should be specified in the configuration. + om.registerModule(new CrdtModule()); + om.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, false); + return om; + } + + private static byte[] sign(byte [] bytes, PrivateKey pk){ + Signature dsa; + try { + dsa = Signature.getInstance("SHA1withDSA", "SUN"); + dsa.initSign(pk); + dsa.update(bytes); + return dsa.sign(); + } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/tron/gossip/replication/AllReplicable.java b/src/main/java/org/tron/gossip/replication/AllReplicable.java new file mode 100755 index 00000000000..c8a64f67898 --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/AllReplicable.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.Base; + +/** + * Replicable implementation which replicates data to any node. This is the default replication + * strategy if a data item not specified its replication behaviour. + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + * @see Replicable + */ +public class AllReplicable implements Replicable { + + @Override + public boolean shouldReplicate(LocalMember me, LocalMember destination, T message) { + return true; + } +} diff --git a/src/main/java/org/tron/gossip/replication/BlackListReplicable.java b/src/main/java/org/tron/gossip/replication/BlackListReplicable.java new file mode 100755 index 00000000000..cb5a852212b --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/BlackListReplicable.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.Base; + +import java.util.ArrayList; +import java.util.List; + +/** + * Replicable implementation which does not replicate data to given set of nodes. + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + * @see Replicable + */ +public class BlackListReplicable implements Replicable { + + private final List blackListMembers; + + public BlackListReplicable(List blackListMembers) { + if (blackListMembers == null) { + this.blackListMembers = new ArrayList<>(); + } else { + this.blackListMembers = blackListMembers; + } + } + + public List getBlackListMembers() { + return blackListMembers; + } + + @Override + public boolean shouldReplicate(LocalMember me, LocalMember destination, T message) { + return !blackListMembers.contains(destination); + } +} diff --git a/src/main/java/org/tron/gossip/replication/DataCenterReplicable.java b/src/main/java/org/tron/gossip/replication/DataCenterReplicable.java new file mode 100755 index 00000000000..9b33bdb009b --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/DataCenterReplicable.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.manager.DatacenterRackAwareActiveGossiper; +import org.tron.gossip.model.Base; + +/** + * Replicable implementation which does replicate data only in the same data center. + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + * @see Replicable + */ +public class DataCenterReplicable implements Replicable { + + @Override + public boolean shouldReplicate(LocalMember me, LocalMember destination, T message) { + if (!me.getProperties().containsKey(DatacenterRackAwareActiveGossiper.DATACENTER)) { + // replicate to others if I am not belong to any data center + return true; + } else if (!destination.getProperties() + .containsKey(DatacenterRackAwareActiveGossiper.DATACENTER)) { + // Do not replicate if the destination data center is not defined + return false; + } else { + return me.getProperties().get(DatacenterRackAwareActiveGossiper.DATACENTER) + .equals(destination.getProperties().get(DatacenterRackAwareActiveGossiper.DATACENTER)); + } + } +} diff --git a/src/main/java/org/tron/gossip/replication/NotReplicable.java b/src/main/java/org/tron/gossip/replication/NotReplicable.java new file mode 100755 index 00000000000..c460326aa5c --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/NotReplicable.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.Base; + +/** + * Replicable implementation which never replicates data on any node + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + * @see Replicable + */ +public class NotReplicable implements Replicable { + + @Override + public boolean shouldReplicate(LocalMember me, LocalMember destination, T message) { + return false; + } +} diff --git a/src/main/java/org/tron/gossip/replication/Replicable.java b/src/main/java/org/tron/gossip/replication/Replicable.java new file mode 100755 index 00000000000..651acd4d579 --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/Replicable.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.Base; + +/** + * This interface is used to determine whether a data item needs to be replicated to + * another gossip member. + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + */ +public interface Replicable { + /** + * Test for a given data item needs to be replicated. + * @param me node that the data item is going to transmit from. + * @param destination target node to replicate. + * @param message this parameter is currently ignored + * @return true if the data item needs to be replicated to the destination. Otherwise false. + */ + boolean shouldReplicate(LocalMember me, LocalMember destination, T message); +} diff --git a/src/main/java/org/tron/gossip/replication/WhiteListReplicable.java b/src/main/java/org/tron/gossip/replication/WhiteListReplicable.java new file mode 100755 index 00000000000..9627ae862f6 --- /dev/null +++ b/src/main/java/org/tron/gossip/replication/WhiteListReplicable.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.replication; + +import org.tron.gossip.LocalMember; +import org.tron.gossip.model.Base; + +import java.util.ArrayList; +import java.util.List; + +/** + * Replicable implementation which replicates data to given set of nodes. + * + * @param A subtype of the class {@link org.tron.gossip.model.Base} which uses this interface + * @see Replicable + */ +public class WhiteListReplicable implements Replicable { + + private final List whiteListMembers; + + public WhiteListReplicable(List whiteListMembers) { + if (whiteListMembers == null) { + this.whiteListMembers = new ArrayList<>(); + } else { + this.whiteListMembers = whiteListMembers; + } + } + + public List getWhiteListMembers() { + return whiteListMembers; + } + + @Override + public boolean shouldReplicate(LocalMember me, LocalMember destination, T message) { + return whiteListMembers.contains(destination); + } +} diff --git a/src/main/java/org/tron/gossip/secure/KeyTool.java b/src/main/java/org/tron/gossip/secure/KeyTool.java new file mode 100755 index 00000000000..faafe097dc1 --- /dev/null +++ b/src/main/java/org/tron/gossip/secure/KeyTool.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.secure; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; + +public class KeyTool { + + public static void generatePubandPrivateKeyFiles(String path, String id) + throws NoSuchAlgorithmException, NoSuchProviderException, IOException{ + SecureRandom r = new SecureRandom(); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA", "SUN"); + keyGen.initialize(1024, r); + KeyPair pair = keyGen.generateKeyPair(); + PrivateKey priv = pair.getPrivate(); + PublicKey pub = pair.getPublic(); + { + FileOutputStream sigfos = new FileOutputStream(new File(path, id)); + sigfos.write(priv.getEncoded()); + sigfos.close(); + } + { + FileOutputStream sigfos = new FileOutputStream(new File(path, id + ".pub")); + sigfos.write(pub.getEncoded()); + sigfos.close(); + } + } + + public static void main (String [] args) throws + NoSuchAlgorithmException, NoSuchProviderException, IOException{ + generatePubandPrivateKeyFiles(args[0], args[1]); + } +} diff --git a/src/main/java/org/tron/gossip/transport/AbstractTransportManager.java b/src/main/java/org/tron/gossip/transport/AbstractTransportManager.java new file mode 100755 index 00000000000..ca3df4b6c4d --- /dev/null +++ b/src/main/java/org/tron/gossip/transport/AbstractTransportManager.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.transport; + +import com.codahale.metrics.MetricRegistry; +import org.tron.gossip.manager.AbstractActiveGossiper; +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.utils.ReflectionUtils; +import org.apache.log4j.Logger; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Manage the protcol threads (active and passive gossipers). + */ +public abstract class AbstractTransportManager implements TransportManager { + + public static final Logger LOGGER = Logger.getLogger(AbstractTransportManager.class); + + private final ExecutorService gossipThreadExecutor; + private final AbstractActiveGossiper activeGossipThread; + protected final GossipManager gossipManager; + protected final GossipCore gossipCore; + + public AbstractTransportManager(GossipManager gossipManager, GossipCore gossipCore) { + this.gossipManager = gossipManager; + this.gossipCore = gossipCore; + gossipThreadExecutor = Executors.newCachedThreadPool(); + activeGossipThread = ReflectionUtils.constructWithReflection( + gossipManager.getSettings().getActiveGossipClass(), + new Class[]{ + GossipManager.class, GossipCore.class, MetricRegistry.class + }, + new Object[]{ + gossipManager, gossipCore, gossipManager.getRegistry() + }); + } + + // shut down threads etc. + @Override + public void shutdown() { + gossipThreadExecutor.shutdown(); + if (activeGossipThread != null) { + activeGossipThread.shutdown(); + } + try { + boolean result = gossipThreadExecutor.awaitTermination(10, TimeUnit.MILLISECONDS); + if (!result) { + // common when blocking patterns are used to read data from a socket. + LOGGER.warn("executor shutdown timed out"); + } + } catch (InterruptedException e) { + LOGGER.error(e); + } + gossipThreadExecutor.shutdownNow(); + } + + @Override + public void startActiveGossiper() { + activeGossipThread.init(); + } + + @Override + public abstract void startEndpoint(); +} diff --git a/src/main/java/org/tron/gossip/transport/TransportManager.java b/src/main/java/org/tron/gossip/transport/TransportManager.java new file mode 100755 index 00000000000..ab87ad6b339 --- /dev/null +++ b/src/main/java/org/tron/gossip/transport/TransportManager.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.transport; + +import java.io.IOException; +import java.net.URI; + +/** interface for manager that sends and receives messages that have already been serialized. */ +public interface TransportManager { + + /** starts the active gossip thread responsible for reaching out to remote nodes. Not related to `startEndpoint()` */ + void startActiveGossiper(); + + /** starts the passive gossip thread that receives messages from remote nodes. Not related to `startActiveGossiper()` */ + void startEndpoint(); + + /** attempts to shutdown all threads. */ + void shutdown(); + + /** sends a payload to an endpoint. */ + void send(URI endpoint, byte[] buf) throws IOException; + + /** gets the next payload being sent to this node */ + byte[] read() throws IOException; +} diff --git a/src/main/java/org/tron/gossip/transport/udp/UdpTransportManager.java b/src/main/java/org/tron/gossip/transport/udp/UdpTransportManager.java new file mode 100755 index 00000000000..738d8c8f6e0 --- /dev/null +++ b/src/main/java/org/tron/gossip/transport/udp/UdpTransportManager.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.transport.udp; + +import org.slf4j.LoggerFactory; +import org.tron.gossip.manager.GossipCore; +import org.tron.gossip.manager.GossipManager; +import org.tron.gossip.model.Base; +import org.tron.gossip.transport.AbstractTransportManager; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.net.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class is constructed by reflection in GossipManager. + * It manages transport (byte read/write) operations over UDP. + */ +public class UdpTransportManager extends AbstractTransportManager implements Runnable { + + public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger("UdpTransportManager"); + /** The socket used for the passive thread of the gossip service. */ + private final DatagramSocket server; + + private final int soTimeout; + + private final Thread me; + + private final AtomicBoolean keepRunning = new AtomicBoolean(true); + + /** required for reflection to work! */ + public UdpTransportManager(GossipManager gossipManager, GossipCore gossipCore) { + super(gossipManager, gossipCore); + soTimeout = gossipManager.getSettings().getGossipInterval() * 2; + try { + SocketAddress socketAddress = new InetSocketAddress(gossipManager.getMyself().getUri().getHost(), + gossipManager.getMyself().getUri().getPort()); + server = new DatagramSocket(socketAddress); + } catch (SocketException ex) { + LOGGER.warn(ex.toString()); + throw new RuntimeException(ex); + } + me = new Thread(this); + } + + @Override + public void run() { + while (keepRunning.get()) { + try { + byte[] buf = read(); + try { + Base message = gossipManager.getProtocolManager().read(buf); + gossipCore.receive(message); + //TODO this is suspect + gossipManager.getMemberStateRefresher().run(); + } catch (RuntimeException ex) {//TODO trap json exception + LOGGER.error("Unable to process message", ex); + } + } catch (IOException e) { + // InterruptedException are completely normal here because of the blocking lifecycle. + if (!(e.getCause() instanceof InterruptedException)) { + LOGGER.error(e.toString()); + } + keepRunning.set(false); + } + } + } + + @Override + public void shutdown() { + keepRunning.set(false); + server.close(); + super.shutdown(); + me.interrupt(); + } + + /** + * blocking read a message. + * @return buffer of message contents. + * @throws IOException + */ + public byte[] read() throws IOException { + byte[] buf = new byte[server.getReceiveBufferSize()]; + DatagramPacket p = new DatagramPacket(buf, buf.length); + server.receive(p); + debug(p.getData()); + return p.getData(); + } + + @Override + public void send(URI endpoint, byte[] buf) throws IOException { + // todo: investigate UDP socket reuse. It would save a little setup/teardown time wrt to the local socket. + try (DatagramSocket socket = new DatagramSocket()){ + socket.setSoTimeout(soTimeout); + InetAddress dest = InetAddress.getByName(endpoint.getHost()); + DatagramPacket payload = new DatagramPacket(buf, buf.length, dest, endpoint.getPort()); + socket.send(payload); + } + } + + private void debug(byte[] jsonBytes) { + if (LOGGER.isDebugEnabled()){ + String receivedMessage = new String(jsonBytes); + LOGGER.debug("Received message ( bytes): " + receivedMessage); + } + } + + @Override + public void startEndpoint() { + me.start(); + } + +} diff --git a/src/main/java/org/tron/gossip/udp/Trackable.java b/src/main/java/org/tron/gossip/udp/Trackable.java new file mode 100755 index 00000000000..239d8771a40 --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/Trackable.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +public interface Trackable { + + String getUriFrom(); + + void setUriFrom(String uriFrom); + + String getUuid(); + + void setUuid(String uuid); + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpActiveGossipMessage.java b/src/main/java/org/tron/gossip/udp/UdpActiveGossipMessage.java new file mode 100755 index 00000000000..81f0aba3bc3 --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpActiveGossipMessage.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.ActiveGossipMessage; + +public class UdpActiveGossipMessage extends ActiveGossipMessage implements Trackable { + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "UdpActiveGossipMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getMembers()=" + + getMembers() + "]"; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpActiveGossipOk.java b/src/main/java/org/tron/gossip/udp/UdpActiveGossipOk.java new file mode 100755 index 00000000000..02a087caa52 --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpActiveGossipOk.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.ActiveGossipOk; + +public class UdpActiveGossipOk extends ActiveGossipOk implements Trackable { + + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpNotAMemberFault.java b/src/main/java/org/tron/gossip/udp/UdpNotAMemberFault.java new file mode 100755 index 00000000000..b86653a879c --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpNotAMemberFault.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.NotAMemberFault; + +public class UdpNotAMemberFault extends NotAMemberFault implements Trackable{ + + public UdpNotAMemberFault(){ + + } + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpPerNodeDataBulkMessage.java b/src/main/java/org/tron/gossip/udp/UdpPerNodeDataBulkMessage.java new file mode 100755 index 00000000000..66289dd186a --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpPerNodeDataBulkMessage.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.PerNodeDataBulkMessage; + +public class UdpPerNodeDataBulkMessage extends PerNodeDataBulkMessage implements Trackable { + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "UdpGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + + ", messages=[" + super.toString() + "] ]"; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpPerNodeDataMessage.java b/src/main/java/org/tron/gossip/udp/UdpPerNodeDataMessage.java new file mode 100755 index 00000000000..b5df42f7ddb --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpPerNodeDataMessage.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.PerNodeDataMessage; + +public class UdpPerNodeDataMessage extends PerNodeDataMessage implements Trackable { + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "UdpGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + + ", getReplicable()=" + getReplicable() + "]"; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpSharedDataBulkMessage.java b/src/main/java/org/tron/gossip/udp/UdpSharedDataBulkMessage.java new file mode 100755 index 00000000000..5d7e960a164 --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpSharedDataBulkMessage.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.SharedDataBulkMessage; + +public class UdpSharedDataBulkMessage extends SharedDataBulkMessage implements Trackable { + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "UdpSharedGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getNodeId()=" + + ", messages=[" + super.toString() + "] ]"; + } + +} diff --git a/src/main/java/org/tron/gossip/udp/UdpSharedDataMessage.java b/src/main/java/org/tron/gossip/udp/UdpSharedDataMessage.java new file mode 100755 index 00000000000..98fbcd3bb6f --- /dev/null +++ b/src/main/java/org/tron/gossip/udp/UdpSharedDataMessage.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.udp; + +import org.tron.gossip.model.SharedDataMessage; + +public class UdpSharedDataMessage extends SharedDataMessage implements Trackable { + + private String uriFrom; + private String uuid; + + public String getUriFrom() { + return uriFrom; + } + + public void setUriFrom(String uriFrom) { + this.uriFrom = uriFrom; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String toString() { + return "UdpSharedGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getNodeId()=" + + getNodeId() + ", getKey()=" + getKey() + ", getPayload()=" + getPayload() + + ", getTimestamp()=" + getTimestamp() + ", getExpireAt()=" + getExpireAt() + + ", getReplicable()=" + getReplicable() + "]"; + } + +} diff --git a/src/main/java/org/tron/gossip/utils/ReflectionUtils.java b/src/main/java/org/tron/gossip/utils/ReflectionUtils.java new file mode 100755 index 00000000000..660f0598cd2 --- /dev/null +++ b/src/main/java/org/tron/gossip/utils/ReflectionUtils.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.tron.gossip.utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class ReflectionUtils { + + /** + * Create an instance of a thing. This method essentially makes code more readable by handing the various exception + * trapping. + * @param className + * @param constructorTypes + * @param constructorArgs + * @param + * @return constructed instance of a thing. + */ + @SuppressWarnings("unchecked") + public static T constructWithReflection(String className, Class[] constructorTypes, Object[] constructorArgs) { + try { + Constructor c = Class.forName(className).getConstructor(constructorTypes); + c.setAccessible(true); + return (T) c.newInstance(constructorArgs); + } catch (InvocationTargetException e) { + // catch ITE and throw the target if it is a RTE. + if (e.getTargetException() != null && RuntimeException.class.isAssignableFrom(e.getTargetException().getClass())) { + throw (RuntimeException) e.getTargetException(); + } else { + throw new RuntimeException(e); + } + } catch (ReflectiveOperationException others) { + // Note: No class in the above list should be a descendent of RuntimeException. Otherwise, we're just wrapping + // and making stack traces confusing. + throw new RuntimeException(others); + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 841b735c049..3562f448da5 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -65,6 +65,11 @@ + + + + + diff --git a/src/test/java/org/tron/gossip/GossipTest.java b/src/test/java/org/tron/gossip/GossipTest.java new file mode 100644 index 00000000000..28c07d8eeb6 --- /dev/null +++ b/src/test/java/org/tron/gossip/GossipTest.java @@ -0,0 +1,51 @@ +/* + * java-tron is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * java-tron 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.tron.gossip; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.tron.gossip.example.StandNode; +import org.tron.overlay.message.Message; +import org.tron.overlay.message.Type; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class GossipTest { + private static StandNode standNode = null; + private final static String CLUSTER = "mycluster"; + private Semaphore lock = new Semaphore(0); + private Object sharedData = null; + + @BeforeClass + public static void init() { + standNode = new StandNode(CLUSTER, "udp://localhost:10000", "0"); + } + + @Test + public void testGossipBroadcast() throws InterruptedException { + standNode.getGossipManager().registerSharedDataSubscriber((key, oldValue, newValue) -> { + if (key.equals("block")) { + sharedData = newValue; + } + }); + + Message message = new Message("test", Type.BLOCK); + standNode.broadcast(message); + lock.tryAcquire(10, TimeUnit.SECONDS); + Assert.assertEquals("test", sharedData); + } +}