From ac8c29d2c70bd45dd03b7a703d0f9f053da40ea0 Mon Sep 17 00:00:00 2001 From: yangchang Date: Wed, 10 Jan 2018 18:10:58 +0800 Subject: [PATCH 01/11] fix database --- .../java/org/tron/application/Module.java | 5 +-- src/main/java/org/tron/config/Configer.java | 34 ++++++++++++------- src/main/java/org/tron/core/Blockchain.java | 15 +++++--- src/main/java/org/tron/core/Constant.java | 11 ++++-- .../org/tron/core/TronBlockChainImpl.java | 2 +- .../java/org/tron/dbStore/BlockStores.java | 5 +-- src/main/java/org/tron/dbStore/UTXOStore.java | 5 +-- .../leveldb/LevelDbDataSourceImpl.java | 23 +++++++++---- .../java/org/tron/core/BlockchainTest.java | 2 +- .../org/tron/dbStore/BlockStoresTest.java | 6 ++-- .../java/org/tron/dbStore/UTXOStoreTest.java | 7 ++-- .../leveldb/LevelDbDataSourceImplTest.java | 10 +++--- 12 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/tron/application/Module.java b/src/main/java/org/tron/application/Module.java index 267a4315b5c..ac37c7cd0f9 100644 --- a/src/main/java/org/tron/application/Module.java +++ b/src/main/java/org/tron/application/Module.java @@ -5,6 +5,7 @@ import com.google.inject.Singleton; import org.tron.consensus.client.Client; import org.tron.consensus.server.Server; +import org.tron.core.Constant; import org.tron.storage.leveldb.LevelDbDataSourceImpl; import javax.inject.Named; @@ -35,7 +36,7 @@ public Server buildServer() { @Singleton @Named("transaction") public LevelDbDataSourceImpl buildTransactionDb() { - LevelDbDataSourceImpl db = new LevelDbDataSourceImpl(TRANSACTION_DB_NAME); + LevelDbDataSourceImpl db = new LevelDbDataSourceImpl(Constant.NORMAL,TRANSACTION_DB_NAME); db.initDB(); return db; } @@ -44,7 +45,7 @@ public LevelDbDataSourceImpl buildTransactionDb() { @Singleton @Named("block") public LevelDbDataSourceImpl buildBlockDb() { - LevelDbDataSourceImpl db = new LevelDbDataSourceImpl(BLOCK_DB_NAME); + LevelDbDataSourceImpl db = new LevelDbDataSourceImpl(Constant.NORMAL,BLOCK_DB_NAME); db.initDB(); return db; } diff --git a/src/main/java/org/tron/config/Configer.java b/src/main/java/org/tron/config/Configer.java index 42869998768..bba0c80fc82 100644 --- a/src/main/java/org/tron/config/Configer.java +++ b/src/main/java/org/tron/config/Configer.java @@ -29,14 +29,15 @@ public class Configer { private static final Logger logger = LoggerFactory.getLogger("Configer"); - public static String TRON_CONF = "tron.conf"; - private final static String DATABASE_DIRECTORY = "database.directory"; + public static String TRON_CONF = Constant.NORMAL_CONF; + private final static String DATABASE_DIRECTORY = Constant.DATABASE_DIR; + private static String generatedNodePrivateKey; - private static Config config; static { try { - File file = new File(Configer.getConf().getString(DATABASE_DIRECTORY), "nodeId.properties"); + File file = new File(Configer.getConf().getString + (DATABASE_DIRECTORY), "nodeId.properties"); Properties props = new Properties(); if (file.canRead()) { try (Reader r = new FileReader(file)) { @@ -50,15 +51,16 @@ public class Configer { String nodeIdPrivateKey = ByteArray.toHexString(privKeyBytes); props.setProperty("nodeIdPrivateKey", nodeIdPrivateKey); - props.setProperty("nodeId", Hex.toHexString(key.getNodeId())); - + props.setProperty("nodeId", Hex.toHexString(key.getNodeId + ())); file.getParentFile().mkdirs(); - try (Writer w = new FileWriter(file)) { props.store(w, "Generated NodeID."); } - logger.info("New nodeID generated: " + props.getProperty ("nodeId")); - logger.info("Generated nodeID and its private key stored " + "in " + file); + logger.info("New nodeID generated: " + props.getProperty + ("nodeId")); + logger.info("Generated nodeID and its private key stored " + + "in " + file); } generatedNodePrivateKey = props.getProperty("nodeIdPrivateKey"); } catch (IOException e) { @@ -67,10 +69,18 @@ public class Configer { } public static Config getConf() { - if (config == null) { - config = ConfigFactory.load(TRON_CONF); + return ConfigFactory.load(TRON_CONF); + } + + public static Config getConf(String conf) { + + if (conf==null||"".equals(conf)){ + return ConfigFactory.load(TRON_CONF); } - return config; + else { + return ConfigFactory.load(conf); + } + } public static ECKey getMyKey() { diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index 6d4ba168d4f..63de451c2fc 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -20,6 +20,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tron.config.Configer; import org.tron.consensus.client.Client; import org.tron.crypto.ECKey; import org.tron.example.Tron; @@ -46,11 +47,11 @@ import static org.tron.core.Constant.BLOCK_DB_NAME; import static org.tron.core.Constant.LAST_HASH; -import static org.tron.storage.leveldb.LevelDbDataSourceImpl.databaseName; public class Blockchain { public static final String GENESIS_COINBASE_DATA = "0x10"; + public static String parentName=Constant.NORMAL; public static final Logger logger = LoggerFactory.getLogger("BlockChain"); private LevelDbDataSourceImpl blockDB = null; @@ -68,7 +69,7 @@ public class Blockchain { */ public Blockchain(String address, String type) { if (dbExists()) { - blockDB = new LevelDbDataSourceImpl(BLOCK_DB_NAME); + blockDB = new LevelDbDataSourceImpl(parentName,BLOCK_DB_NAME); blockDB.initDB(); this.lastHash = blockDB.getData(LAST_HASH); @@ -76,7 +77,7 @@ public Blockchain(String address, String type) { logger.info("load blockchain"); } else { - blockDB = new LevelDbDataSourceImpl(BLOCK_DB_NAME); + blockDB = new LevelDbDataSourceImpl(Constant.NORMAL,BLOCK_DB_NAME); blockDB.initDB(); InputStream is = getClass().getClassLoader().getResourceAsStream("genesis.json"); @@ -239,7 +240,13 @@ public HashMap findUTXO() { * @return boolean */ public static boolean dbExists() { - File file = new File(Paths.get(databaseName, BLOCK_DB_NAME).toString()); + if (Constant.NORMAL==parentName){ + parentName= Configer.getConf(Constant.NORMAL_CONF).getString(Constant.DATABASE_DIR); + }else { + parentName=Configer.getConf(Constant.TEST_CONF).getString(Constant.DATABASE_DIR); + + } + File file = new File(Paths.get(parentName, BLOCK_DB_NAME).toString()); return file.exists(); } diff --git a/src/main/java/org/tron/core/Constant.java b/src/main/java/org/tron/core/Constant.java index 3dc4c4aa9f1..064c63216df 100644 --- a/src/main/java/org/tron/core/Constant.java +++ b/src/main/java/org/tron/core/Constant.java @@ -32,7 +32,12 @@ public class Constant { public final static Integer PARTITION = 0; //config - public final static String NORMAL_CONF = "tron.conf"; - public final static String TEST_CONF = "tron-test.conf"; - public final static String DATABASE_DIR = "database.directory"; + public final static String NORMAL="normal"; + public final static String TEST="test"; + public final static String NORMAL_CONF="tron.conf"; + public final static String TEST_CONF="tron-test.conf"; + public final static String DATABASE_DIR="database.directory"; + + + } diff --git a/src/main/java/org/tron/core/TronBlockChainImpl.java b/src/main/java/org/tron/core/TronBlockChainImpl.java index acc146cd54d..688bd503ee3 100644 --- a/src/main/java/org/tron/core/TronBlockChainImpl.java +++ b/src/main/java/org/tron/core/TronBlockChainImpl.java @@ -101,7 +101,7 @@ public synchronized void addBlockToChain(TronBlock.Block block) { * initDB level DB blockStoreInter */ private static LevelDbDataSourceImpl initBD() { - LevelDbDataSourceImpl levelDbDataSource = new LevelDbDataSourceImpl("blockStoreInter"); + LevelDbDataSourceImpl levelDbDataSource = new LevelDbDataSourceImpl(Constant.NORMAL,"blockStoreInter"); levelDbDataSource.initDB(); return levelDbDataSource; } diff --git a/src/main/java/org/tron/dbStore/BlockStores.java b/src/main/java/org/tron/dbStore/BlockStores.java index 7613baec6b0..6f233ce6aa4 100644 --- a/src/main/java/org/tron/dbStore/BlockStores.java +++ b/src/main/java/org/tron/dbStore/BlockStores.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tron.core.Constant; import org.tron.storage.leveldb.LevelDbDataSourceImpl; import static org.tron.core.Constant.BLOCK_DB_NAME; @@ -24,9 +25,9 @@ public class BlockStores { public static final Logger logger = LoggerFactory.getLogger("BlockStores"); private LevelDbDataSourceImpl blockDbDataSource; - public BlockStores() { + public BlockStores(String parentName,String childName) { - blockDbDataSource = new LevelDbDataSourceImpl(BLOCK_DB_NAME); + blockDbDataSource = new LevelDbDataSourceImpl(parentName,childName); blockDbDataSource.initDB(); } diff --git a/src/main/java/org/tron/dbStore/UTXOStore.java b/src/main/java/org/tron/dbStore/UTXOStore.java index 896b6eb22c5..9d2857080ae 100644 --- a/src/main/java/org/tron/dbStore/UTXOStore.java +++ b/src/main/java/org/tron/dbStore/UTXOStore.java @@ -16,6 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.tron.core.Constant; import org.tron.storage.leveldb.LevelDbDataSourceImpl; import java.util.Set; @@ -27,8 +28,8 @@ public class UTXOStore { public static final Logger logger = LoggerFactory.getLogger("UTXOStore"); private LevelDbDataSourceImpl uTXODataSource; - public UTXOStore( ) { - uTXODataSource=new LevelDbDataSourceImpl(TRANSACTION_DB_NAME); + public UTXOStore(String parentName,String childName ) { + uTXODataSource=new LevelDbDataSourceImpl( parentName, childName); uTXODataSource.initDB(); } diff --git a/src/main/java/org/tron/storage/leveldb/LevelDbDataSourceImpl.java b/src/main/java/org/tron/storage/leveldb/LevelDbDataSourceImpl.java index da84a364dd0..d6c8bc55d70 100644 --- a/src/main/java/org/tron/storage/leveldb/LevelDbDataSourceImpl.java +++ b/src/main/java/org/tron/storage/leveldb/LevelDbDataSourceImpl.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tron.config.Configer; +import org.tron.core.Constant; import org.tron.storage.DbSourceInter; import org.tron.utils.FileUtil; @@ -38,18 +39,26 @@ public class LevelDbDataSourceImpl implements DbSourceInter { private static final Logger logger = LoggerFactory.getLogger("dbStore"); + private String parentName; - private final static String LEVEL_DB_DIRECTORY = "database.directory"; - public final static String databaseName = Configer.getConf().getString(LEVEL_DB_DIRECTORY); + String dataBaseName; + DB database; + boolean alive; - private String dataBaseName; - private DB database; - private boolean alive; private ReadWriteLock resetDbLock = new ReentrantReadWriteLock(); + public LevelDbDataSourceImpl() { + } + + public LevelDbDataSourceImpl(String parentName ,String name) { + if (Constant.NORMAL==parentName){ + parentName=Configer.getConf(Constant.NORMAL_CONF).getString(Constant.DATABASE_DIR); + }else { + parentName=Configer.getConf(Constant.TEST_CONF).getString(Constant.DATABASE_DIR); - public LevelDbDataSourceImpl(String name) { + } + this.parentName=parentName; this.dataBaseName = name; logger.debug("New LevelDbDataSourceImpl: " + name); } @@ -97,7 +106,7 @@ public void initDB() { } private Path getDBPath() { - return Paths.get(databaseName, dataBaseName); + return Paths.get(parentName, dataBaseName); } public void resetDB() { diff --git a/src/test/java/org/tron/core/BlockchainTest.java b/src/test/java/org/tron/core/BlockchainTest.java index db873ca1f68..7c0c87aff42 100644 --- a/src/test/java/org/tron/core/BlockchainTest.java +++ b/src/test/java/org/tron/core/BlockchainTest.java @@ -138,7 +138,7 @@ public void testAddBlockToChain() { Block block = BlockUtils.newBlock(null, parentHash, difficulty, 0); - LevelDbDataSourceImpl levelDbDataSource = new LevelDbDataSourceImpl("blockStore_test"); + LevelDbDataSourceImpl levelDbDataSource = new LevelDbDataSourceImpl(Constant.TEST,"blockStore_test"); levelDbDataSource.initDB(); String lastHash = "lastHash"; byte[] key = lastHash.getBytes(); diff --git a/src/test/java/org/tron/dbStore/BlockStoresTest.java b/src/test/java/org/tron/dbStore/BlockStoresTest.java index 8f0b9b1c231..d51320a0ab8 100644 --- a/src/test/java/org/tron/dbStore/BlockStoresTest.java +++ b/src/test/java/org/tron/dbStore/BlockStoresTest.java @@ -21,13 +21,15 @@ import org.tron.core.Constant; import org.tron.utils.ByteArray; +import static org.tron.core.Constant.BLOCK_DB_NAME; + @Ignore public class BlockStoresTest { @Test public void saveBlock() { Configer.TRON_CONF = Constant.TEST_CONF; - BlockStores blockStores = new BlockStores(); + BlockStores blockStores = new BlockStores(Constant.TEST,BLOCK_DB_NAME); blockStores.saveBlock( "0001245".getBytes(),"xxdfrgds".getBytes()); blockStores.close(); } @@ -35,7 +37,7 @@ public void saveBlock() { @Test public void findBlockByHash() { Configer.TRON_CONF = Constant.TEST_CONF; - BlockStores blockStores = new BlockStores(); + BlockStores blockStores = new BlockStores(Constant.TEST,BLOCK_DB_NAME); byte[] blockByHash = blockStores.findBlockByHash("0001245".getBytes()); blockStores.close(); System.out.println(ByteArray.toStr(blockByHash)); diff --git a/src/test/java/org/tron/dbStore/UTXOStoreTest.java b/src/test/java/org/tron/dbStore/UTXOStoreTest.java index 518466eddc7..0748a6a6a6a 100644 --- a/src/test/java/org/tron/dbStore/UTXOStoreTest.java +++ b/src/test/java/org/tron/dbStore/UTXOStoreTest.java @@ -16,8 +16,11 @@ package org.tron.dbStore; import org.junit.Ignore; import org.junit.Test; +import org.tron.core.Constant; import org.tron.utils.ByteArray; +import static org.tron.core.Constant.BLOCK_DB_NAME; + @Ignore public class UTXOStoreTest { @@ -26,14 +29,14 @@ public class UTXOStoreTest { */ @Test public void saveUTXO() { - UTXOStore utxoStore = new UTXOStore(); + UTXOStore utxoStore = new UTXOStore(Constant.TEST,BLOCK_DB_NAME); utxoStore.saveUTXO("00012546".getBytes(),"300".getBytes()); utxoStore.close(); } @Test public void find() { - UTXOStore utxoStore = new UTXOStore(); + UTXOStore utxoStore = new UTXOStore(Constant.TEST,BLOCK_DB_NAME); byte[] bytes = utxoStore.find("00012546".getBytes()); utxoStore.close(); System.out.println(ByteArray.toStr(bytes)); diff --git a/src/test/java/org/tron/storage/leveldb/LevelDbDataSourceImplTest.java b/src/test/java/org/tron/storage/leveldb/LevelDbDataSourceImplTest.java index 29d7cdc1f41..7831dff8d00 100644 --- a/src/test/java/org/tron/storage/leveldb/LevelDbDataSourceImplTest.java +++ b/src/test/java/org/tron/storage/leveldb/LevelDbDataSourceImplTest.java @@ -14,8 +14,7 @@ public class LevelDbDataSourceImplTest { @Test public void testGet() { - Configer.TRON_CONF= Constant.TEST_CONF; - LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl("test"); + LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl(Constant.TEST,"test"); dataSource.initDB(); String key1="000134yyyhy"; byte[] key = key1.getBytes(); @@ -27,8 +26,7 @@ public void testGet() { @Test public void testPut() { - Configer.TRON_CONF= Constant.TEST_CONF; - LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl("test"); + LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl(Constant.TEST,"test"); dataSource.initDB(); String key1="000134yyyhy"; byte[] key = key1.getBytes(); @@ -46,8 +44,8 @@ public void testPut() { @Test public void testRest() { - Configer.TRON_CONF= Constant.TEST_CONF; - LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl("test"); + + LevelDbDataSourceImpl dataSource = new LevelDbDataSourceImpl(Constant.TEST_CONF,"test"); dataSource.resetDB(); dataSource.closeDB(); } From 6bd5f743bfb462e4ccfd4230e52bd3a8969cdd3a Mon Sep 17 00:00:00 2001 From: "Sungjun, Lee" Date: Wed, 10 Jan 2018 21:18:26 +0900 Subject: [PATCH 02/11] First of all. -> First of all, --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f84cb995d91..234fc4edc24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ please let us know if anything feels wrong or incomplete. ### Pull requests -First of all. java-tron follows [gitflow workflow]( +First of all, java-tron follows [gitflow workflow]( https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). Please open pull requests to the **develop** branch. Once approved, we will close the pull request and merge into master branch. From 789cc6a3bcf89deecb2d33069315e9d87046b4e8 Mon Sep 17 00:00:00 2001 From: ibmfaruk Date: Wed, 10 Jan 2018 16:35:19 +0300 Subject: [PATCH 03/11] kubernetes yaml files added which supports kafka --- .../kubernetes/kafka-controller.yaml | 27 ++++++++++++++++ infrastructure/kubernetes/kafka-service.yaml | 32 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 infrastructure/kubernetes/kafka-controller.yaml create mode 100644 infrastructure/kubernetes/kafka-service.yaml diff --git a/infrastructure/kubernetes/kafka-controller.yaml b/infrastructure/kubernetes/kafka-controller.yaml new file mode 100644 index 00000000000..c88814239ca --- /dev/null +++ b/infrastructure/kubernetes/kafka-controller.yaml @@ -0,0 +1,27 @@ +kind: ReplicationController +apiVersion: v1 +metadata: + name: kafka-controller +spec: + replicas: 1 + selector: + app: kafka + template: + metadata: + labels: + app: kafka + spec: + containers: + - name: kafka + image: wurstmeister/kafka + ports: + - containerPort: 9092 + env: + - name: KAFKA_ADVERTISED_HOST_NAME + value: kafka-service + - name: KAFKA_ZOOKEEPER_CONNECT + value: zook:2181 + - name: zookeeper + image: digitalwonderland/zookeeper + ports: + - containerPort: 2181 \ No newline at end of file diff --git a/infrastructure/kubernetes/kafka-service.yaml b/infrastructure/kubernetes/kafka-service.yaml new file mode 100644 index 00000000000..8d85a20e3cb --- /dev/null +++ b/infrastructure/kubernetes/kafka-service.yaml @@ -0,0 +1,32 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: zook + labels: + app: kafka +spec: + ports: + - port: 2181 + name: zookeeper-port + targetPort: 2181 + protocol: TCP + selector: + app: kafka +--- +apiVersion: v1 +kind: Service +metadata: + name: kafka-service + labels: + app: kafka +spec: + ports: + - port: 9092 + name: kafka-port + targetPort: 9092 + nodePort: 31000 + protocol: TCP + selector: + app: kafka + type: NodePort \ No newline at end of file From 8e2cea307d5a64ff6fe5951631c0883b8b50604d Mon Sep 17 00:00:00 2001 From: ibmfaruk Date: Wed, 10 Jan 2018 17:10:25 +0300 Subject: [PATCH 04/11] kafka support with kubernetes --- infrastructure/kubernetes/kafka-controller.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/kubernetes/kafka-controller.yaml b/infrastructure/kubernetes/kafka-controller.yaml index c88814239ca..6c3b359e82c 100644 --- a/infrastructure/kubernetes/kafka-controller.yaml +++ b/infrastructure/kubernetes/kafka-controller.yaml @@ -21,6 +21,8 @@ spec: value: kafka-service - name: KAFKA_ZOOKEEPER_CONNECT value: zook:2181 + - name: KAFKA_CREATE_TOPICS + value: "block:1:1,transaction:1:1" - name: zookeeper image: digitalwonderland/zookeeper ports: From 720e2b29a9684c0bbc38b297cd24371fafdeb962 Mon Sep 17 00:00:00 2001 From: Roy van Kaathoven Date: Wed, 10 Jan 2018 22:53:10 +0100 Subject: [PATCH 05/11] should refer to own blockchain instance instead of singleton instance --- src/main/java/org/tron/core/Blockchain.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index 63de451c2fc..68c5b4492db 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -303,7 +303,7 @@ public void addBlock(List transactions, Net net) { byte[] lastHash = blockDB.getData(LAST_HASH); ByteString parentHash = ByteString.copyFrom(lastHash); // getData number - long number = BlockUtils.getIncreaseNumber(Tron.getPeer().getBlockchain()); + long number = BlockUtils.getIncreaseNumber(this); // getData difficulty ByteString difficulty = ByteString.copyFromUtf8(Constant.DIFFICULTY); Block block = BlockUtils.newBlock(transactions, parentHash, difficulty, @@ -322,8 +322,7 @@ public void addBlock(List transactions) { byte[] lastHash = blockDB.getData(LAST_HASH); ByteString parentHash = ByteString.copyFrom(lastHash); // get number - long number = BlockUtils.getIncreaseNumber(Tron.getPeer() - .getBlockchain()); + long number = BlockUtils.getIncreaseNumber(this); // get difficulty ByteString difficulty = ByteString.copyFromUtf8(Constant.DIFFICULTY); Block block = BlockUtils.newBlock(transactions, parentHash, difficulty, From 3b3b56ee9764947020a3c7036db1ff02558944ef Mon Sep 17 00:00:00 2001 From: Jean-Philippe Quemener Date: Thu, 11 Jan 2018 00:43:49 +0100 Subject: [PATCH 06/11] refactor: make wallet less complex to use The Wallet class was unnecessary complex as one had to call init() for no real reason as this could also happen easily in the constructor. --- src/main/java/org/tron/peer/PeerBuilder.java | 4 +-- src/main/java/org/tron/wallet/Wallet.java | 29 ++++++------------- .../java/org/tron/core/BlockchainTest.java | 2 -- src/test/java/org/tron/core/WalletTest.java | 1 - 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/tron/peer/PeerBuilder.java b/src/main/java/org/tron/peer/PeerBuilder.java index 28cc754553f..abeff84bd21 100644 --- a/src/main/java/org/tron/peer/PeerBuilder.java +++ b/src/main/java/org/tron/peer/PeerBuilder.java @@ -2,7 +2,6 @@ import com.google.inject.Injector; import org.tron.consensus.client.Client; -import org.tron.consensus.server.Server; import org.tron.core.Blockchain; import org.tron.core.UTXOSet; import org.tron.crypto.ECKey; @@ -49,8 +48,7 @@ private void buildUTXOSet() { private void buildWallet() { if (key == null) throw new IllegalStateException("Key must be set before building the wallet"); - wallet = new Wallet(); - wallet.init(key); + wallet = new Wallet(key); } public PeerBuilder setType(String type) { diff --git a/src/main/java/org/tron/wallet/Wallet.java b/src/main/java/org/tron/wallet/Wallet.java index d3dd1d236f2..5b54d14938a 100644 --- a/src/main/java/org/tron/wallet/Wallet.java +++ b/src/main/java/org/tron/wallet/Wallet.java @@ -21,45 +21,34 @@ import org.tron.utils.Utils; public class Wallet { + private static final Logger logger = LoggerFactory.getLogger("Wallet"); - private ECKey ecKey; - private byte[] address; + private final ECKey ecKey; /** - * getData a new wallet key + * Creates a new Wallet with a random ECKey */ - public void init() { + public Wallet() { this.ecKey = new ECKey(Utils.getRandom()); - address = this.ecKey.getAddress(); } /** - * getData a wallet by the key + * Creates a Wallet with an existing ECKey * - * @param ecKey keypair + * @param ECKey ecKey Existing Key */ - public void init(ECKey ecKey) { + public Wallet(final ECKey ecKey) { this.ecKey = ecKey; - address = this.ecKey.getAddress(); - - logger.info("wallet address: {}", ByteArray.toHexString(address)); + logger.info("wallet address: {}", ByteArray.toHexString(this.ecKey.getAddress())); } public ECKey getEcKey() { return ecKey; } - public void setEcKey(ECKey ecKey) { - this.ecKey = ecKey; - } - public byte[] getAddress() { - return address; - } - - public void setAddress(byte[] address) { - this.address = address; + return ecKey.getAddress(); } } diff --git a/src/test/java/org/tron/core/BlockchainTest.java b/src/test/java/org/tron/core/BlockchainTest.java index 7c0c87aff42..c7bd454fe2d 100644 --- a/src/test/java/org/tron/core/BlockchainTest.java +++ b/src/test/java/org/tron/core/BlockchainTest.java @@ -107,7 +107,6 @@ public void testDBExists() { public void testFindUTXO() { long testAmount = 10; Wallet wallet = new Wallet(); - wallet.init(); SpendableOutputs spendableOutputs = new SpendableOutputs(); spendableOutputs.setAmount(testAmount + 1); spendableOutputs.setUnspentOutputs(new HashMap<>()); @@ -134,7 +133,6 @@ public void testAddBlockToChain() { ("2001")); Wallet wallet = new Wallet(); - wallet.init(); Block block = BlockUtils.newBlock(null, parentHash, difficulty, 0); diff --git a/src/test/java/org/tron/core/WalletTest.java b/src/test/java/org/tron/core/WalletTest.java index ad574711ffc..d1ceed89369 100644 --- a/src/test/java/org/tron/core/WalletTest.java +++ b/src/test/java/org/tron/core/WalletTest.java @@ -26,7 +26,6 @@ public class WalletTest { @Test public void testWallet() { Wallet wallet = new Wallet(); - wallet.init(); logger.info("wallet address = {}", ByteArray.toHexString(wallet .getAddress())); From 30519c5a3d82cb438e8eec54e43e6693e1b8e335 Mon Sep 17 00:00:00 2001 From: Sean Robbins Date: Thu, 11 Jan 2018 01:13:05 +0000 Subject: [PATCH 07/11] Inject BlockDB dependency when initializing Blockchain Mock BlockDB in BlockchainTest --- src/main/java/org/tron/core/Blockchain.java | 21 +++++------ src/main/java/org/tron/peer/PeerBuilder.java | 9 ++++- .../java/org/tron/core/BlockchainTest.java | 35 ++++++++++++------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index 68c5b4492db..bf95e5fab3b 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -54,7 +54,7 @@ public class Blockchain { public static String parentName=Constant.NORMAL; public static final Logger logger = LoggerFactory.getLogger("BlockChain"); - private LevelDbDataSourceImpl blockDB = null; + private LevelDbDataSourceImpl blockDB; private PendingState pendingState = new PendingStateImpl(); private byte[] lastHash; @@ -67,19 +67,11 @@ public class Blockchain { * * @param address wallet address */ - public Blockchain(String address, String type) { - if (dbExists()) { - blockDB = new LevelDbDataSourceImpl(parentName,BLOCK_DB_NAME); - blockDB.initDB(); - - this.lastHash = blockDB.getData(LAST_HASH); - this.currentHash = this.lastHash; - - logger.info("load blockchain"); - } else { - blockDB = new LevelDbDataSourceImpl(Constant.NORMAL,BLOCK_DB_NAME); - blockDB.initDB(); + public Blockchain(LevelDbDataSourceImpl blockDB, String address, String type) { + this.blockDB = blockDB; + this.lastHash = blockDB.getData(LAST_HASH); + if(this.lastHash == null) { InputStream is = getClass().getClassLoader().getResourceAsStream("genesis.json"); String json = null; try { @@ -126,6 +118,9 @@ public Blockchain(String address, String type) { } logger.info("new blockchain"); + } else { + this.currentHash = this.lastHash; + logger.info("load blockchain"); } } diff --git a/src/main/java/org/tron/peer/PeerBuilder.java b/src/main/java/org/tron/peer/PeerBuilder.java index abeff84bd21..0794e089a4e 100644 --- a/src/main/java/org/tron/peer/PeerBuilder.java +++ b/src/main/java/org/tron/peer/PeerBuilder.java @@ -1,10 +1,13 @@ package org.tron.peer; import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; import org.tron.consensus.client.Client; import org.tron.core.Blockchain; import org.tron.core.UTXOSet; import org.tron.crypto.ECKey; +import org.tron.storage.leveldb.LevelDbDataSourceImpl; import org.tron.utils.ByteArray; import org.tron.wallet.Wallet; @@ -33,7 +36,11 @@ private void buildBlockchain() { if (wallet == null) throw new IllegalStateException("Wallet must be set before building the blockchain"); if (type == null) throw new IllegalStateException("Type must be set before building the blockchain"); - blockchain = new Blockchain(ByteArray.toHexString(wallet.getAddress()), this.type); + blockchain = new Blockchain( + injector.getInstance(Key.get(LevelDbDataSourceImpl.class, Names.named("block"))), + ByteArray.toHexString(wallet.getAddress()), + this.type + ); blockchain.setClient(injector.getInstance(Client.class)); } diff --git a/src/test/java/org/tron/core/BlockchainTest.java b/src/test/java/org/tron/core/BlockchainTest.java index c7bd454fe2d..0f86621a96d 100644 --- a/src/test/java/org/tron/core/BlockchainTest.java +++ b/src/test/java/org/tron/core/BlockchainTest.java @@ -14,9 +14,12 @@ */ package org.tron.core; +import com.alibaba.fastjson.JSON; +import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; @@ -29,26 +32,32 @@ import org.tron.utils.ByteArray; import org.tron.wallet.Wallet; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.tron.core.Blockchain.GENESIS_COINBASE_DATA; import static org.tron.core.Blockchain.dbExists; +import static org.tron.core.Constant.LAST_HASH; import static org.tron.utils.ByteArray.toHexString; public class BlockchainTest { private static final Logger logger = LoggerFactory.getLogger("Test"); private static Blockchain blockchain; - - @BeforeClass - public static void init() { - blockchain = new Blockchain - ("0304f784e4e7bae517bcab94c3e0c9214fb4ac7ff9d7d5a937d1f40031f87b85","normal"); - } - - @AfterClass - public static void teardown() { - blockchain.getBlockDB().closeDB(); + private static LevelDbDataSourceImpl mockBlockDB; + + @Before + public void setup() throws IOException { + mockBlockDB = Mockito.mock(LevelDbDataSourceImpl.class); + Mockito.when(mockBlockDB.getData(eq(LAST_HASH))).thenReturn(null); + Mockito.when(mockBlockDB.getData(any())).thenReturn(ByteArray.fromString("")); + blockchain = new Blockchain( + mockBlockDB, + "0304f784e4e7bae517bcab94c3e0c9214fb4ac7ff9d7d5a937d1f40031f87b85", + "normal" + ); } @Test From e619b49dfa65603dcc6f168a034a0908d6ac7cf1 Mon Sep 17 00:00:00 2001 From: Sean Robbins Date: Thu, 11 Jan 2018 08:53:50 +0000 Subject: [PATCH 08/11] Remove dbExists logic, as now rely on the Dependency Injection for BlockDB --- src/main/java/org/tron/core/Blockchain.java | 35 ------------------- .../java/org/tron/core/BlockchainTest.java | 16 ++------- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index bf95e5fab3b..f8a874bc7ca 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -124,24 +124,6 @@ public Blockchain(LevelDbDataSourceImpl blockDB, String address, String type) { } } - /** - * create blockchain by db source - */ - @Inject - public Blockchain(@Named("block") LevelDbDataSourceImpl blockDb) { - if (!dbExists()) { - logger.info("no existing blockchain found. please create one first"); - throw new IllegalStateException("No existing blockchain found. please create one first"); - } - - blockDB = blockDb; - - this.lastHash = blockDB.getData(LAST_HASH); - this.currentHash = this.lastHash; - - logger.info("load blockchain"); - } - /** * find transaction by id * @@ -229,23 +211,6 @@ public HashMap findUTXO() { return utxo; } - /** - * Checks if the database file exists - * - * @return boolean - */ - public static boolean dbExists() { - if (Constant.NORMAL==parentName){ - parentName= Configer.getConf(Constant.NORMAL_CONF).getString(Constant.DATABASE_DIR); - }else { - parentName=Configer.getConf(Constant.TEST_CONF).getString(Constant.DATABASE_DIR); - - } - File file = new File(Paths.get(parentName, BLOCK_DB_NAME).toString()); - return file.exists(); - } - - /** * add a block into database * diff --git a/src/test/java/org/tron/core/BlockchainTest.java b/src/test/java/org/tron/core/BlockchainTest.java index 0f86621a96d..1aa628e50c1 100644 --- a/src/test/java/org/tron/core/BlockchainTest.java +++ b/src/test/java/org/tron/core/BlockchainTest.java @@ -14,13 +14,9 @@ */ package org.tron.core; -import com.alibaba.fastjson.JSON; -import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; @@ -33,13 +29,12 @@ import org.tron.wallet.Wallet; import java.io.IOException; -import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.tron.core.Blockchain.GENESIS_COINBASE_DATA; -import static org.tron.core.Blockchain.dbExists; import static org.tron.core.Constant.LAST_HASH; import static org.tron.utils.ByteArray.toHexString; @@ -107,11 +102,6 @@ public void testFindTransaction() { logger.info("{}", TransactionUtils.toPrintString(transaction)); } - @Test - public void testDBExists() { - logger.info("test dbStore exists: {}", dbExists()); - } - @Test public void testFindUTXO() { long testAmount = 10; From 9f6c8abe78369aa68ae1e7d291592a018d286402 Mon Sep 17 00:00:00 2001 From: Sean Robbins Date: Thu, 11 Jan 2018 09:02:11 +0000 Subject: [PATCH 09/11] Update java doc on Blockchain constructor --- src/main/java/org/tron/core/Blockchain.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index f8a874bc7ca..4762662080e 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -20,7 +20,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.tron.config.Configer; import org.tron.consensus.client.Client; import org.tron.crypto.ECKey; import org.tron.example.Tron; @@ -37,15 +36,10 @@ import org.tron.storage.leveldb.LevelDbDataSourceImpl; import org.tron.utils.ByteArray; -import javax.inject.Inject; -import javax.inject.Named; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Paths; import java.util.*; -import static org.tron.core.Constant.BLOCK_DB_NAME; import static org.tron.core.Constant.LAST_HASH; public class Blockchain { @@ -65,7 +59,10 @@ public class Blockchain { /** * create new blockchain * + * @param blockDB block database * @param address wallet address + * @param type peer type + * */ public Blockchain(LevelDbDataSourceImpl blockDB, String address, String type) { this.blockDB = blockDB; From f3d65f519416b4d616b58a97267c191ed5d7d656 Mon Sep 17 00:00:00 2001 From: sasaxie Date: Fri, 12 Jan 2018 14:47:14 +0800 Subject: [PATCH 10/11] fix conflict --- .../org/tron/command/ConsensusCommand.java | 2 +- .../client/BlockchainClientListener.java | 53 ++++++++++++++++++ src/main/java/org/tron/core/Blockchain.java | 55 ++++++------------- .../tron/core/events/BlockchainListener.java | 23 ++++++++ src/main/java/org/tron/example/Tron.java | 9 +-- src/main/java/org/tron/peer/PeerBuilder.java | 11 ++-- 6 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 src/main/java/org/tron/consensus/client/BlockchainClientListener.java create mode 100644 src/main/java/org/tron/core/events/BlockchainListener.java diff --git a/src/main/java/org/tron/command/ConsensusCommand.java b/src/main/java/org/tron/command/ConsensusCommand.java index 7e515ba8899..2e223b98564 100644 --- a/src/main/java/org/tron/command/ConsensusCommand.java +++ b/src/main/java/org/tron/command/ConsensusCommand.java @@ -51,7 +51,7 @@ public void putClient(String[] args) { } public void getClient(Peer peer) { - if (Tron.getPeer().getType().equals(PeerType.PEER_SERVER)) { + if (peer.getType().equals(PeerType.PEER_SERVER)) { client.getMessage(peer, MessageType.TRANSACTION); client.getMessage(peer, MessageType.BLOCK); } else { diff --git a/src/main/java/org/tron/consensus/client/BlockchainClientListener.java b/src/main/java/org/tron/consensus/client/BlockchainClientListener.java new file mode 100644 index 00000000000..1a13e6ad830 --- /dev/null +++ b/src/main/java/org/tron/consensus/client/BlockchainClientListener.java @@ -0,0 +1,53 @@ +package org.tron.consensus.client; + +import org.tron.core.events.BlockchainListener; +import org.tron.overlay.Net; +import org.tron.overlay.message.Message; +import org.tron.overlay.message.Type; +import org.tron.peer.Peer; +import org.tron.peer.PeerType; +import org.tron.protos.core.TronBlock; +import org.tron.utils.ByteArray; + +public class BlockchainClientListener implements BlockchainListener { + + private Client client; + private Peer peer; + + public BlockchainClientListener(Client client, Peer peer) { + this.client = client; + this.peer = peer; + } + + @Override + public void addBlock(TronBlock.Block block) { + String value = ByteArray.toHexString(block.toByteArray()); + + if (peer.getType().equals(PeerType.PEER_SERVER)) { + Message message = new Message(value, Type.BLOCK); + //net.broadcast(message); + client.putMessage1(message); // consensus: put message + } + } + + @Override + public void addBlockNet(TronBlock.Block block, Net net) { + if (peer.getType().equals(PeerType.PEER_SERVER)) { + String value = ByteArray.toHexString(block.toByteArray()); + Message message = new Message(value, Type.BLOCK); + net.broadcast(message); + } + } + + @Override + public void addGenesisBlock(TronBlock.Block block) { + if (peer.getType().equals(PeerType.PEER_SERVER)) { + String value = ByteArray.toHexString(block.toByteArray()); + Message message = new Message(value, Type.BLOCK); + client.putMessage1(message); // consensus: put message GenesisBlock + //Merely for the placeholders, no real meaning + Message time = new Message(value, Type.TRANSACTION); + client.putMessage1(time); + } + } +} diff --git a/src/main/java/org/tron/core/Blockchain.java b/src/main/java/org/tron/core/Blockchain.java index 4762662080e..335339a62a5 100644 --- a/src/main/java/org/tron/core/Blockchain.java +++ b/src/main/java/org/tron/core/Blockchain.java @@ -20,14 +20,10 @@ import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.tron.consensus.client.Client; +import org.tron.core.events.BlockchainListener; import org.tron.crypto.ECKey; -import org.tron.example.Tron; import org.tron.overlay.Net; -import org.tron.overlay.message.Message; -import org.tron.overlay.message.Type; import org.tron.peer.Peer; -import org.tron.peer.PeerType; import org.tron.protos.core.TronBlock.Block; import org.tron.protos.core.TronTXInput.TXInput; import org.tron.protos.core.TronTXOutput.TXOutput; @@ -45,7 +41,7 @@ public class Blockchain { public static final String GENESIS_COINBASE_DATA = "0x10"; - public static String parentName=Constant.NORMAL; + public static String parentName = Constant.NORMAL; public static final Logger logger = LoggerFactory.getLogger("BlockChain"); private LevelDbDataSourceImpl blockDB; @@ -54,21 +50,20 @@ public class Blockchain { private byte[] lastHash; private byte[] currentHash; - private Client client; + private List listeners = new ArrayList<>(); /** * create new blockchain * * @param blockDB block database * @param address wallet address - * @param type peer type - * + * @param type peer type */ public Blockchain(LevelDbDataSourceImpl blockDB, String address, String type) { this.blockDB = blockDB; this.lastHash = blockDB.getData(LAST_HASH); - if(this.lastHash == null) { + if (this.lastHash == null) { InputStream is = getClass().getClassLoader().getResourceAsStream("genesis.json"); String json = null; try { @@ -104,16 +99,10 @@ public Blockchain(LevelDbDataSourceImpl blockDB, String address, String type) { .toByteArray(); blockDB.putData(LAST_HASH, lastHash); - // put message to consensus - if (type.equals(PeerType.PEER_SERVER) && client != null) { - String value = ByteArray.toHexString(genesisBlock.toByteArray()); - Message message = new Message(value, Type.BLOCK); - client.putMessage1(message); // consensus: put message GenesisBlock - //Merely for the placeholders, no real meaning - Message time = new Message(value, Type.TRANSACTION); - client.putMessage1(time); - + for (BlockchainListener listener : listeners) { + listener.addGenesisBlock(genesisBlock); } + logger.info("new blockchain"); } else { this.currentHash = this.lastHash; @@ -266,11 +255,8 @@ public void addBlock(List transactions, Net net) { Block block = BlockUtils.newBlock(transactions, parentHash, difficulty, number); - String value = ByteArray.toHexString(block.toByteArray()); - - if (Tron.getPeer().getType().equals(PeerType.PEER_SERVER)) { - Message message = new Message(value, Type.BLOCK); - net.broadcast(message); + for (BlockchainListener listener : listeners) { + listener.addBlockNet(block, net); } } @@ -282,17 +268,10 @@ public void addBlock(List transactions) { long number = BlockUtils.getIncreaseNumber(this); // get difficulty ByteString difficulty = ByteString.copyFromUtf8(Constant.DIFFICULTY); - Block block = BlockUtils.newBlock(transactions, parentHash, difficulty, - number); + Block block = BlockUtils.newBlock(transactions, parentHash, difficulty, number); - String value = ByteArray.toHexString(block.toByteArray()); - // View the type of peer - //System.out.println(Tron.getPeer().getType()); - - if (Tron.getPeer().getType().equals(PeerType.PEER_SERVER) && client != null) { - Message message = new Message(value, Type.BLOCK); - //net.broadcast(message); - client.putMessage1(message); // consensus: put message + for (BlockchainListener listener : listeners) { + listener.addBlock(block); } } @@ -330,6 +309,10 @@ public void receiveBlock(Block block, UTXOSet utxoSet, Peer peer) { utxoSet.reindex(); } + public void addListener(BlockchainListener listener) { + this.listeners.add(listener); + } + public LevelDbDataSourceImpl getBlockDB() { return blockDB; } @@ -361,8 +344,4 @@ public byte[] getCurrentHash() { public void setCurrentHash(byte[] currentHash) { this.currentHash = currentHash; } - - public void setClient(Client client) { - this.client = client; - } } diff --git a/src/main/java/org/tron/core/events/BlockchainListener.java b/src/main/java/org/tron/core/events/BlockchainListener.java new file mode 100644 index 00000000000..9b59cba589d --- /dev/null +++ b/src/main/java/org/tron/core/events/BlockchainListener.java @@ -0,0 +1,23 @@ +package org.tron.core.events; + +import org.tron.overlay.Net; +import org.tron.protos.core.TronBlock; + +public interface BlockchainListener { + + /** + * New block added to blockchain + */ + void addBlock(TronBlock.Block block); + + /** + * New block added to blockchain + * includes net reference + */ + void addBlockNet(TronBlock.Block block, Net net); + + /** + * Genesis block added to blockchain + */ + void addGenesisBlock(TronBlock.Block block); +} diff --git a/src/main/java/org/tron/example/Tron.java b/src/main/java/org/tron/example/Tron.java index 459092d75ac..359e29af170 100644 --- a/src/main/java/org/tron/example/Tron.java +++ b/src/main/java/org/tron/example/Tron.java @@ -31,9 +31,6 @@ public class Tron { @Parameter(names = {"--type", "-t"}, validateWith = PeerType.class) private String type = "normal"; - private static Peer peer; - - public static void main(String[] args) { Tron tron = new Tron(); JCommander.newBuilder() @@ -51,7 +48,7 @@ public void run() { app.addService(new Server()); app.run(); - peer = app.getInjector().getInstance(PeerBuilder.class) + Peer peer = app.getInjector().getInstance(PeerBuilder.class) .setKey(Configer.getMyKey()) .setType(type) .build(); @@ -61,8 +58,4 @@ public void run() { Cli cli = new Cli(); cli.run(app); } - - public static Peer getPeer() { - return peer; - } } diff --git a/src/main/java/org/tron/peer/PeerBuilder.java b/src/main/java/org/tron/peer/PeerBuilder.java index 0794e089a4e..61cbbaa062e 100644 --- a/src/main/java/org/tron/peer/PeerBuilder.java +++ b/src/main/java/org/tron/peer/PeerBuilder.java @@ -3,6 +3,7 @@ import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.name.Names; +import org.tron.consensus.client.BlockchainClientListener; import org.tron.consensus.client.Client; import org.tron.core.Blockchain; import org.tron.core.UTXOSet; @@ -15,7 +16,7 @@ /** * Builds a peer - * + *

* Set the key and type before calling build */ public class PeerBuilder { @@ -38,10 +39,9 @@ private void buildBlockchain() { blockchain = new Blockchain( injector.getInstance(Key.get(LevelDbDataSourceImpl.class, Names.named("block"))), - ByteArray.toHexString(wallet.getAddress()), - this.type - ); - blockchain.setClient(injector.getInstance(Client.class)); + ByteArray.toHexString(wallet.getAddress()), + this.type + ); } private void buildUTXOSet() { @@ -74,6 +74,7 @@ public Peer build() { buildUTXOSet(); Peer peer = new Peer(type, blockchain, utxoSet, wallet, key); peer.setClient(injector.getInstance(Client.class)); + blockchain.addListener(new BlockchainClientListener(injector.getInstance(Client.class), peer)); return peer; } } From 76c571fd8890d4f8ff9db25a150f69a78a792a80 Mon Sep 17 00:00:00 2001 From: sasaxie Date: Fri, 12 Jan 2018 14:43:50 +0800 Subject: [PATCH 11/11] 1. Gossip sample example. 2. Unit tests. --- .gitignore | 5 + build.gradle | 6 + .../java/org/tron/gossip/GossipSettings.java | 282 ++++++++++++ .../java/org/tron/gossip/LocalMember.java | 71 ++++ src/main/java/org/tron/gossip/Member.java | 166 ++++++++ .../java/org/tron/gossip/RemoteMember.java | 47 ++ .../java/org/tron/gossip/StartupSettings.java | 231 ++++++++++ .../tron/gossip/accrual/FailureDetector.java | 80 ++++ src/main/java/org/tron/gossip/crdt/Crdt.java | 39 ++ .../tron/gossip/crdt/CrdtAddRemoveSet.java | 29 ++ .../tron/gossip/crdt/CrdtBiFunctionMerge.java | 55 +++ .../org/tron/gossip/crdt/CrdtCounter.java | 24 ++ .../java/org/tron/gossip/crdt/CrdtModule.java | 167 ++++++++ .../java/org/tron/gossip/crdt/CrdtSet.java | 26 ++ .../org/tron/gossip/crdt/GrowOnlyCounter.java | 119 ++++++ .../org/tron/gossip/crdt/GrowOnlySet.java | 157 +++++++ .../java/org/tron/gossip/crdt/LwwSet.java | 171 ++++++++ .../org/tron/gossip/crdt/MaxChangeSet.java | 117 +++++ src/main/java/org/tron/gossip/crdt/OrSet.java | 307 ++++++++++++++ .../java/org/tron/gossip/crdt/PNCounter.java | 139 ++++++ .../org/tron/gossip/crdt/TwoPhaseSet.java | 115 +++++ .../org/tron/gossip/event/GossipListener.java | 24 ++ .../org/tron/gossip/event/GossipState.java | 28 ++ .../gossip/event/data/DataEventConstants.java | 42 ++ .../gossip/event/data/DataEventManager.java | 102 +++++ .../data/UpdateNodeDataEventHandler.java | 37 ++ .../data/UpdateSharedDataEventHandler.java | 34 ++ .../org/tron/gossip/example/StandNode.java | 146 +++++++ .../org/tron/gossip/lock/LockManager.java | 318 ++++++++++++++ .../tron/gossip/lock/LockManagerSettings.java | 83 ++++ .../lock/exceptions/VoteFailedException.java | 43 ++ .../tron/gossip/lock/vote/MajorityVote.java | 169 ++++++++ .../gossip/lock/vote/RandomVoteSelector.java | 35 ++ .../java/org/tron/gossip/lock/vote/Vote.java | 70 +++ .../tron/gossip/lock/vote/VoteCandidate.java | 75 ++++ .../tron/gossip/lock/vote/VoteSelector.java | 33 ++ .../manager/AbstractActiveGossiper.java | 262 ++++++++++++ .../java/org/tron/gossip/manager/Clock.java | 25 ++ .../org/tron/gossip/manager/DataReaper.java | 85 ++++ .../DatacenterRackAwareActiveGossiper.java | 244 +++++++++++ .../org/tron/gossip/manager/GossipCore.java | 335 +++++++++++++++ .../gossip/manager/GossipCoreConstants.java | 29 ++ .../tron/gossip/manager/GossipManager.java | 401 ++++++++++++++++++ .../gossip/manager/GossipManagerBuilder.java | 139 ++++++ .../manager/GossipMemberStateRefresher.java | 157 +++++++ .../manager/PassiveGossipConstants.java | 23 + .../gossip/manager/RingStatePersister.java | 72 ++++ .../gossip/manager/SimpleActiveGossiper.java | 110 +++++ .../org/tron/gossip/manager/SystemClock.java | 32 ++ .../gossip/manager/UserDataPersister.java | 97 +++++ .../handlers/ActiveGossipMessageHandler.java | 82 ++++ .../manager/handlers/MessageHandler.java | 32 ++ .../handlers/MessageHandlerFactory.java | 57 +++ .../PerNodeDataBulkMessageHandler.java | 41 ++ .../handlers/PerNodeDataMessageHandler.java | 39 ++ .../manager/handlers/ResponseHandler.java | 42 ++ .../SharedDataBulkMessageHandler.java | 41 ++ .../handlers/SharedDataMessageHandler.java | 39 ++ .../handlers/ShutdownMessageHandler.java | 46 ++ .../manager/handlers/TypedMessageHandler.java | 51 +++ .../gossip/model/ActiveGossipMessage.java | 39 ++ .../org/tron/gossip/model/ActiveGossipOk.java | 22 + src/main/java/org/tron/gossip/model/Base.java | 49 +++ .../java/org/tron/gossip/model/Fault.java | 40 ++ .../java/org/tron/gossip/model/Member.java | 87 ++++ .../java/org/tron/gossip/model/Message.java | 22 + .../tron/gossip/model/NotAMemberFault.java | 29 ++ .../gossip/model/PerNodeDataBulkMessage.java | 39 ++ .../tron/gossip/model/PerNodeDataMessage.java | 79 ++++ .../java/org/tron/gossip/model/Response.java | 22 + .../gossip/model/SharedDataBulkMessage.java | 39 ++ .../tron/gossip/model/SharedDataMessage.java | 78 ++++ .../tron/gossip/model/ShutdownMessage.java | 51 +++ .../org/tron/gossip/model/SignedPayload.java | 36 ++ .../tron/gossip/protocol/ProtocolManager.java | 41 ++ .../protocol/json/JacksonProtocolManager.java | 126 ++++++ .../gossip/replication/AllReplicable.java | 36 ++ .../replication/BlackListReplicable.java | 52 +++ .../replication/DataCenterReplicable.java | 46 ++ .../gossip/replication/NotReplicable.java | 35 ++ .../tron/gossip/replication/Replicable.java | 38 ++ .../replication/WhiteListReplicable.java | 52 +++ .../java/org/tron/gossip/secure/KeyTool.java | 57 +++ .../transport/AbstractTransportManager.java | 83 ++++ .../gossip/transport/TransportManager.java | 40 ++ .../transport/udp/UdpTransportManager.java | 129 ++++++ .../java/org/tron/gossip/udp/Trackable.java | 30 ++ .../gossip/udp/UdpActiveGossipMessage.java | 49 +++ .../tron/gossip/udp/UdpActiveGossipOk.java | 44 ++ .../tron/gossip/udp/UdpNotAMemberFault.java | 46 ++ .../gossip/udp/UdpPerNodeDataBulkMessage.java | 49 +++ .../gossip/udp/UdpPerNodeDataMessage.java | 49 +++ .../gossip/udp/UdpSharedDataBulkMessage.java | 49 +++ .../tron/gossip/udp/UdpSharedDataMessage.java | 51 +++ .../tron/gossip/utils/ReflectionUtils.java | 53 +++ src/main/resources/logback.xml | 5 + src/test/java/org/tron/gossip/GossipTest.java | 51 +++ 97 files changed, 7916 insertions(+) create mode 100755 src/main/java/org/tron/gossip/GossipSettings.java create mode 100755 src/main/java/org/tron/gossip/LocalMember.java create mode 100755 src/main/java/org/tron/gossip/Member.java create mode 100755 src/main/java/org/tron/gossip/RemoteMember.java create mode 100755 src/main/java/org/tron/gossip/StartupSettings.java create mode 100755 src/main/java/org/tron/gossip/accrual/FailureDetector.java create mode 100755 src/main/java/org/tron/gossip/crdt/Crdt.java create mode 100755 src/main/java/org/tron/gossip/crdt/CrdtAddRemoveSet.java create mode 100755 src/main/java/org/tron/gossip/crdt/CrdtBiFunctionMerge.java create mode 100755 src/main/java/org/tron/gossip/crdt/CrdtCounter.java create mode 100755 src/main/java/org/tron/gossip/crdt/CrdtModule.java create mode 100755 src/main/java/org/tron/gossip/crdt/CrdtSet.java create mode 100755 src/main/java/org/tron/gossip/crdt/GrowOnlyCounter.java create mode 100755 src/main/java/org/tron/gossip/crdt/GrowOnlySet.java create mode 100755 src/main/java/org/tron/gossip/crdt/LwwSet.java create mode 100755 src/main/java/org/tron/gossip/crdt/MaxChangeSet.java create mode 100755 src/main/java/org/tron/gossip/crdt/OrSet.java create mode 100755 src/main/java/org/tron/gossip/crdt/PNCounter.java create mode 100755 src/main/java/org/tron/gossip/crdt/TwoPhaseSet.java create mode 100755 src/main/java/org/tron/gossip/event/GossipListener.java create mode 100755 src/main/java/org/tron/gossip/event/GossipState.java create mode 100755 src/main/java/org/tron/gossip/event/data/DataEventConstants.java create mode 100755 src/main/java/org/tron/gossip/event/data/DataEventManager.java create mode 100755 src/main/java/org/tron/gossip/event/data/UpdateNodeDataEventHandler.java create mode 100755 src/main/java/org/tron/gossip/event/data/UpdateSharedDataEventHandler.java create mode 100644 src/main/java/org/tron/gossip/example/StandNode.java create mode 100755 src/main/java/org/tron/gossip/lock/LockManager.java create mode 100755 src/main/java/org/tron/gossip/lock/LockManagerSettings.java create mode 100755 src/main/java/org/tron/gossip/lock/exceptions/VoteFailedException.java create mode 100755 src/main/java/org/tron/gossip/lock/vote/MajorityVote.java create mode 100755 src/main/java/org/tron/gossip/lock/vote/RandomVoteSelector.java create mode 100755 src/main/java/org/tron/gossip/lock/vote/Vote.java create mode 100755 src/main/java/org/tron/gossip/lock/vote/VoteCandidate.java create mode 100755 src/main/java/org/tron/gossip/lock/vote/VoteSelector.java create mode 100755 src/main/java/org/tron/gossip/manager/AbstractActiveGossiper.java create mode 100755 src/main/java/org/tron/gossip/manager/Clock.java create mode 100755 src/main/java/org/tron/gossip/manager/DataReaper.java create mode 100755 src/main/java/org/tron/gossip/manager/DatacenterRackAwareActiveGossiper.java create mode 100755 src/main/java/org/tron/gossip/manager/GossipCore.java create mode 100755 src/main/java/org/tron/gossip/manager/GossipCoreConstants.java create mode 100755 src/main/java/org/tron/gossip/manager/GossipManager.java create mode 100755 src/main/java/org/tron/gossip/manager/GossipManagerBuilder.java create mode 100755 src/main/java/org/tron/gossip/manager/GossipMemberStateRefresher.java create mode 100755 src/main/java/org/tron/gossip/manager/PassiveGossipConstants.java create mode 100755 src/main/java/org/tron/gossip/manager/RingStatePersister.java create mode 100755 src/main/java/org/tron/gossip/manager/SimpleActiveGossiper.java create mode 100755 src/main/java/org/tron/gossip/manager/SystemClock.java create mode 100755 src/main/java/org/tron/gossip/manager/UserDataPersister.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/ActiveGossipMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/MessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/MessageHandlerFactory.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/PerNodeDataBulkMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/PerNodeDataMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/ResponseHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/SharedDataBulkMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/SharedDataMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/ShutdownMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/manager/handlers/TypedMessageHandler.java create mode 100755 src/main/java/org/tron/gossip/model/ActiveGossipMessage.java create mode 100755 src/main/java/org/tron/gossip/model/ActiveGossipOk.java create mode 100755 src/main/java/org/tron/gossip/model/Base.java create mode 100755 src/main/java/org/tron/gossip/model/Fault.java create mode 100755 src/main/java/org/tron/gossip/model/Member.java create mode 100755 src/main/java/org/tron/gossip/model/Message.java create mode 100755 src/main/java/org/tron/gossip/model/NotAMemberFault.java create mode 100755 src/main/java/org/tron/gossip/model/PerNodeDataBulkMessage.java create mode 100755 src/main/java/org/tron/gossip/model/PerNodeDataMessage.java create mode 100755 src/main/java/org/tron/gossip/model/Response.java create mode 100755 src/main/java/org/tron/gossip/model/SharedDataBulkMessage.java create mode 100755 src/main/java/org/tron/gossip/model/SharedDataMessage.java create mode 100755 src/main/java/org/tron/gossip/model/ShutdownMessage.java create mode 100755 src/main/java/org/tron/gossip/model/SignedPayload.java create mode 100755 src/main/java/org/tron/gossip/protocol/ProtocolManager.java create mode 100755 src/main/java/org/tron/gossip/protocol/json/JacksonProtocolManager.java create mode 100755 src/main/java/org/tron/gossip/replication/AllReplicable.java create mode 100755 src/main/java/org/tron/gossip/replication/BlackListReplicable.java create mode 100755 src/main/java/org/tron/gossip/replication/DataCenterReplicable.java create mode 100755 src/main/java/org/tron/gossip/replication/NotReplicable.java create mode 100755 src/main/java/org/tron/gossip/replication/Replicable.java create mode 100755 src/main/java/org/tron/gossip/replication/WhiteListReplicable.java create mode 100755 src/main/java/org/tron/gossip/secure/KeyTool.java create mode 100755 src/main/java/org/tron/gossip/transport/AbstractTransportManager.java create mode 100755 src/main/java/org/tron/gossip/transport/TransportManager.java create mode 100755 src/main/java/org/tron/gossip/transport/udp/UdpTransportManager.java create mode 100755 src/main/java/org/tron/gossip/udp/Trackable.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpActiveGossipMessage.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpActiveGossipOk.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpNotAMemberFault.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpPerNodeDataBulkMessage.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpPerNodeDataMessage.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpSharedDataBulkMessage.java create mode 100755 src/main/java/org/tron/gossip/udp/UdpSharedDataMessage.java create mode 100755 src/main/java/org/tron/gossip/utils/ReflectionUtils.java create mode 100644 src/test/java/org/tron/gossip/GossipTest.java 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); + } +}