diff --git a/duniter4j-cmd/lib/j-text-utils-0.3.3.jar b/duniter4j-cmd/lib/j-text-utils-0.3.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..42a9c6fa4ce334d113eba5af341d8a06b1a651e7 Binary files /dev/null and b/duniter4j-cmd/lib/j-text-utils-0.3.3.jar differ diff --git a/duniter4j-cmd/lib/j-text-utils-0.3.3.pom b/duniter4j-cmd/lib/j-text-utils-0.3.3.pom new file mode 100644 index 0000000000000000000000000000000000000000..1a68c2db5fbeda3d9dfdb110c2cef99fe2a50235 --- /dev/null +++ b/duniter4j-cmd/lib/j-text-utils-0.3.3.pom @@ -0,0 +1,65 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>dnl.utils</groupId> + <artifactId>j-text-utils</artifactId> + <packaging>jar</packaging> + <version>0.3.3</version> + <name>Java Text Utilities</name> + <url>http://code.google.com/p/j-text-utils</url> + + <dependencies> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.4</version> + </dependency> + <dependency> + <groupId>net.sf.opencsv</groupId> + <artifactId>opencsv</artifactId> + <version>2.3</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>14.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.7</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <extensions> + <extension> + <groupId>org.jvnet.wagon-svn</groupId> + <artifactId>wagon-svn</artifactId> + <version>1.9</version> + </extension> + </extensions> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.7</source> + <target>1.7</target> + </configuration> + </plugin> + </plugins> + </build> + + <distributionManagement> + <repository> + <uniqueVersion>false</uniqueVersion> + <id>googlecode</id> + <url>svn:https://j-text-utils.googlecode.com/svn/trunk/repo/</url> + </repository> + </distributionManagement> + +</project> diff --git a/duniter4j-cmd/pom.xml b/duniter4j-cmd/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..0491353b51545e4e1ebc3fde7d3df65e6ed439fa --- /dev/null +++ b/duniter4j-cmd/pom.xml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>duniter4j</artifactId> + <groupId>org.duniter</groupId> + <version>0.9.2-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>duniter4j-cmd</artifactId> + + <properties> + <jTextUtilsVersion>0.3.3</jTextUtilsVersion> + </properties> + + <repositories> + <repository> + <id>d-maven</id> + <url>https://github.com/neilpanchal/j-text-utils/tree/master/repo</url> + </repository> + </repositories> + + <dependencies> + <dependency> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-core-client</artifactId> + <version>${project.version}</version> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + </dependency> + + + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + <version>1.60</version> + </dependency> + + <dependency> + <groupId>dnl.utils</groupId> + <artifactId>j-text-utils</artifactId> + <version>${jTextUtilsVersion}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>2.6</version> + </dependency> + + </dependencies> + + <profiles> + <profile> + <id>install-missing-libs</id> + <activation> + <file> + <missing>${settings.localRepository}/dnl/utils/j-text-utils/${jTextUtilsVersion}/j-text-utils-${jTextUtilsVersion}.jar</missing> + </file> + </activation> + <build> + <plugins> + <plugin> + <artifactId>maven-install-plugin</artifactId> + <version>2.5.2</version> + <executions> + <execution> + <id>installing j-text-utils.jar</id> + <phase>initialize</phase> + <goals> + <goal>install-file</goal> + </goals> + <configuration> + <groupId>dnl.utils</groupId> + <artifactId>j-text-utils</artifactId> + <version>${jTextUtilsVersion}</version> + <packaging>jar</packaging> + <file>${project.basedir}/lib/j-text-utils-${jTextUtilsVersion}.jar</file> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-antrun-plugin</artifactId> + <executions> + <execution> + <id>enforce-dependencies-exists</id> + <phase>generate-sources</phase> + <goals> + <goal>run</goal> + </goals> + <configuration> + <target> + + <condition property="displayMessage"> + <and> + <not><available file="${project.basedir}/.maven/install.log" /></not> + <!-- do not failed here if performRelease --> + <isfalse value="${performRelease}" /> + </and> + </condition> + <property name="installSuccessMessage">* + ************************************************************************* + * + * IMPORTANT: + * + * Missing lib dependencies successfully installed on [${settings.localRepository}] + * You should now re-run the build. + * This message will NOT appear again + * + ************************************************************************* + </property> + + <echo file="${project.basedir}/.maven/install.log">${installSuccessMessage}</echo> + + <fail message="${installSuccessMessage}" > + <condition> + <istrue value="${displayMessage}"/> + </condition> + </fail> + </target> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>use-installed-libs</id> + <activation> + <file> + <exists>${settings.localRepository}/dnl/utils/j-text-utils/${jTextUtilsVersion}/j-text-utils-${jTextUtilsVersion}.jar</exists> + </file> + </activation> + <dependencies> + <dependency> + <groupId>dnl.utils</groupId> + <artifactId>j-text-utils</artifactId> + <version>${jTextUtilsVersion}</version> + </dependency> + </dependencies> + </profile> + </profiles> +</project> \ No newline at end of file diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..7208a3dc3163e375146c34163ccaeb030f4ef9d8 --- /dev/null +++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java @@ -0,0 +1,149 @@ +package fr.duniter.cmd; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import com.google.common.collect.Lists; +import fr.duniter.cmd.actions.NetworkAction; +import fr.duniter.cmd.actions.SentMoneyAction; +import org.apache.commons.io.FileUtils; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.service.ServiceLocator; +import org.duniter.core.util.StringUtils; +import org.nuiton.i18n.I18n; +import org.nuiton.i18n.init.DefaultI18nInitializer; +import org.nuiton.i18n.init.UserI18nInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Created by blavenie on 22/03/17. + */ +public class Main { + + @Parameter(names = "-debug", description = "Debug mode", arity = 1) + private boolean debug = false; + + @Parameter(names = "--help", help = true) + private boolean help; + + @Parameter(names = "-config", description = "Configuration file path") + private String configFilename = "duniter-cmd.config"; + + public static void main(String ... args) { + Main main = new Main(); + main.run(args); + } + + protected void run(String ... args) { + + Map<String, Runnable> actions = new HashMap<>(); + actions.put("network", new NetworkAction()); + actions.put("send", new SentMoneyAction()); + + // Parsing args + JCommander jc = new JCommander(this); + actions.entrySet().stream().forEach(entry -> jc.addCommand(entry.getKey(), entry.getValue())); + try { + jc.parse(args); + } + catch(ParameterException e) { + System.err.println(e.getMessage()); + System.err.println("Try --help for usage"); + //jc.usage(); + System.exit(-1); + } + + // Usage, if help or no command + String actionName = jc.getParsedCommand(); + if (StringUtils.isBlank(actionName)) { + jc.usage(); + // Return error code, if not help + if (!help) System.exit(-1); + return; + } + + // Set log level + // TODO + + // Init configuration + initConfiguration(configFilename); + + // Init i18n + try { + initI18n(); + } catch(IOException e) { + System.out.println("Unable to initialize translations"); + System.exit(-1); + } + + // Set a default account id, then load cache + ServiceLocator.instance().getDataContext().setAccountId(0); + + // Initialize service locator + ServiceLocator.instance().init(); + + Runnable action = actions.get(actionName); + action.run(); + } + + + protected String getI18nBundleName() { + return "duniter4j-core-client-i18n"; + } + + /* -- -- */ + + /** + * Convenience methods that could be override to initialize other configuration + * + * @param configFilename + * @param configArgs + */ + protected void initConfiguration(String configFilename) { + String[] configArgs = getConfigArgs(); + Configuration config = new Configuration(configFilename, configArgs); + Configuration.setInstance(config); + } + + protected void initI18n() throws IOException { + Configuration config = Configuration.instance(); + + // --------------------------------------------------------------------// + // init i18n + // --------------------------------------------------------------------// + File i18nDirectory = new File(config.getDataDirectory(), "i18n"); + if (i18nDirectory.exists()) { + // clean i18n cache + FileUtils.cleanDirectory(i18nDirectory); + } + + FileUtils.forceMkdir(i18nDirectory); + + if (debug) { + System.out.println("I18N directory: " + i18nDirectory); + } + + Locale i18nLocale = config.getI18nLocale(); + + if (debug) { + System.out.println(String.format("Starts i18n with locale [%s] at [%s]", + i18nLocale, i18nDirectory)); + } + I18n.init(new UserI18nInitializer( + i18nDirectory, new DefaultI18nInitializer(getI18nBundleName())), + i18nLocale); + } + + protected String[] getConfigArgs() { + List<String> configArgs = Lists.newArrayList(); + /*configArgs.addAll(Lists.newArrayList( + "--option", ConfigurationOption.BASEDIR.getKey(), getResourceDirectory().getAbsolutePath()));*/ + return configArgs.toArray(new String[configArgs.size()]); + } + +} diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java new file mode 100644 index 0000000000000000000000000000000000000000..ac10b861e02cdbaf888f49ea549a234e77d4fd65 --- /dev/null +++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java @@ -0,0 +1,80 @@ +package fr.duniter.cmd.actions; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import dnl.utils.text.table.TextTable; +import fr.duniter.cmd.actions.utils.Formatters; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.ServiceLocator; +import org.duniter.core.client.service.local.NetworkService; +import org.duniter.core.util.CollectionUtils; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created by blavenie on 22/03/17. + */ +@Parameters(commandDescription = "Display network peers") +public class NetworkAction implements Runnable { + + @Parameter(names = "-host", description = "Duniter host") + private String host = "g1.duniter.org"; + + @Parameter(names = "-port", description = "Duniter port") + private int port = 10901; + + @Override + public void run() { + NetworkService service = ServiceLocator.instance().getNetworkService(); + Peer mainPeer = Peer.newBuilder().setHost(host).setPort(port).build(); + + List<Peer> peers = service.getPeers(mainPeer); + + if (CollectionUtils.isEmpty(peers)) { + System.out.println("No peers found"); + } + else { + + String[] columnNames = { + "Uid", + "Pubkey", + "Address", + "Status", + "API", + "Version", + "Difficulty", + "Block #"}; + + List<Object[]> data = peers.stream().map(peer -> { + boolean isUp = peer.getStats().getStatus() == Peer.PeerStatus.UP; + return new Object[] { + Formatters.formatUid(peer.getStats().getUid()), + Formatters.formatPubkey(peer.getPubkey()), + peer.getHost() + ":" + peer.getPort(), + peer.getStats().getStatus().name(), + isUp && peer.isUseSsl() ? "SSL" : null, + isUp ? peer.getStats().getVersion() : null, + isUp ? peer.getStats().getHardshipLevel() : "Mirror", + isUp ? peer.getStats().getBlockNumber() : null + }; + }) + .collect(Collectors.toList()); + + Object[][] rows = new Object[data.size()][]; + int i = 0; + for (Object[] row : data) { + rows[i++] = row; + } + + + TextTable tt = new TextTable(columnNames, rows); + // this adds the numbering on the left + tt.setAddRowNumbering(true); + tt.printTable(); + } + + } + + +} diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java new file mode 100644 index 0000000000000000000000000000000000000000..9b64dcf8233f5d756b49c1e36c0996a1abc31444 --- /dev/null +++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java @@ -0,0 +1,76 @@ +package fr.duniter.cmd.actions; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParametersDelegate; +import fr.duniter.cmd.actions.params.WalletParameters; +import fr.duniter.cmd.actions.utils.Formatters; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.bma.BlockchainParameters; +import org.duniter.core.client.model.local.Currency; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.model.local.Wallet; +import org.duniter.core.client.service.ServiceLocator; +import org.duniter.core.client.service.bma.BlockchainRemoteService; +import org.duniter.core.client.service.bma.TransactionRemoteService; +import org.duniter.core.service.CryptoService; +import org.duniter.core.util.crypto.CryptoUtils; +import org.duniter.core.util.crypto.KeyPair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by blavenie on 22/03/17. + */ +public class SentMoneyAction implements Runnable { + + private static final Logger log = LoggerFactory.getLogger(SentMoneyAction.class); + + @ParametersDelegate + private WalletParameters walletParams = new WalletParameters(); + + @Parameter(names = "--amount", description = "Amount", required = true) + public int amount; + + @Parameter(names = "--dest", description = "Destination pubkey", required = true) + public String destPubkey; + + @Parameter(names = "--comment", description = "TX Comment") + public String comment; + + @Override + public void run() { + + CryptoService cryptoService = ServiceLocator.instance().getCryptoService(); + TransactionRemoteService txService = ServiceLocator.instance().getTransactionRemoteService(); + Configuration config = Configuration.instance(); + + Peer peer = Peer.newBuilder().setHost(config.getNodeHost()) + .setPort(config.getNodePort()) + .build(); + + Currency currency = ServiceLocator.instance().getBlockchainRemoteService().getCurrencyFromPeer(peer); + ServiceLocator.instance().getCurrencyService().save(currency); + peer.setCurrencyId(currency.getId()); + peer.setCurrency(currency.getCurrencyName()); + ServiceLocator.instance().getPeerService().save(peer); + + // Compute keypair and wallet + KeyPair keypair = cryptoService.getKeyPair(walletParams.salt, walletParams.password); + Wallet wallet = new Wallet( + currency.getCurrencyName(), + null, + keypair.getPubKey(), + keypair.getSecKey()); + wallet.setCurrencyId(currency.getId()); + + System.out.println("Connected to wallet: " + wallet.getPubKeyHash()); + + txService.transfer(wallet, destPubkey, amount, comment); + + + System.out.println(String.format("Successfully sent [%d %s] to [%s]", + amount, + Formatters.currencySymbol(currency.getCurrencyName()), + Formatters.formatPubkey(destPubkey))); + } +} diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java new file mode 100644 index 0000000000000000000000000000000000000000..2694cafc0c0f304a6b6b709026f157f4c3d520d8 --- /dev/null +++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java @@ -0,0 +1,14 @@ +package fr.duniter.cmd.actions.params; + +import com.beust.jcommander.Parameter; + +/** + * Created by blavenie on 22/03/17. + */ +public class WalletParameters { + @Parameter(names = "--salt", description = "Salt (to generate the keypair)", required = true) + public String salt; + + @Parameter(names = "--passwd", description = "Password (to generate the keypair)", required = true) + public String password; +} diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java new file mode 100644 index 0000000000000000000000000000000000000000..0f8cdc9bdbbf0b2df6d3c8399b93e881e6f5262b --- /dev/null +++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java @@ -0,0 +1,34 @@ +package fr.duniter.cmd.actions.utils; + +/** + * Created by blavenie on 24/03/17. + */ +public class Formatters { + + public static String formatPubkey(String pubkey) { + if (pubkey != null && pubkey.length() > 8) { + return pubkey.substring(0, 8); + } + return pubkey; + } + + public static String formatUid(String uid) { + if (uid != null && uid.length() > 20) { + return uid.substring(0, 19); + } + return uid; + } + + public static String currencySymbol(String currencyName) { + String[] parts = currencyName.split("-_"); + if (parts.length < 2) { + if (currencyName.length() <= 3) { + return currencyName.toUpperCase(); + } + else { + return currencyName.toUpperCase().substring(0,1); + } + } + return currencySymbol(parts[0]) + currencySymbol(parts[1]); + } +} diff --git a/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean new file mode 100644 index 0000000000000000000000000000000000000000..bbf9fb6b0c5cc69950b92beafedb92622ab88201 --- /dev/null +++ b/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean @@ -0,0 +1,13 @@ +org.duniter.core.client.service.bma.BlockchainRemoteServiceImpl +org.duniter.core.client.service.bma.NetworkRemoteServiceImpl +org.duniter.core.client.service.bma.WotRemoteServiceImpl +org.duniter.core.client.service.bma.TransactionRemoteServiceImpl +org.duniter.core.client.service.elasticsearch.CurrencyRegistryRemoteServiceImpl +org.duniter.core.service.Ed25519CryptoServiceImpl +org.duniter.core.client.service.HttpServiceImpl +org.duniter.core.client.service.DataContext +org.duniter.core.client.service.local.PeerServiceImpl +org.duniter.core.client.service.local.CurrencyServiceImpl +org.duniter.core.client.service.local.NetworkServiceImpl +org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl +org.duniter.core.client.dao.mem.MemoryPeerDaoImpl \ No newline at end of file diff --git a/duniter4j-cmd/src/main/resources/duniter4j-cmd.config b/duniter4j-cmd/src/main/resources/duniter4j-cmd.config new file mode 100644 index 0000000000000000000000000000000000000000..d9f4f75b7e5ea3263c1a2c3b4ac7eecaba84c90e --- /dev/null +++ b/duniter4j-cmd/src/main/resources/duniter4j-cmd.config @@ -0,0 +1,5 @@ +duniter4j.node.host=192.168.0.5 +duniter4j.node.port=10901 + +duniter4j.node.elasticsearch.host=localhost +duniter4j.node.elasticsearch.port=9200 diff --git a/duniter4j-cmd/src/main/resources/log4j.properties b/duniter4j-cmd/src/main/resources/log4j.properties new file mode 100644 index 0000000000000000000000000000000000000000..f31940a1b8ca70d29ca0eb5840a6ec86174973f1 --- /dev/null +++ b/duniter4j-cmd/src/main/resources/log4j.properties @@ -0,0 +1,27 @@ +### +# Global logging configuration +log4j.rootLogger=ERROR, stdout, file + +# Console output +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p - %m%n + +# duniter4j levels +log4j.logger.org.duniter=INFO +log4j.logger.org.duniter.cmd=INFO +#log4j.logger.org.duniter.core.client.service=DEBUG +log4j.logger.org.duniter.core.client.service.local=DEBUG +#log4j.logger.org.duniter.core.client.service.bma=DEBUG +log4j.logger.org.duniter.core.beans=WARN +#log4j.logger.org.duniter.core.client.service=TRACE + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.file=ucoin-client.log +log4j.appender.file.MaxFileSize=10MB +log4j.appender.file.MaxBackupIndex=4 + +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p %c - %m%n + + diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java index e6d06adb26087ec59debd9c4e1d4eb3db09c00b8..dfc6b61988c8a8a43cc36a7be56cc47dc60bd405 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java @@ -84,14 +84,14 @@ public class Configuration { this.applicationConfig.setEncoding(Charsets.UTF_8.name()); this.applicationConfig.setConfigFileName(file); - // get all config providers + // get allOfToList config providers Set<ApplicationConfigProvider> providers = ApplicationConfigHelper.getProviders(null, null, null, true); - // load all default options + // load allOfToList default options ApplicationConfigHelper.loadAllDefaultOption(applicationConfig, providers); @@ -106,7 +106,7 @@ public class Configuration { // Override application version initVersion(applicationConfig); - // get all transient and final option keys + // get allOfToList transient and final option keys Set<String> optionToSkip = ApplicationConfigHelper.getTransientOptionKeys(providers); @@ -233,11 +233,7 @@ public class Configuration { public String getNodeCurrency() { return applicationConfig.getOption(ConfigurationOption.NODE_CURRENCY.getKey()); } - - public String getNodeProtocol() { - return applicationConfig.getOption(ConfigurationOption.NODE_PROTOCOL.getKey()); - } - + public String getNodeHost() { return applicationConfig.getOption(ConfigurationOption.NODE_HOST.getKey()); } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java index 41d80093af34d6b6ffbe0817c5ea5e8ca4562540..606253e0c73a2e84b73a628b95c572532d869f3a 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java @@ -143,14 +143,14 @@ public enum ConfigurationOption implements ConfigOptionDef { NODE_HOST( "duniter4j.node.host", n("duniter4j.config.option.node.host.description"), - "cgeek.fr", + "g1.duniter.org", String.class, false), NODE_PORT( "duniter4j.node.port", n("duniter4j.config.option.node.port.description"), - "9330", + "10901", Integer.class, false), @@ -164,7 +164,7 @@ public enum ConfigurationOption implements ConfigOptionDef { NETWORK_TIMEOUT( "duniter4j.network.timeout", n("duniter4j.config.option.network.timeout.description"), - "100000", // = 10 s + "20000", // = 2 s Integer.class, false), diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java index 1df3da70caa447790e09943d22cce224f1a94be9..959794aeb5d26688a14c94ed24c2f814dcf31cbb 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java @@ -32,4 +32,13 @@ public interface Constants { String CURRENCY_NAME = "[A-Za-z0-9_-]"; String PUBKEY = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}"; } + + interface HttpStatus { + int SC_TOO_MANY_REQUESTS = 429; + } + + interface Config { + int TOO_MANY_REQUEST_RETRY_TIME = 500; // 500 ms + int MAX_SAME_REQUEST_COUNT = 5; // 5 requests before to get 429 error + } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java similarity index 92% rename from duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java rename to duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java index 1f4b2571737a00cb2863b0f7228ef21a1e50cf89..3c9965ff929413d775b27cd898a79f7094834f07 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java @@ -23,7 +23,10 @@ package org.duniter.core.client.model.bma; */ -public enum EndpointProtocol { +public enum EndpointApi { BASIC_MERKLED_API, + BMAS, + ES_CORE_API, + ES_USER_API, UNDEFINED } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java index 1d24f9c51679cfdc8a446f5f5626e35ffea45c54..560a70e02c7b778e3f2d3207e3179034ca075439 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java @@ -35,6 +35,7 @@ public class NetworkPeering implements Serializable { private String block; private String signature; + private String raw; private String pubkey; @@ -112,26 +113,26 @@ public class NetworkPeering implements Serializable { } public static class Endpoint implements Serializable { - public EndpointProtocol protocol; - public String url; + public EndpointApi api; + public String dns; public String ipv4; public String ipv6; public Integer port; - public EndpointProtocol getProtocol() { - return protocol; + public EndpointApi getApi() { + return api; } - public void setProtocol(EndpointProtocol protocol) { - this.protocol = protocol; + public void setApi(EndpointApi api) { + this.api = api; } - public String getUrl() { - return url; + public String getDns() { + return dns; } - public void setUrl(String url) { - this.url = url; + public void setDns(String dns) { + this.dns = dns; } public String getIpv4() { @@ -160,8 +161,8 @@ public class NetworkPeering implements Serializable { @Override public String toString() { - String s = "protocol=" + protocol.name() + "\n" + - "url=" + url + "\n" + + String s = "api=" + api.name() + "\n" + + "dns=" + dns + "\n" + "ipv4=" + ipv4 + "\n" + "ipv6=" + ipv6 + "\n" + "port=" + port + "\n"; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java index 90cbf2cf84aec6ef619f90a2d50f9bfa9ad16a86..c413492d9586f1980fd14cfc5b3d91e6d222c406 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java @@ -148,7 +148,9 @@ public class NetworkPeers implements Serializable { "status=" + status + "\n" + "block=" + block + "\n"; for(NetworkPeering.Endpoint endpoint: endpoints) { - s += endpoint.toString() + "\n"; + if (endpoint != null) { + s += endpoint.toString() + "\n"; + } } return s; } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java index df822dd07a44e9c8f16863685275f7dac4686a81..04c77d8f45ecf18738bab899feb49a9694b24b1f 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java @@ -27,9 +27,9 @@ package org.duniter.core.client.model.bma; */ public interface Protocol { - String VERSION = "2"; + String VERSION = "10"; - String TX_VERSION = "3"; + String TX_VERSION = "10"; String TYPE_IDENTITY = "Identity"; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java index 36e4901a5c9d62cfa718405c7d18885c2e2b1ab5..4c0a05e072f76d26619c495338979818a868e256 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java @@ -89,7 +89,7 @@ public class TxSource { } /** - * Source type : <ul> + * Source sortType : <ul> * <li><code>D</code> : Universal Dividend</li> * <li><code>T</code> : Transaction</li> * </ul> diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java index 2c8d35b912a8e58c8a2df43a80ea057db3e9cf20..35896bd932ce050e07d211f6dc854a7d80152d90 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java @@ -25,9 +25,9 @@ package org.duniter.core.client.model.bma.gson; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.bma.NetworkPeering; -import org.apache.http.conn.util.InetAddressUtils; +import org.duniter.core.util.http.InetAddressUtils; import java.io.IOException; import java.util.ArrayList; @@ -51,19 +51,19 @@ public class EndpointAdapter extends TypeAdapter<NetworkPeering.Endpoint> { endpoint.ipv4 = word; } else if (InetAddressUtils.isIPv6Address(word)) { endpoint.ipv6 = word; - } else if (word.startsWith("http")) { - endpoint.url = word; + } else if (word.trim().length() > 0) { + endpoint.dns = word; } else { try { - endpoint.protocol = EndpointProtocol.valueOf(word); + endpoint.api = EndpointApi.valueOf(word); } catch (IllegalArgumentException e) { // skip this part } } } - if (endpoint.protocol == null) { - endpoint.protocol = EndpointProtocol.UNDEFINED; + if (endpoint.api == null) { + endpoint.api = EndpointApi.UNDEFINED; } return endpoint; @@ -74,8 +74,8 @@ public class EndpointAdapter extends TypeAdapter<NetworkPeering.Endpoint> { writer.nullValue(); return; } - writer.value(endpoint.protocol.name() + " " + - endpoint.url + " " + + writer.value(endpoint.api.name() + " " + + endpoint.dns + " " + endpoint.ipv4 + " " + endpoint.ipv6 + " " + endpoint.port); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java index d8b5a9b2855dc7868f0f086aa64bbf9cafb8f1b3..fbd94e04174312d7050edbcbb67fe96b82d1f7f8 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java @@ -58,7 +58,7 @@ public class MultimapTypeAdapter implements JsonSerializer<Multimap>, JsonDeseri Preconditions.checkArgument(multimapType instanceof ParameterizedType); final ParameterizedType paramType = (ParameterizedType)multimapType; final Type[] typeArguments = paramType.getActualTypeArguments(); - Preconditions.checkArgument(2 == typeArguments.length, "Type must contain exactly 2 type arguments."); + Preconditions.checkArgument(2 == typeArguments.length, "Type must contain exactly 2 sortType arguments."); final ParameterizedTypeImpl valueType = new ParameterizedTypeImpl(Collection.class, null, typeArguments[1]); final ParameterizedTypeImpl mapType = new ParameterizedTypeImpl(Map.class, null, typeArguments[0], valueType); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java index 2681d5df51fa4855c21a34d6026e537e38927d26..7a877fe64949a229086429b08e7cd279ccc7bea3 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java @@ -3,45 +3,124 @@ package org.duniter.core.client.model.bma.jackson; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import org.apache.http.conn.util.InetAddressUtils; -import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.bma.NetworkPeering; +import org.duniter.core.util.StringUtils; +import org.duniter.core.util.http.InetAddressUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Created by blavenie on 07/12/16. */ public class EndpointDeserializer extends JsonDeserializer<NetworkPeering.Endpoint> { + + private static final Logger log = LoggerFactory.getLogger(EndpointDeserializer.class); + + public static final String EP_END_REGEXP = "(?:[ ]+([a-z0-9-_]+[.][a-z0-9-_.]*))?(?:[ ]+([0-9.]+))?(?:[ ]+([0-9a-f:]+))?(?:[ ]+([0-9]+))$"; + public static final String BMA_API_REGEXP = "^BASIC_MERKLED_API" + EP_END_REGEXP; + public static final String BMAS_API_REGEXP = "^BMAS" + EP_END_REGEXP; + public static final String OTHER_API_REGEXP = "^([A-Z_-]+)" + EP_END_REGEXP; + + private Pattern bmaPattern; + private Pattern bmasPattern; + private Pattern otherApiPattern; + + public EndpointDeserializer() { + bmaPattern = Pattern.compile(BMA_API_REGEXP); + bmasPattern = Pattern.compile(BMAS_API_REGEXP); + otherApiPattern = Pattern.compile(OTHER_API_REGEXP); + } + @Override public NetworkPeering.Endpoint deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { String ept = jp.getText(); - ArrayList<String> parts = new ArrayList<>(Arrays.asList(ept.split(" "))); + NetworkPeering.Endpoint endpoint = new NetworkPeering.Endpoint(); - endpoint.port = Integer.parseInt(parts.remove(parts.size() - 1)); - for (String word : parts) { - if (InetAddressUtils.isIPv4Address(word)) { - endpoint.ipv4 = word; - } else if (InetAddressUtils.isIPv6Address(word)) { - endpoint.ipv6 = word; - } else if (word.startsWith("http")) { - endpoint.url = word; - } else { - try { - endpoint.protocol = EndpointProtocol.valueOf(word); - } catch (IllegalArgumentException e) { - // skip this part + + // BMA API + Matcher mather = bmaPattern.matcher(ept); + if (mather.matches()) { + endpoint.api = EndpointApi.BASIC_MERKLED_API; + + for(int i=1; i<=mather.groupCount(); i++) { + String word = mather.group(i); + + if (StringUtils.isNotBlank(word)) { + if (InetAddressUtils.isIPv4Address(word)) { + endpoint.ipv4 = word; + } else if (InetAddressUtils.isIPv6Address(word)) { + endpoint.ipv6 = word; + } else if (i == mather.groupCount() && word.matches("\\d+")){ + endpoint.port = Integer.parseInt(word); + } else { + endpoint.dns = word; + } + } + } + + return endpoint; + } + + // BMAS API + mather = bmasPattern.matcher(ept); + if (mather.matches()) { + endpoint.api = EndpointApi.BMAS; + + for(int i=1; i<=mather.groupCount(); i++) { + String word = mather.group(i); + + if (StringUtils.isNotBlank(word)) { + if (InetAddressUtils.isIPv4Address(word)) { + endpoint.ipv4 = word; + } else if (InetAddressUtils.isIPv6Address(word)) { + endpoint.ipv6 = word; + } else if (i == mather.groupCount() && word.matches("\\d+")){ + endpoint.port = Integer.parseInt(word); + } else { + endpoint.dns = word; + } } } + + return endpoint; } - if (endpoint.protocol == null) { - endpoint.protocol = EndpointProtocol.UNDEFINED; + // Other API + mather = otherApiPattern.matcher(ept); + if (mather.matches()) { + try { + endpoint.api = EndpointApi.valueOf(mather.group(1)); + } catch(Exception e) { + log.warn("Unable to deserialize endpoint: unknown api [" + mather.group(1) + "]"); + // not known API: skip + return null; + } + + for(int i=2; i<=mather.groupCount(); i++) { + String word = mather.group(i); + + if (StringUtils.isNotBlank(word)) { + if (InetAddressUtils.isIPv4Address(word)) { + endpoint.ipv4 = word; + } else if (InetAddressUtils.isIPv6Address(word)) { + endpoint.ipv6 = word; + } else if (i == mather.groupCount() && word.matches("\\d+")){ + endpoint.port = Integer.parseInt(word); + } else { + endpoint.dns = word; + } + } + } + + return endpoint; } - return endpoint; + return null; } } \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java index c09fe0d125212863155ea662b67b071fa39676f1..d87291d7e20b990145f5dc11539bce7ecea63dbd 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java @@ -28,7 +28,7 @@ package org.duniter.core.client.model.elasticsearch; public class DeleteRecord extends Record { public static final String PROPERTY_INDEX="index"; - public static final String PROPERTY_TYPE="type"; + public static final String PROPERTY_TYPE="sortType"; public static final String PROPERTY_ID="id"; private String index; diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java index 5b292b8731a4e8ef782fee97d7e2eb1bbc269d5f..02eb05ccfa62998f4a50656cf5ba93b28e2ba19b 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java @@ -23,107 +23,460 @@ package org.duniter.core.client.model.local; */ +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.duniter.core.client.model.bma.EndpointApi; +import org.duniter.core.client.model.bma.NetworkPeering; +import org.duniter.core.util.Preconditions; +import org.duniter.core.util.StringUtils; +import org.duniter.core.util.http.InetAddressUtils; + import java.io.Serializable; +import java.util.StringJoiner; public class Peer implements LocalEntity, Serializable { + + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String api; + private String dns; + private String ipv4; + private String ipv6; + private Integer port; + private Boolean useSsl; + private String pubkey; + private String hash; + private String currency; + + public Builder() { + + } + + public Builder setApi(String api) { + this.api = api; + return this; + } + + public Builder setDns(String dns) { + this.dns = dns; + return this; + } + + public Builder setIpv4(String ipv4) { + this.ipv4 = ipv4; + return this; + } + + public Builder setIpv6(String ipv6) { + this.ipv6 = ipv6; + return this; + } + + public Builder setPort(int port) { + this.port = port; + return this; + } + + public Builder setUseSsl(boolean useSsl) { + this.useSsl = useSsl; + return this; + } + + public Builder setCurrency(String currency) { + this.currency = currency; + return this; + } + + public Builder setPubkey(String pubkey) { + this.pubkey = pubkey; + return this; + } + + public Builder setHash(String hash) { + this.hash = hash; + return this; + } + + public Builder setHost(String host) { + Preconditions.checkNotNull(host); + if (InetAddressUtils.isIPv4Address(host)) { + this.ipv4 = host; + } + else if (InetAddressUtils.isIPv6Address(host)) { + this.ipv6 = host; + } + else { + this.dns = host; + } + return this; + } + + public Builder setEndpoint(NetworkPeering.Endpoint source) { + Preconditions.checkNotNull(source); + if (source.api != null) { + setApi(source.api.name()); + } + if (StringUtils.isNotBlank(source.dns)) { + setDns(source.dns); + } + if (StringUtils.isNotBlank(source.ipv4)) { + setIpv4(source.ipv4); + } + if (StringUtils.isNotBlank(source.ipv6)) { + setIpv6(source.ipv6); + } + if (StringUtils.isNotBlank(source.ipv6)) { + setHost(source.ipv6); + } + if (source.port != null) { + setPort(source.port); + } + return this; + } + + public Peer build() { + int port = this.port != null ? this.port : 80; + boolean useSsl = this.useSsl != null ? this.useSsl : + (port == 443 || this.api == EndpointApi.BMAS.name()); + String api = this.api != null ? this.api : EndpointApi.BASIC_MERKLED_API.name(); + Peer ep = new Peer(api, dns, ipv4, ipv6, port, useSsl); + if (StringUtils.isNotBlank(this.currency)) { + ep.setCurrency(this.currency); + } + if (StringUtils.isNotBlank(this.pubkey)) { + ep.setPubkey(this.pubkey); + } + if (StringUtils.isNotBlank(this.hash)) { + ep.setHash(this.hash); + } + return ep; + } + + } + + + // Local entity attribute (only used for local DB) private Long id; private Long currencyId; + + private String api; + private String dns; + private String ipv4; + private String ipv6; + + private String url; private String host; + private String pubkey; + + private String hash; + private String currency; + + private Stats stats = new Stats(); + private int port; private boolean useSsl; - private String url; // computed public Peer() { // default constructor, need for de-serialization } - public Peer(String host, int port) { - this.host = host; - this.port = port; - this.useSsl = (port == 443); - this.url = computeUrl(this.host, this.port, this.useSsl); + /** + * @deprecated Use Builder instead + * @param host Can be a ipv4, ipv6 or a dns + * @param port any port, or null (default: 80) + */ + @Deprecated + public Peer(String host, Integer port) { + this.api = EndpointApi.BASIC_MERKLED_API.name(); + if (InetAddressUtils.isIPv4Address(host)) { + this.ipv4 = host; + } + if (InetAddressUtils.isIPv6Address(host)) { + this.ipv6 = host; + } + else { + this.dns = host; + } + this.port = port != null ? port : 80; + this.useSsl = (port == 443 || this.api == EndpointApi.BMAS.name()); + init(); } - public Peer(String host, int port, boolean useSsl) { - this.host = host; + public Peer(String api, String dns, String ipv4, String ipv6, int port, boolean useSsl) { + this.api = api; + this.dns = StringUtils.isNotBlank(dns) ? dns : null; + this.ipv4 = StringUtils.isNotBlank(ipv4) ? ipv4 : null; + this.ipv6 = StringUtils.isNotBlank(ipv6) ? ipv6 : null; this.port = port; this.useSsl = useSsl; - this.url = computeUrl(this.host, this.port, this.useSsl); + init(); } - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - public String getUrl() { - return url; + protected void init() { + // If SSL: prefer DNS name (should be defined in SSL certificate) + // else (if define) use ipv4 (if NOT local IP) + // else (if define) use dns + // else (if define) use ipv6 + this.host = ((port == 443 || useSsl) && dns != null) ? dns : + (ipv4 != null && InetAddressUtils.isNotLocalIPv4Address(ipv4) ? ipv4 : + (dns != null ? dns : + (ipv6 != null ? "[" + ipv6 + "]" : ""))); + String protocol = (port == 443 || useSsl) ? "https" : "http"; + this.url = protocol + "://" + this.host + (port != 80 ? (":" + port) : ""); } + @JsonIgnore public Long getId() { return id; } + @JsonIgnore public void setId(Long id) { this.id = id; } + @JsonIgnore public Long getCurrencyId() { return currencyId; } + @JsonIgnore public void setCurrencyId(Long currencyId) { this.currencyId = currencyId; } - public void setPort(int port) { - this.port = port; - if (port == 443) { - this.useSsl = true; - } - this.url = computeUrl(this.host, this.port, this.useSsl); + public String getApi() { + return api; } - public void setHost(String host) { - this.host = host; - this.url = computeUrl(this.host, this.port, this.useSsl); + public String getDns() { + return dns; + } + + public void setDns(String dns) { + this.dns = dns; + init(); + } + + public String getIpv4() { + return ipv4; + } + + public void setIpv4(String ipv4) { + this.ipv4 = ipv4; + init(); + } + + public String getIpv6() { + return ipv6; + } + + public void setIpv6(String ipv6) { + this.ipv6 = ipv6; + init(); + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + init(); } public boolean isUseSsl() { - return this.useSsl; + return useSsl; } public void setUseSsl(boolean useSsl) { this.useSsl = useSsl; - this.url = computeUrl(this.host, this.port, this.useSsl); + init(); } - public String toString() { - return new StringBuilder().append(host) - .append(":") - .append(port) - .append(useSsl ? "[+SSL]" : "") - .toString(); + public String getPubkey() { + return pubkey; + } + + public void setPubkey(String pubkey) { + this.pubkey = pubkey; + } + + @JsonIgnore + public String getHost() { + return this.host; // computed in init() + } + + @JsonIgnore + public String getUrl() { + return this.url; // computed in init() + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + @JsonIgnore + public Stats getStats() { + return stats; } - @Override - public boolean equals(Object o) { - if (o == null) { - return false; + @JsonIgnore + public void setStats(Stats stats) { + this.stats = stats; + } + + public String toString() { + StringJoiner joiner = new StringJoiner(" "); + if (api != null) { + joiner.add(api); + } + if (dns != null) { + joiner.add(dns); + } + if (ipv4 != null) { + joiner.add(ipv4); } - if (id != null && o instanceof Peer) { - return id.equals(((Peer)o).getId()); + if (ipv6 != null) { + joiner.add(ipv6); } - return super.equals(o); + if (port != 80) { + joiner.add(String.valueOf(port)); + } + return joiner.toString(); + } + + public enum PeerStatus { + UP, + DOWN, + ERROR } - /* -- Internal methods -- */ + public static class Stats { + private String version; + private PeerStatus status = PeerStatus.UP; // default + private Integer blockNumber; + private String blockHash; + private String error; + private Long medianTime; + private Integer hardshipLevel; + private boolean isMainConsensus = false; + private boolean isForkConsensus = false; + private Double consensusPct = 0d; + private String uid; + + public Stats() { + + } + + public PeerStatus getStatus() { + return status; + } + + @JsonIgnore + public boolean isReacheable() { + return status != null && status == PeerStatus.UP; + } + + public void setStatus(PeerStatus status) { + this.status = status; + } + + public String getError() { + return error; + } - protected String computeUrl(String host, int port, boolean useSsl) { - return String.format("%s://%s:%s", (useSsl ? "https" : "http"), host, port); + public void setError(String error) { + this.error = error; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public Integer getBlockNumber() { + return blockNumber; + } + + public void setBlockNumber(Integer blockNumber) { + this.blockNumber = blockNumber; + } + + public String getBlockHash() { + return blockHash; + } + + public void setBlockHash(String blockHash) { + this.blockHash = blockHash; + } + + public Long getMedianTime() { + return medianTime; + } + + public void setMedianTime(Long medianTime) { + this.medianTime = medianTime; + } + + public boolean isMainConsensus() { + return isMainConsensus; + } + + public void setMainConsensus(boolean mainConsensus) { + this.isMainConsensus = mainConsensus; + } + + public boolean isForkConsensus() { + return isForkConsensus; + } + + public void setForkConsensus(boolean forkConsensus) { + this.isForkConsensus = forkConsensus; + } + + public Double getConsensusPct() { + return consensusPct; + } + + public void setConsensusPct(Double consensusPct) { + this.consensusPct = consensusPct; + } + + public Integer getHardshipLevel() { + return hardshipLevel; + } + + public void setHardshipLevel(Integer hardshipLevel) { + this.hardshipLevel = hardshipLevel; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java index ac46d141a7a8d8a9bdfe0dc5c9c91124bb1cb8f7..eaa8ce61df003f3f22a3b760b88d2a7c1f8e01d9 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java @@ -24,7 +24,6 @@ package org.duniter.core.client.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; -import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; @@ -38,13 +37,13 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.duniter.core.beans.InitializingBean; import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.bma.Constants; import org.duniter.core.client.model.bma.Error; import org.duniter.core.client.model.bma.jackson.JacksonUtils; import org.duniter.core.client.model.local.Peer; import org.duniter.core.client.service.exception.*; import org.duniter.core.exception.TechnicalException; import org.duniter.core.util.StringUtils; -import org.duniter.core.util.cache.Cache; import org.duniter.core.util.cache.SimpleCache; import org.nuiton.i18n.I18n; import org.slf4j.Logger; @@ -93,6 +92,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean protected void initCaches() { Configuration config = Configuration.instance(); int cacheTimeInMillis = config.getNetworkCacheTimeInMillis(); + int defaultTimeout = config.getNetworkTimeout(); requestConfigCache = new SimpleCache<Integer, RequestConfig>(cacheTimeInMillis) { @Override @@ -162,7 +162,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean } public <T> T executeRequest(Peer peer, String absolutePath, Class<? extends T> resultClass) { - HttpGet httpGet = new HttpGet(getPath(peer, absolutePath)); + HttpGet httpGet = new HttpGet(peer.getUrl() + absolutePath); return executeRequest(httpClientCache.get(0), httpGet, resultClass); } @@ -228,12 +228,17 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean @SuppressWarnings("unchecked") protected <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Class<? extends T> resultClass, Class<?> errorClass) { + return executeRequest(httpClient, request, resultClass, errorClass, 5); + } + + protected <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Class<? extends T> resultClass, Class<?> errorClass, int retryCount) { T result = null; if (debug) { log.debug("Executing request : " + request.getRequestLine()); } + boolean retry = false; HttpResponse response = null; try { response = httpClient.execute(request); @@ -271,6 +276,11 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean catch(IOException e) { throw new HttpBadRequestException(I18n.t("duniter4j.client.status", response.getStatusLine().toString())); } + + case HttpStatus.SC_SERVICE_UNAVAILABLE: + case Constants.HttpStatus.SC_TOO_MANY_REQUESTS: + retry = true; + break; default: throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString())); } @@ -296,6 +306,23 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean } } + // HTTP requests limit exceed, retry when possible + if (retry) { + if (retryCount > 0) { + log.debug(String.format("Service unavailable: waiting [%s ms] before retrying...", Constants.Config.TOO_MANY_REQUEST_RETRY_TIME)); + try { + Thread.sleep(Constants.Config.TOO_MANY_REQUEST_RETRY_TIME); + } catch (InterruptedException e) { + throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString())); + } + // iterate + return executeRequest(httpClient, request, resultClass, errorClass, retryCount - 1); + } + else { + throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString())); + } + } + return result; } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java index e0f14a353c8b514c68bada786d197d278d4bb241..38107551c7cf74de53406be06771c33864ee5861 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java @@ -25,9 +25,13 @@ package org.duniter.core.client.service; import org.duniter.core.beans.Bean; import org.duniter.core.beans.BeanFactory; -import org.duniter.core.client.service.bma.*; +import org.duniter.core.client.service.bma.BlockchainRemoteService; +import org.duniter.core.client.service.bma.NetworkRemoteService; +import org.duniter.core.client.service.bma.TransactionRemoteService; +import org.duniter.core.client.service.bma.WotRemoteService; import org.duniter.core.client.service.elasticsearch.CurrencyRegistryRemoteService; import org.duniter.core.client.service.local.CurrencyService; +import org.duniter.core.client.service.local.NetworkService; import org.duniter.core.client.service.local.PeerService; import org.duniter.core.service.CryptoService; import org.duniter.core.service.MailService; @@ -140,10 +144,14 @@ public class ServiceLocator implements Closeable { return getBean(CurrencyRegistryRemoteService.class); } - public MailService getMaiLService() { + public MailService getMailService() { return getBean(MailService.class); } + public NetworkService getNetworkService() { + return getBean(NetworkService.class); + } + public <S extends Bean> S getBean(Class<S> clazz) { if (beanFactory == null) { initBeanFactory(); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java index 8fb4bf2bb97f8bc3cf4aac02549ff368fadeef83..8e92845eb8810efaeea6b24bc6570f56061d5db0 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java @@ -76,7 +76,7 @@ public interface BlockchainRemoteService extends Service { /** * Retrieve the dividend of a block, by id (from 0 to current). - * Usefull method to avoid to deserialize all the block + * Usefull method to avoid to deserialize allOfToList the block * * @param currencyId * @param number @@ -222,8 +222,24 @@ public interface BlockchainRemoteService extends Service { */ Map<Integer, Long> getUDs(long currencyId, long startOffset); + /** + * Listening new block event + * @param currencyId + * @param listener + * @return + */ WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener); WebsocketClientEndpoint addBlockListener(Peer peer, WebsocketClientEndpoint.MessageListener listener); + /** + * Listening new peer event + * @param currencyId + * @param listener + * @return + */ + WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener); + + WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener); + } \ No newline at end of file diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java index d431342e9ac914f8a0c32ff271fac385428b52d8..25c8b7a16c4cd5abab17afa43401eec92a69a157 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java @@ -22,6 +22,7 @@ package org.duniter.core.client.service.bma; * #L% */ +import com.fasterxml.jackson.databind.ObjectMapper; import org.duniter.core.util.Preconditions; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; @@ -76,6 +77,11 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement public static final String URL_MEMBERSHIP_SEARCH = URL_BASE + "/memberships/%s"; + public static final String URL_WS_BLOCK = "/ws/block"; + + public static final String URL_WS_PEER = "/ws/peer"; + + private ObjectMapper objectMapper; private NetworkRemoteService networkRemoteService; @@ -88,7 +94,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement // Cache on blockchain parameters private Cache<Long, BlockchainParameters> mParametersCache; - private Map<URI, WebsocketClientEndpoint> blockWsEndPoints = new HashMap<>(); + private Map<URI, WebsocketClientEndpoint> wsEndPoints = new HashMap<>(); public BlockchainRemoteServiceImpl() { super(); @@ -108,11 +114,11 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement public void close() throws IOException { super.close(); - if (blockWsEndPoints.size() != 0) { - for (WebsocketClientEndpoint clientEndPoint: blockWsEndPoints.values()) { + if (wsEndPoints.size() != 0) { + for (WebsocketClientEndpoint clientEndPoint: wsEndPoints.values()) { clientEndPoint.close(); } - blockWsEndPoints.clear(); + wsEndPoints.clear(); } } @@ -561,8 +567,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement @Override public WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener) { - Peer peer = peerService.getActivePeerByCurrencyId(currencyId); - return addBlockListener(peer, listener); + return addBlockListener(peerService.getActivePeerByCurrencyId(currencyId), listener); } @Override @@ -570,8 +575,27 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement Preconditions.checkNotNull(peer); Preconditions.checkNotNull(listener); - // Get the websocket endpoint - WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, "/ws/block"); + // Get (or create) the websocket endpoint + WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_BLOCK); + + // add listener + wsClientEndPoint.registerListener(listener); + + return wsClientEndPoint; + } + + @Override + public WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener) { + return addBlockListener(peerService.getActivePeerByCurrencyId(currencyId), listener); + } + + @Override + public WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener) { + Preconditions.checkNotNull(peer); + Preconditions.checkNotNull(listener); + + // Get (or create) the websocket endpoint + WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_PEER); // add listener wsClientEndPoint.registerListener(listener); @@ -807,17 +831,17 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement path)); // Get the websocket, or open new one if not exists - WebsocketClientEndpoint wsClientEndPoint = blockWsEndPoints.get(wsBlockURI); + WebsocketClientEndpoint wsClientEndPoint = wsEndPoints.get(wsBlockURI); if (wsClientEndPoint == null || wsClientEndPoint.isClosed()) { - log.info(String.format("Starting to listen block from [%s]...", wsBlockURI.toString())); + log.info(String.format("Starting to listen on [%s]...", wsBlockURI.toString())); wsClientEndPoint = new WebsocketClientEndpoint(wsBlockURI); - blockWsEndPoints.put(wsBlockURI, wsClientEndPoint); + wsEndPoints.put(wsBlockURI, wsClientEndPoint); } return wsClientEndPoint; } catch (URISyntaxException | ServiceConfigurationError ex) { - throw new TechnicalException("could not create URI need for web socket on block: " + ex.getMessage()); + throw new TechnicalException(String.format("Could not create URI need for web socket [%s]: %s", path, ex.getMessage())); } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java index 9e5da699dc8f689ef28850d97af1c4ea5e67531e..262dd501a4848d698d5a1be067a8bc2468cf0a1a 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java @@ -23,9 +23,11 @@ package org.duniter.core.client.service.bma; */ import org.duniter.core.beans.Service; -import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.bma.NetworkPeering; +import org.duniter.core.client.model.bma.NetworkPeers; import org.duniter.core.client.model.local.Peer; +import org.duniter.core.util.websocket.WebsocketClientEndpoint; import java.util.List; @@ -38,5 +40,11 @@ public interface NetworkRemoteService extends Service { List<Peer> getPeers(Peer peer); - List<Peer> findPeers(Peer peer, String status, EndpointProtocol endpointProtocol, Integer currentBlockNumber, String currentBlockHash); + List<String> getPeersLeaves(Peer peer); + + NetworkPeers.Peer getPeerLeaf(Peer peer, String leaf); + + List<Peer> findPeers(Peer peer, String status, EndpointApi endpointApi, Integer currentBlockNumber, String currentBlockHash); + + WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect); } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java index 48902b024a07fb7de8b2295e2a93cb51885b4a09..3815be7e913a023c3c5c0372d678f69cb4fa7717 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java @@ -22,22 +22,31 @@ package org.duniter.core.client.service.bma; * #L% */ -import java.util.ArrayList; -import java.util.List; - -import org.duniter.core.client.model.bma.EndpointProtocol; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.bma.NetworkPeering; import org.duniter.core.client.model.bma.NetworkPeers; +import org.duniter.core.client.model.bma.jackson.JacksonUtils; import org.duniter.core.client.model.local.Peer; -import org.duniter.core.util.ObjectUtils; +import org.duniter.core.exception.TechnicalException; import org.duniter.core.util.Preconditions; import org.duniter.core.util.StringUtils; +import org.duniter.core.util.websocket.WebsocketClientEndpoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Created by eis on 05/02/15. */ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements NetworkRemoteService{ + private static final Logger log = LoggerFactory.getLogger(NetworkRemoteServiceImpl.class); public static final String URL_BASE = "/network"; @@ -47,10 +56,20 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N public static final String URL_PEERING_PEERS = URL_PEERING + "/peers"; - public static final String URL_PEERING_PEERS_LEAF = URL_PEERING + "/peers?leaf="; + public static final String URL_PEERING_PEERS_LEAVES = URL_PEERING_PEERS + "?leaves=true"; + + public static final String URL_PEERING_PEERS_LEAF = URL_PEERING_PEERS + "?leaf="; + + public static final String URL_WS_PEER = "/ws/peer"; + + protected ObjectMapper objectMapper; + + private Map<URI, WebsocketClientEndpoint> wsEndPoints = new HashMap<>(); public NetworkRemoteServiceImpl() { super(); + + objectMapper = JacksonUtils.newObjectMapper(); } public NetworkPeering getPeering(Peer peer) { @@ -58,13 +77,60 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N return result; } + @Override + public void close() throws IOException { + super.close(); + + if (wsEndPoints.size() != 0) { + for (WebsocketClientEndpoint clientEndPoint: wsEndPoints.values()) { + clientEndPoint.close(); + } + wsEndPoints.clear(); + } + } + @Override public List<Peer> getPeers(Peer peer) { return findPeers(peer, null, null, null, null); } @Override - public List<Peer> findPeers(Peer peer, String status, EndpointProtocol endpointProtocol, Integer currentBlockNumber, String currentBlockHash) { + public List<String> getPeersLeaves(Peer peer) { + Preconditions.checkNotNull(peer); + + List<String> result = new ArrayList<>(); + JsonNode jsonNode= httpService.executeRequest(peer, URL_PEERING_PEERS_LEAVES, JsonNode.class); + jsonNode.get("leaves").forEach(jsonNode1 -> { + result.add(jsonNode1.asText()); + }); + return result; + } + + @Override + public NetworkPeers.Peer getPeerLeaf(Peer peer, String leaf) { + Preconditions.checkNotNull(peer); + JsonNode jsonNode = httpService.executeRequest(peer, URL_PEERING_PEERS_LEAF + leaf, JsonNode.class); + NetworkPeers.Peer result = null; + + try { + + if (jsonNode.has("leaf")) { + jsonNode = jsonNode.get("leaf"); + if (jsonNode.has("value")) { + jsonNode = jsonNode.get("value"); + String json = objectMapper.writeValueAsString(jsonNode); + result = objectMapper.readValue(json, NetworkPeers.Peer.class); + } + } + } catch(IOException e) { + throw new TechnicalException(e); + } + + return result; + } + + @Override + public List<Peer> findPeers(Peer peer, String status, EndpointApi endpointApi, Integer currentBlockNumber, String currentBlockHash) { Preconditions.checkNotNull(peer); List<Peer> result = new ArrayList<Peer>(); @@ -80,13 +146,15 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N for (NetworkPeering.Endpoint endpoint : remotePeer.endpoints) { - match = endpointProtocol == null || endpointProtocol == endpoint.protocol; + match = endpointApi == null || endpointApi == endpoint.api; - if (match) { - Peer childPeer = toPeer(endpoint); - if (childPeer != null) { - result.add(childPeer); - } + if (match && endpoint != null) { + Peer childPeer = Peer.newBuilder() + .setCurrency(remotePeer.getCurrency()) + .setPubkey(remotePeer.getPubkey()) + .setEndpoint(endpoint) + .build(); + result.add(childPeer); } } @@ -96,25 +164,22 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N return result; } - /* -- Internal methods -- */ + @Override + public WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) { + Preconditions.checkNotNull(peer); + Preconditions.checkNotNull(listener); - protected Peer toPeer(NetworkPeering.Endpoint source) { - Peer target = new Peer(); - if (StringUtils.isNotBlank(source.ipv4)) { - target.setHost(source.ipv4); - } else if (StringUtils.isNotBlank(source.ipv6)) { - target.setHost(source.ipv6); - } else if (StringUtils.isNotBlank(source.url)) { - target.setHost(source.url); - } else { - target = null; - } - if (target != null && source.port != null) { - target.setPort(source.port); - } - return target; + // Get (or create) the websocket endpoint + WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_PEER, autoReconnect); + + // add listener + wsClientEndPoint.registerListener(listener); + + return wsClientEndPoint; } + /* -- Internal methods -- */ + protected Integer parseBlockNumber(NetworkPeers.Peer remotePeer) { Preconditions.checkNotNull(remotePeer); @@ -148,4 +213,29 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N String hash = remotePeer.block.substring(index+1); return hash; } + + public WebsocketClientEndpoint getWebsocketClientEndpoint(Peer peer, String path, boolean autoReconnect) { + + try { + URI wsBlockURI = new URI(String.format("%s://%s:%s%s", + peer.isUseSsl() ? "wss" : "ws", + peer.getHost(), + peer.getPort(), + path)); + + // Get the websocket, or open new one if not exists + WebsocketClientEndpoint wsClientEndPoint = wsEndPoints.get(wsBlockURI); + if (wsClientEndPoint == null || wsClientEndPoint.isClosed()) { + log.info(String.format("Starting to listen on [%s]...", wsBlockURI.toString())); + wsClientEndPoint = new WebsocketClientEndpoint(wsBlockURI, autoReconnect); + wsEndPoints.put(wsBlockURI, wsClientEndPoint); + } + + return wsClientEndPoint; + + } catch (URISyntaxException | ServiceConfigurationError ex) { + throw new TechnicalException(String.format("Could not create URI need for web socket [%s]: %s", path, ex.getMessage())); + } + + } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java index b927f4e5e19d50ee7daf21e8cde4ebf17744116f..a6ad175ccf63ced59fd8ba9c7ecd1169c665a873 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java @@ -308,7 +308,9 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen } // Comment - sb.append("Comment: ").append(comments).append('\n'); + sb.append("Comment: "); + if (comments != null) sb.append(comments); + sb.append('\n'); return sb.toString(); } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java index 09fbe9c22895cde0847a613f9b7ba48b704b28b6..0b8aca4c0fb407f1f8cca179b9503fab95618ff2 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java @@ -32,6 +32,7 @@ import org.duniter.core.client.model.local.Wallet; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; public interface WotRemoteService extends Service { @@ -64,6 +65,10 @@ public interface WotRemoteService extends Service { String getSignedIdentity(String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid); + Map<String, String> getMembersUids(long currencyId); + + Map<String, String> getMembersUids(Peer peer); + void sendIdentity(long currencyId, byte[] pubKey, byte[] secKey, String uid, String blockUid); void sendIdentity(Peer peer, String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java index 3e42c34609db280f3699b2496d543935b8dd1169..863e6e07aa8de33b579d75095e158a52cee4c1b7 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java @@ -22,6 +22,7 @@ package org.duniter.core.client.service.bma; * #L% */ +import com.fasterxml.jackson.databind.JsonNode; import org.duniter.core.client.model.ModelUtils; import org.duniter.core.client.model.bma.*; import org.duniter.core.client.model.local.Certification; @@ -54,6 +55,8 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe public static final String URL_ADD = URL_BASE + "/add"; + public static final String URL_MEMBERS = URL_BASE + "/members"; + public static final String URL_LOOKUP = URL_BASE + "/lookup/%s"; public static final String URL_REQUIREMENT = URL_BASE+"/requirements/%s"; @@ -119,6 +122,35 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe } + public Map<String, String> getMembersUids(long currencyId) { + // get /wot/members + JsonNode json = executeRequest(currencyId, URL_MEMBERS, JsonNode.class); + + if (json == null || !json.has("results")) return null; + + Map<String, String> result = new HashMap<>(); + + json.get("results").forEach(entry -> { + result.put(entry.get("pubkey").asText(), entry.get("uid").asText()); + }); + return result; + } + + public Map<String, String> getMembersUids(Peer peer) { + // get /wot/members + JsonNode json = executeRequest(peer, URL_MEMBERS, JsonNode.class); + + if (json == null || !json.has("results")) return null; + + Map<String, String> result = new HashMap<>(); + + json.get("results").forEach(entry -> { + result.put(entry.get("pubkey").asText(), entry.get("uid").asText()); + }); + return result; + } + + public void getRequirments(long currencyId, String pubKey) { if (log.isDebugEnabled()) { log.debug(String.format("Try to find user requirements on [%s]", pubKey)); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java index b2ab891fd7ac2c1f008cffc35ae68a1d555d9733..47c81eef9350f3f06556a64007ca7f4aa1f240cb 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java @@ -58,7 +58,7 @@ public class CurrencyRegistryRemoteServiceImpl extends BaseRemoteServiceImpl imp public void afterPropertiesSet() { super.afterPropertiesSet(); config = Configuration.instance(); - peer = new Peer(config.getNodeElasticSearchHost(), config.getNodeElasticSearchPort()); + peer = Peer.newBuilder().setHost(config.getNodeElasticSearchHost()).setPort(config.getNodeElasticSearchPort()).build(); } @Override @@ -97,7 +97,7 @@ public class CurrencyRegistryRemoteServiceImpl extends BaseRemoteServiceImpl imp @Override public List<String> getAllCurrencyNames() { if (log.isDebugEnabled()) { - log.debug("Getting all currency names..."); + log.debug("Getting allOfToList currency names..."); } // get currency diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java index 3dbdd24659484c7f877680459bdcc7fc52af800a..f9cbe365dfb7a0cd06de41f09eae007717013fc9 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java @@ -67,7 +67,7 @@ public interface CurrencyService extends Service { int getCurrencyCount(); /** - * Fill all cache need for currencies + * Fill allOfToList cache need for currencies * @param context */ void loadCache(long accountId); diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java index 7ffc39c3de9148c8927682d789f282f5bcc8c771..91b9be1f13544a194b66bc144936cdfee35c57e6 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java @@ -167,7 +167,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean { /** - * Fill all cache need for currencies + * Fill allOfToList cache need for currencies * @param accountId */ public void loadCache(long accountId) { diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java new file mode 100644 index 0000000000000000000000000000000000000000..501b5a87f3c11e88eb4631dc6e84e49dd3a541b7 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java @@ -0,0 +1,57 @@ +package org.duniter.core.client.service.local; + +import org.duniter.core.beans.Service; +import org.duniter.core.client.model.local.Peer; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.function.Predicate; + +/** + * Created by blavenie on 20/03/17. + */ +public interface NetworkService extends Service { + + class Sort { + public SortType sortType; + public boolean sortAsc; + } + + class Filter { + public FilterType filterType; + public Peer.PeerStatus filterStatus; + public Boolean filterSsl; + public List<String> filterEndpoints; + } + + + enum SortType { + UID, + PUBKEY, + API, + HARDSHIP, + BLOCK_NUMBER + } + + enum FilterType { + MEMBER, // Only members peers + MIRROR // Only mirror peers + } + + List<Peer> getPeers(Peer mainPeer); + + List<Peer> getPeers(Peer mainPeer, Filter filter, Sort sort); + + CompletableFuture<List<CompletableFuture<Peer>>> asyncGetPeers(Peer mainPeer, ExecutorService pool) throws ExecutionException, InterruptedException; + + List<Peer> fillPeerStatsConsensus(final List<Peer> peers); + + Predicate<Peer> peerFilter(Filter filter); + + Comparator<Peer> peerComparator(Sort sort); + + +} diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..6d0d6679c5176639f82adaa3fbc32538501818e3 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java @@ -0,0 +1,444 @@ +package org.duniter.core.client.service.local; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; +import org.duniter.core.client.model.bma.Constants; +import org.duniter.core.client.model.bma.EndpointApi; +import org.duniter.core.client.model.bma.NetworkPeering; +import org.duniter.core.client.model.bma.NetworkPeers; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.ServiceLocator; +import org.duniter.core.client.service.bma.BaseRemoteServiceImpl; +import org.duniter.core.client.service.bma.NetworkRemoteService; +import org.duniter.core.client.service.bma.WotRemoteService; +import org.duniter.core.client.service.exception.HttpConnectException; +import org.duniter.core.client.service.exception.HttpNotFoundException; +import org.duniter.core.exception.TechnicalException; +import org.duniter.core.service.CryptoService; +import org.duniter.core.util.CollectionUtils; +import org.duniter.core.util.Preconditions; +import org.duniter.core.util.StringUtils; +import org.duniter.core.util.concurrent.CompletableFutures; +import org.duniter.core.util.websocket.WebsocketClientEndpoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Created by blavenie on 20/03/17. + */ +public class NetworkServiceImpl extends BaseRemoteServiceImpl implements NetworkService { + + private static final Logger log = LoggerFactory.getLogger(NetworkServiceImpl.class); + + private final static String BMA_URL_STATUS = "/node/summary"; + private final static String BMA_URL_BLOCKCHAIN_CURRENT = "/blockchain/current"; + private final static String BMA_URL_BLOCKCHAIN_HARDSHIP = "/blockchain/hardship/"; + + private NetworkRemoteService networkRemoteService; + private CryptoService cryptoService; + private WotRemoteService wotRemoteService; + + public NetworkServiceImpl() { + } + + public NetworkServiceImpl(NetworkRemoteService networkRemoteService, + WotRemoteService wotRemoteService, + CryptoService cryptoService) { + this(); + this.networkRemoteService = networkRemoteService; + this.wotRemoteService = wotRemoteService; + this.cryptoService = cryptoService; + } + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + this.networkRemoteService = ServiceLocator.instance().getNetworkRemoteService(); + this.wotRemoteService = ServiceLocator.instance().getWotRemoteService(); + this.cryptoService = ServiceLocator.instance().getCryptoService(); + } + + @Override + public List<Peer> getPeers(Peer firstPeer) { + + // Default filter + Filter filterDef = new Filter(); + filterDef.filterType = null; + filterDef.filterStatus = Peer.PeerStatus.UP; + filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name()); + + // Default sort + Sort sortDef = new Sort(); + sortDef.sortType = null; + + return getPeers(firstPeer, filterDef, sortDef); + } + + @Override + public List<Peer> getPeers(Peer firstPeer, Filter filter, Sort sort) { + + try { + return asyncGetPeers(firstPeer, null) + .thenCompose(CompletableFutures::allOfToList) + .thenApply(this::fillPeerStatsConsensus) + .thenApply(peers -> peers.stream() + // filter, then sort + .filter(peerFilter(filter)) + .sorted(peerComparator(sort)) + .collect(Collectors.toList())) + .thenApply(this::logPeers) + .get(); + } catch (InterruptedException | ExecutionException e) { + throw new TechnicalException("Error while loading peers: " + e.getMessage(), e); + } + } + + @Override + public Predicate<Peer> peerFilter(final Filter filter) { + return peer -> applyPeerFilter(peer, filter); + } + + @Override + public Comparator<Peer> peerComparator(final Sort sort) { + return Comparator.comparing(peer -> computePeerStatsScore(peer, sort), (score1, score2) -> score2.compareTo(score1)); + } + + @Override + public CompletableFuture<List<CompletableFuture<Peer>>> asyncGetPeers(Peer mainPeer, ExecutorService executor) throws ExecutionException, InterruptedException { + Preconditions.checkNotNull(mainPeer); + + log.debug("Loading network peers..."); + + final ExecutorService pool = (executor != null) ? executor : ForkJoinPool.commonPool(); + + CompletableFuture<List<Peer>> peersFuture = CompletableFuture.supplyAsync(() -> loadPeerLeafs(mainPeer), pool); + CompletableFuture<Map<String, String>> memberUidsFuture = CompletableFuture.supplyAsync(() -> wotRemoteService.getMembersUids(mainPeer), pool); + + return CompletableFuture.allOf( + new CompletableFuture[] {peersFuture, memberUidsFuture}) + .thenApply(v -> { + final Map<String, String> memberUids = memberUidsFuture.join(); + return peersFuture.join().stream().map(peer -> + CompletableFuture.supplyAsync(() -> getVersion(peer), pool) + .thenApply(this::getCurrentBlock) + .exceptionally(throwable -> { + peer.getStats().setStatus(Peer.PeerStatus.DOWN); + if(!(throwable instanceof HttpConnectException)) { + Throwable cause = throwable.getCause() != null ? throwable.getCause() : throwable; + peer.getStats().setError(cause.getMessage()); + if (log.isDebugEnabled()) { + if (log.isTraceEnabled()) { + log.debug(String.format("[%s] is DOWN: %s", peer, cause.getMessage()), cause); + } + else log.debug(String.format("[%s] is DOWN: %s", peer, cause.getMessage())); + } + } + else if (log.isTraceEnabled()) log.debug(String.format("[%s] is DOWN", peer)); + return peer; + }) + .thenApply(apeer -> { + String uid = StringUtils.isNotBlank(peer.getPubkey()) ? memberUids.get(peer.getPubkey()) : null; + peer.getStats().setUid(uid); + if (peer.getStats().isReacheable() && StringUtils.isNotBlank(uid)) { + getHardship(peer); + } + return apeer; + }) + .exceptionally(throwable -> { + peer.getStats().setHardshipLevel(0); + return peer; + }) + ).collect(Collectors.toList()); + }); + } + + public List<Peer> fillPeerStatsConsensus(final List<Peer> peers) { + + final Map<String,Long> peerCountByBuid = peers.stream() + .filter(peer -> peer.getStats().getStatus() == Peer.PeerStatus.UP) + .map(this::buid) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + // Compute main consensus buid + Optional<Map.Entry<String, Long>> maxPeerCountEntry = peerCountByBuid.entrySet().stream() + .sorted(Comparator.comparing(Map.Entry::getValue, (l1, l2) -> l2.compareTo(l1))) + .findFirst(); + + final String mainBuid = maxPeerCountEntry.isPresent() ? maxPeerCountEntry.get().getKey() : null;; + + // Compute total of UP peers + final Long peersUpTotal = peerCountByBuid.values().stream().mapToLong(Long::longValue).sum(); + + // Compute pct by buid + final Map<String, Double> buidsPct = peerCountByBuid.keySet().stream() + .collect(Collectors.toMap( + buid -> buid, + buid -> (peerCountByBuid.get(buid).doubleValue() * 100 / peersUpTotal))); + + // Set consensus stats + peers.forEach(peer -> { + Peer.Stats stats = peer.getStats(); + String buid = buid(stats); + + // Set consensus stats on each peers + if (buid != null) { + boolean isMainConsensus = buid.equals(mainBuid); + stats.setMainConsensus(isMainConsensus); + + boolean isForkConsensus = !isMainConsensus && peerCountByBuid.get(buid) > 1; + stats.setForkConsensus(isForkConsensus); + + stats.setConsensusPct(isMainConsensus || isForkConsensus ? buidsPct.get(buid) : 0d); + } + }); + + return peers; + } + + /* -- protected methods -- */ + + protected List<Peer> loadPeerLeafs(Peer peer) { + List<String> leaves = networkRemoteService.getPeersLeaves(peer); + + if (CollectionUtils.isEmpty(leaves)) return new ArrayList<>(); // should never occur + + // If less than 100 node, get it in ONE call + if (leaves.size() < 100) { + // TODO uncomment on prod + //List<Peer> peers = networkService.getPeers(peer); + //return ImmutableList.of(peers.get(0), peers.get(1), peers.get(2), peers.get(3)); + + //return networkService.getPeers(peer); + } + + // Get it by multiple call on /network/peering?leaf= + List<Peer> result = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(leaves)) { + int offset = 0; + int count = Constants.Config.MAX_SAME_REQUEST_COUNT; + while (offset < leaves.size()) { + if (offset + count > leaves.size()) count = leaves.size() - offset; + loadPeerLeafs(peer, result, leaves, offset, count); + offset += count; + try { + Thread.sleep(1000); // wait 1 s + } catch (InterruptedException e) { + // stop + offset = leaves.size(); + } + } + } + + return result; + } + + protected void loadPeerLeafs(Peer requestedPeer, List<Peer> result, List<String> leaves, int offset, int count) { + + for (int i = offset; i< offset + count; i++) { + String leaf = leaves.get(i); + try { + NetworkPeers.Peer peer = networkRemoteService.getPeerLeaf(requestedPeer, leaf); + + if (CollectionUtils.isNotEmpty(peer.getEndpoints())) { + for (NetworkPeering.Endpoint ep: peer.getEndpoints()) { + if (ep != null && ep.getApi() != null) { + Peer peerEp = Peer.newBuilder() + .setCurrency(peer.getCurrency()) + .setHash(leaf) + .setPubkey(peer.getPubkey()) + .setEndpoint(ep) + .build(); + result.add(peerEp); + } + } + } + + + } catch(HttpNotFoundException e) { + log.warn("Peer not found for leaf=" + leaf); + // skip + } + } + } + + + protected boolean applyPeerFilter(Peer peer, Filter filter) { + + Peer.Stats stats = peer.getStats(); + + // Filter member or mirror + if (filter.filterType != null && ( + (filter.filterType == FilterType.MEMBER && StringUtils.isBlank(stats.getUid())) + || (filter.filterType == FilterType.MIRROR && StringUtils.isNotBlank(stats.getUid())) + )) { + return false; + } + + // Filter on endpoints + if (CollectionUtils.isNotEmpty(filter.filterEndpoints) + && (StringUtils.isBlank(peer.getApi()) + || !filter.filterEndpoints.contains(peer.getApi()))) { + return false; + } + + // Filter on status + if (filter.filterStatus != null && filter.filterStatus != stats.getStatus()) { + return false; + } + + // Filter on SSL + if (filter.filterSsl != null && filter.filterSsl.booleanValue() != peer.isUseSsl()) { + return false; + } + + return true; + } + + protected Peer getVersion(final Peer peer) { + JsonNode json = executeRequest(peer, BMA_URL_STATUS, JsonNode.class); + // TODO update peer + json = json.get("duniter"); + if (json.isMissingNode()) throw new TechnicalException(String.format("Invalid format of [%s] response", BMA_URL_STATUS)); + json = json.get("version"); + if (json.isMissingNode()) throw new TechnicalException(String.format("No version attribute found in [%s] response", BMA_URL_STATUS)); + String version = json.asText(); + peer.getStats().setVersion(version); + return peer; + } + + protected Peer getCurrentBlock(final Peer peer) { + JsonNode json = executeRequest(peer, BMA_URL_BLOCKCHAIN_CURRENT , JsonNode.class); + + Integer number = json.has("number") ? json.get("number").asInt() : null; + peer.getStats().setBlockNumber(number); + + String hash = json.has("hash") ? json.get("hash").asText() : null; + peer.getStats().setBlockHash(hash); + + Long medianTime = json.has("medianTime") ? json.get("medianTime").asLong() : null; + peer.getStats().setMedianTime(medianTime); + + if (log.isTraceEnabled()) { + log.trace(String.format("[%s] current block [%s-%s]", peer.toString(), number, hash)); + } + + return peer; + } + + protected Peer getHardship(final Peer peer) { + if (StringUtils.isBlank(peer.getPubkey())) return peer; + + JsonNode json = executeRequest(peer, BMA_URL_BLOCKCHAIN_HARDSHIP + peer.getPubkey(), JsonNode.class); + Integer level = json.has("level") ? json.get("level").asInt() : null; + peer.getStats().setHardshipLevel(level); + return peer; + } + + protected String computeUniqueId(Peer peer) { + return cryptoService.hash( + new StringJoiner("|") + .add(peer.getPubkey()) + .add(peer.getDns()) + .add(peer.getIpv4()) + .add(peer.getIpv6()) + .add(String.valueOf(peer.getPort())) + .add(Boolean.toString(peer.isUseSsl())) + .toString()); + } + + protected JsonNode get(final Peer peer, String path) { + return executeRequest(peer, path, JsonNode.class); + } + + /** + * Log allOfToList peers found + */ + protected List<Peer> logPeers(final List<Peer> peers) { + if (!log.isDebugEnabled()) return peers; + + if (CollectionUtils.isEmpty(peers)) { + log.debug("No peers found."); + } + else { + log.debug(String.format("Found %s peers", peers.size())); + if (log.isTraceEnabled()) { + + peers.forEach(peerFound -> { + if (peerFound.getStats().getStatus() == Peer.PeerStatus.DOWN) { + String error = peerFound.getStats().getError(); + log.trace(String.format("Found peer [%s] [%s] %s", + peerFound.toString(), + peerFound.getStats().getStatus().name(), + error != null ? error : "")); + } else { + log.trace(String.format("Found peer [%s] [%s] [v%s] block [%s]", peerFound.toString(), + peerFound.getStats().getStatus().name(), + peerFound.getStats().getVersion(), + peerFound.getStats().getBlockNumber() + )); + + } + }); + } + } + return peers; + } + + protected double computePeerStatsScore(Peer peer, Sort sort) { + double score = 0; + Peer.Stats stats = peer.getStats(); + if (sort != null && sort.sortType != null) { + long specScore = 0; + specScore += (sort.sortType == SortType.UID ? computeScoreAlphaValue(stats.getUid(), 3, sort.sortAsc) : 0); + specScore += (sort.sortType == SortType.PUBKEY ? computeScoreAlphaValue(peer.getPubkey(), 3, sort.sortAsc) : 0); + specScore += (sort.sortType == SortType.API ? + (peer.isUseSsl() ? (sort.sortAsc ? 1 : -1) : + (hasEndPointAPI(peer, EndpointApi.ES_USER_API) ? (sort.sortAsc ? 0.5 : -0.5) : 0)) : 0); + specScore += (sort.sortType == SortType.HARDSHIP ? (stats.getHardshipLevel() != null ? (sort.sortAsc ? (10000-stats.getHardshipLevel()) : stats.getHardshipLevel()): 0) : 0); + specScore += (sort.sortType == SortType.BLOCK_NUMBER ? (stats.getBlockNumber() != null ? (sort.sortAsc ? (1000000000 - stats.getBlockNumber()) : stats.getBlockNumber()) : 0) : 0); + score += (10000000000l * specScore); + } + score += (1000000000 * (stats.getStatus() == Peer.PeerStatus.UP ? 1 : 0)); + score += (100000000 * (stats.isMainConsensus() ? 1 : 0)); + score += (1000000 * (stats.isForkConsensus() ? stats.getConsensusPct() : 0)); + + score += (100 * (stats.getHardshipLevel() != null ? (10000-stats.getHardshipLevel()) : 0)); + score += /* 1 */(peer.getPubkey() != null ? computeScoreAlphaValue(peer.getPubkey(), 2, true) : 0); + + return score; + } + + protected int computeScoreAlphaValue(String value, int nbChars, boolean asc) { + if (StringUtils.isBlank(value)) return 0; + int score = 0; + value = value.toLowerCase(); + if (nbChars > value.length()) { + nbChars = value.length(); + } + score += (int)value.charAt(0); + for (int i=1; i < nbChars; i++) { + score += Math.pow(0.001, i) * value.charAt(i); + } + return asc ? (1000 - score) : score; + } + + protected boolean hasEndPointAPI(Peer peer, EndpointApi api) { + return peer.getApi() != null && peer.getApi().equalsIgnoreCase(api.name()); + } + + protected String buid(Peer peer) { + return buid(peer.getStats()); + } + + protected String buid(Peer.Stats stats) { + return stats.getStatus() == Peer.PeerStatus.UP + ? stats.getBlockNumber() + "-" + stats.getBlockHash() + : null; + } +} diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java index d8f3692418f5bb3e62f97b15bc3f983acc723898..f724f1576d1f9f65ac86da21037fbabeb5b45027 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java @@ -150,7 +150,7 @@ public class PeerServiceImpl implements PeerService, InitializingBean { } /** - * Fill all cache need for currencies + * Fill allOfToList cache need for currencies * @param accountId */ public void loadCache(long accountId) { diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java index 28d37dbd7cfc0f9d60788df39c13dd291ccc6321..1029dadb1f1e56926ef3f159918356cf17304957 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java @@ -155,7 +155,10 @@ public class TestResource extends org.duniter.core.test.TestResource { // Set a default account id, then load cache ServiceLocator.instance().getDataContext().setAccountId(0); - Peer peer = new Peer(config.getNodeHost(), config.getNodePort()); + Peer peer = Peer.newBuilder() + .setHost(config.getNodeHost()) + .setPort(config.getNodePort()) + .build(); peer.setCurrencyId(fixtures.getDefaultCurrencyId()); ServiceLocator.instance().getPeerService().save(peer); diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java index 0f96bc8bf125e2a5c361b60bad554e195e955e20..62eb7714b3e6f450e93fbf7c2a7c232772d17727 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java @@ -25,19 +25,13 @@ package org.duniter.core.client.service; import org.duniter.core.client.TestResource; import org.duniter.core.client.config.Configuration; -import org.duniter.core.client.model.bma.EndpointProtocol; -import org.duniter.core.client.model.bma.NetworkPeering; import org.duniter.core.client.model.local.Peer; -import org.duniter.core.client.service.bma.NetworkRemoteService; -import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - public class HttpServiceTest { private static final Logger log = LoggerFactory.getLogger(HttpServiceTest.class); @@ -64,10 +58,9 @@ public class HttpServiceTest { /* -- internal methods */ protected Peer createTestPeer() { - Peer peer = new Peer( - Configuration.instance().getNodeHost(), - Configuration.instance().getNodePort()); - - return peer; + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); } } diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java index fb88104790407a94ceaaefa12e14b31b11828653..33ee0035fd0ca25d5d2ea0b87c946949590222cd 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java @@ -114,7 +114,7 @@ public class BlockchainRemoteServiceTest { Assert.assertNotNull(result); Assert.assertEquals(10, result.length); - // Make sure all json are valid blocks + // Make sure allOfToList json are valid blocks ObjectMapper objectMapper = JacksonUtils.newObjectMapper(); int number = 0; @@ -192,11 +192,10 @@ public class BlockchainRemoteServiceTest { /* -- Internal methods -- */ protected Peer createTestPeer() { - Peer peer = new Peer( - Configuration.instance().getNodeHost(), - Configuration.instance().getNodePort()); - - return peer; + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); } protected Wallet createTestWallet() { diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java index 5544aee2482de27386ee170b4a20a7d4dd27c80a..ccd274e860c2431071a16f67f34e2445664f2fb2 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java @@ -25,7 +25,7 @@ package org.duniter.core.client.service.bma; import org.duniter.core.client.TestResource; import org.duniter.core.client.config.Configuration; -import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.bma.NetworkPeering; import org.duniter.core.client.model.local.Peer; import org.duniter.core.client.service.ServiceLocator; @@ -69,7 +69,7 @@ public class NetworkRemoteServiceTest { @Test public void findPeers() throws Exception { - List<Peer> result = service.findPeers(peer, null, EndpointProtocol.BASIC_MERKLED_API, null, null); + List<Peer> result = service.findPeers(peer, null, EndpointApi.BASIC_MERKLED_API, null, null); Assert.assertNotNull(result); Assert.assertTrue(result.size() > 0); @@ -87,10 +87,9 @@ public class NetworkRemoteServiceTest { /* -- internal methods */ protected Peer createTestPeer() { - Peer peer = new Peer( - Configuration.instance().getNodeHost(), - Configuration.instance().getNodePort()); - - return peer; + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); } } diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java index 8d8950c612d0229f0f3c6742e5345b174d9b5576..3118e7f373661f1c9060abbd5182999c69510331 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java @@ -90,10 +90,9 @@ public class TransactionRemoteServiceTest { } protected Peer createTestPeer() { - Peer peer = new Peer( - Configuration.instance().getNodeHost(), - Configuration.instance().getNodePort()); - - return peer; + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); } } diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java index 5cbbd36e453fa608a099cfc802b0119c521e7f5c..ca52ee154ad72030aa3174afebdb51e5e9ae9325 100644 --- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java @@ -165,10 +165,9 @@ public class WotRemoteServiceTest { } protected Peer createTestPeer() { - Peer peer = new Peer( - Configuration.instance().getNodeHost(), - Configuration.instance().getNodePort()); - - return peer; + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); } } diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8106d95f86b39254e6114fcaefaaf35c4baa6d8d --- /dev/null +++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java @@ -0,0 +1,72 @@ +package org.duniter.core.client.service.local; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program 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. + * + * This program 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 + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import org.duniter.core.client.TestResource; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.ServiceLocator; +import org.junit.Assert; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class NetworkServiceTest { + + private static final Logger log = LoggerFactory.getLogger(NetworkServiceTest.class); + + @ClassRule + public static final TestResource resource = TestResource.create(); + + private NetworkService service; + private Peer peer; + + @Before + public void setUp() { + peer = createTestPeer(); + service = ServiceLocator.instance().getNetworkService(); + } + + @Test + public void start() throws Exception { + + List<Peer> peers = service.getPeers(peer); + + Assert.assertNotNull(peers); + Assert.assertTrue(peers.size() > 0); + } + + /* -- internal methods */ + + protected Peer createTestPeer() { + return Peer.newBuilder() + .setHost(Configuration.instance().getNodeHost()) + .setPort(Configuration.instance().getNodePort()) + .build(); + } +} diff --git a/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean index e4fdb2bbd7e5e975380f1b3256c23970d322ba73..bbf9fb6b0c5cc69950b92beafedb92622ab88201 100644 --- a/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean +++ b/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean @@ -8,5 +8,6 @@ org.duniter.core.client.service.HttpServiceImpl org.duniter.core.client.service.DataContext org.duniter.core.client.service.local.PeerServiceImpl org.duniter.core.client.service.local.CurrencyServiceImpl +org.duniter.core.client.service.local.NetworkServiceImpl org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl org.duniter.core.client.dao.mem.MemoryPeerDaoImpl \ No newline at end of file diff --git a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties index 9e0174256782c42e59993c031833112967a47301..d9f4f75b7e5ea3263c1a2c3b4ac7eecaba84c90e 100644 --- a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties +++ b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties @@ -1,5 +1,5 @@ -duniter4j.node.host=gtest.duniter.org -duniter4j.node.port=10900 +duniter4j.node.host=192.168.0.5 +duniter4j.node.port=10901 duniter4j.node.elasticsearch.host=localhost duniter4j.node.elasticsearch.port=9200 diff --git a/duniter4j-core-client/src/test/resources/log4j.properties b/duniter4j-core-client/src/test/resources/log4j.properties index a3e37387864bcfee1cb503b4b88d5d0f781cafe3..e1c60841fcdf60dd05ccd622f278ae8d34a91a7d 100644 --- a/duniter4j-core-client/src/test/resources/log4j.properties +++ b/duniter4j-core-client/src/test/resources/log4j.properties @@ -7,12 +7,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - %m%n -# ucoin levels -log4j.logger.org.duniter=INFO -log4j.logger.org.duniter.core.client.service=DEBUG -log4j.logger.org.duniter.core.client.service.bma=DEBUG -log4j.logger.org.duniter.core.beans=WARN - +# File output log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.file=ucoin-client.log log4j.appender.file.MaxFileSize=10MB @@ -21,4 +16,15 @@ log4j.appender.file.MaxBackupIndex=4 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p %c - %m%n +# duniter4j levels +log4j.logger.org.duniter=INFO +log4j.logger.org.duniter.core.client.service=DEBUG +log4j.logger.org.duniter.core.client.service.bma=DEBUG +log4j.logger.org.duniter.core.beans=WARN +log4j.logger.org.duniter.core.client.service=TRACE + +# Framework levels +log4j.logger.org.apache.http=WARN + + diff --git a/duniter4j-core-shared/pom.xml b/duniter4j-core-shared/pom.xml index 7844bdeae84993aeec9267c06214ad834b05237b..f826f638bebf7a4043a5a65f45775f4d288a7037 100644 --- a/duniter4j-core-shared/pom.xml +++ b/duniter4j-core-shared/pom.xml @@ -41,6 +41,16 @@ <artifactId>scrypt</artifactId> </dependency> + <!-- http --> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + </dependency> + <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java index 4d1b4d458bc2cd7b40e3b7b7ada17242da1353a8..4567cea4a8f677485eb0249ba5a7ed16f1293b80 100644 --- a/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java @@ -120,7 +120,6 @@ public class BeanFactory implements Closeable{ throw new TechnicalException(String.format("Unable to create bean with type [%s]: not configured for the service loader [%s]", clazz.getName(), Bean.class.getCanonicalName())); } - @Override public void close() throws IOException { for(Object bean: beansCache.values()) { diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java new file mode 100644 index 0000000000000000000000000000000000000000..b0b78f91ae42fd04bdd2234d8968d971c0cede18 --- /dev/null +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java @@ -0,0 +1,38 @@ +package org.duniter.core.util.concurrent; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Helper class on CompletableFuture and concurrent classes + * Created by blavenie on 24/03/17. + */ +public class CompletableFutures { + + private CompletableFutures() { + } + + public static <T> CompletableFuture<List<T>> allOfToList(List<CompletableFuture<T>> futures) { + CompletableFuture<Void> allDoneFuture = + CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); + return allDoneFuture.thenApply(v -> + futures.stream() + .map(future -> future.join()) + .filter(peer -> peer != null) // skip + .collect(Collectors.toList()) + ); + } + + public static <T> CompletableFuture<List<T>> allOfToList(List<CompletableFuture<T>> futures, Predicate<? super T> filter) { + CompletableFuture<Void> allDoneFuture = + CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])); + return allDoneFuture.thenApply(v -> + futures.stream() + .map(future -> future.join()) + .filter(filter) + .collect(Collectors.toList()) + ); + } +} diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b88050ef68d830a8cbda7a38c41177a87b36a6fc --- /dev/null +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java @@ -0,0 +1,27 @@ +package org.duniter.core.util.http; + +import java.util.regex.Pattern; + +/** + * Created by blavenie on 24/03/17. + */ +public class InetAddressUtils { + + public static final Pattern LOCAL_IP_ADDRESS_PATTERN = Pattern.compile("^127[.]0[.]0.|192[.]168[.]|10[.]0[.]0[.]|172[.]16[.]"); + + private InetAddressUtils() { + } + + public static boolean isNotLocalIPv4Address(String input) { + return org.apache.http.conn.util.InetAddressUtils.isIPv4Address(input) && + !LOCAL_IP_ADDRESS_PATTERN.matcher(input).matches(); + } + + public static boolean isIPv4Address(String input) { + return org.apache.http.conn.util.InetAddressUtils.isIPv4Address(input); + } + + public static boolean isIPv6Address(String input) { + return org.apache.http.conn.util.InetAddressUtils.isIPv6Address(input); + } +} diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java index 6a58a0688110cb3731dbefb0fd3b6d53abb3d55e..2f127d8dae2b8f279b75fabcad23c8bec343163a 100644 --- a/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java @@ -136,6 +136,17 @@ public class WebsocketClientEndpoint implements Closeable { } } + /** + * unregister message listener + * + * @param listener + */ + public void unregisterListener(MessageListener listener) { + synchronized (messageListeners) { + this.messageListeners.remove(listener); + } + } + /** * register connection listener * @@ -147,6 +158,18 @@ public class WebsocketClientEndpoint implements Closeable { } } + /** + * unregister connection listener + * + * @param listener + */ + public void unregisterListener(ConnectionListener listener) { + synchronized (connectionListeners) { + this.connectionListeners.remove(listener); + } + } + + /** * Send a message. * diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java index b8d4bde5a57834e778f2ddb9caa12c1148c4a26f..89d277399098291704539d207cd6b39aa1825c55 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java @@ -27,6 +27,7 @@ import org.duniter.core.client.model.local.Peer; import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.BlockchainService; import org.duniter.elasticsearch.service.CurrencyService; +import org.duniter.elasticsearch.service.NetworkService; import org.duniter.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.health.ClusterHealthStatus; @@ -135,6 +136,11 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> { injector.getInstance(BlockchainService.class) .indexLastBlocks(peer) .listenAndIndexNewBlock(peer); + + // Index peers (and listen if new peer appear) + injector.getInstance(NetworkService.class) + .indexLastPeers(peer)/* + .listenAndIndexNewPeer(peer)*/; } } } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java index 50cb48e4bd5290f549d2eec62b2db6ed2d04e57f..e95270d390d0a7cb0ee960d11e5113b5efb94578 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java @@ -105,7 +105,6 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { applicationConfig.setDefaultOption(ConfigurationOption.BASEDIR.getKey(), baseDir); applicationConfig.setDefaultOption(ConfigurationOption.NODE_HOST.getKey(), getNodeBmaHost()); applicationConfig.setDefaultOption(ConfigurationOption.NODE_PORT.getKey(), String.valueOf(getNodeBmaPort())); - applicationConfig.setDefaultOption(ConfigurationOption.NODE_PROTOCOL.getKey(), getNodeBmaUseSsl() ? "https" : "http"); applicationConfig.setDefaultOption(ConfigurationOption.NETWORK_TIMEOUT.getKey(), String.valueOf(getNetworkTimeout())); try { @@ -168,7 +167,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { } public boolean getNodeBmaUseSsl() { - return settings.getAsBoolean("duniter.useSsl", getNodeBmaPort() == 443); + return settings.getAsBoolean("duniter.useSsl", null); } public boolean isIndexBulkEnable() { @@ -227,7 +226,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { return null; } - Peer peer = new Peer(getNodeBmaHost(), getNodeBmaPort(), getNodeBmaUseSsl()); + Peer peer = Peer.newBuilder().setHost(getNodeBmaHost()).setPort(getNodeBmaPort()).setUseSsl(getNodeBmaUseSsl()).build(); return peer; } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java index ce82dfc3435d8b97c1dc264cfa80364a4493525e..8f3192de1985f3dffc53c41e4fdb9f6b492372dd 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java @@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.duniter.core.client.model.bma.BlockchainBlock; import org.duniter.core.client.model.bma.BlockchainParameters; -import org.duniter.core.client.model.bma.EndpointProtocol; +import org.duniter.core.client.model.bma.EndpointApi; import org.duniter.core.client.model.local.Peer; import org.duniter.core.util.json.JsonAttributeParser; import org.duniter.core.client.model.bma.jackson.JacksonUtils; @@ -178,7 +178,7 @@ public class BlockchainService extends AbstractService { // Check if index exists createIndexIfNotExists(currencyName, true/*wait cluster health*/); - // Then index all blocks + // Then index allOfToList blocks BlockchainBlock peerCurrentBlock = blockchainRemoteService.getCurrentBlock(peer); if (peerCurrentBlock != null) { @@ -243,7 +243,7 @@ public class BlockchainService extends AbstractService { progressionModel.setStatus(ProgressionModel.Status.SUCCESS); } else { - logger.warn(String.format("[%s] [%s] Could not indexed all blocks. Missing %s blocks.", currencyName, peer, missingBlocks.size())); + logger.warn(String.format("[%s] [%s] Could not indexed allOfToList blocks. Missing %s blocks.", currencyName, peer, missingBlocks.size())); progressionModel.setStatus(ProgressionModel.Status.FAILED); } } @@ -294,7 +294,7 @@ public class BlockchainService extends AbstractService { .build(); createIndexRequestBuilder.setSettings(indexSettings); createIndexRequestBuilder.addMapping(BLOCK_TYPE, createBlockTypeMapping()); - createIndexRequestBuilder.addMapping(PEER_TYPE, createPeerTypeMapping()); + createIndexRequestBuilder.addMapping(PEER_TYPE, NetworkService.createPeerTypeMapping()); createIndexRequestBuilder.execute().actionGet(); } @@ -689,34 +689,6 @@ public class BlockchainService extends AbstractService { } } - public XContentBuilder createPeerTypeMapping() { - try { - XContentBuilder mapping = XContentFactory.jsonBuilder() - .startObject() - .startObject(PEER_TYPE) - .startObject("properties") - - // currency - .startObject("currency") - .field("type", "string") - .endObject() - - // pubkey - .startObject("pubkey") - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - - - .endObject() - .endObject().endObject(); - - return mapping; - } - catch(IOException ioe) { - throw new TechnicalException("Error while getting mapping for block index: " + ioe.getMessage(), ioe); - } - } public BlockchainBlock getBlockByIdStr(String currencyName, String blockId) { @@ -954,9 +926,9 @@ public class BlockchainService extends AbstractService { // Select other peers, in filtering on the same blockchain version // TODO : a activer quand les peers seront bien mis à jour (UP/DOWN, block, hash...) - //List<Peer> otherPeers = networkRemoteService.findPeers(peer, "UP", EndpointProtocol.BASIC_MERKLED_API, + //List<Peer> otherPeers = networkRemoteService.findPeers(peer, "UP", EndpointApi.BASIC_MERKLED_API, // currentBlock.getNumber(), currentBlock.getHash()); - List<Peer> otherPeers = networkRemoteService.findPeers(peer, null, EndpointProtocol.BASIC_MERKLED_API, + List<Peer> otherPeers = networkRemoteService.findPeers(peer, null, EndpointApi.BASIC_MERKLED_API, null, null); for(Peer childPeer: otherPeers) { diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java new file mode 100644 index 0000000000000000000000000000000000000000..209abd269a9356cc65ba89a5cc2419a95861306e --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java @@ -0,0 +1,571 @@ +package org.duniter.elasticsearch.service; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program 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. + * + * This program 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 + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.duniter.core.client.model.bma.BlockchainParameters; +import org.duniter.core.client.model.bma.EndpointApi; +import org.duniter.core.client.model.bma.jackson.JacksonUtils; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.local.NetworkServiceImpl; +import org.duniter.core.exception.TechnicalException; +import org.duniter.core.model.NullProgressionModel; +import org.duniter.core.model.ProgressionModel; +import org.duniter.core.util.CollectionUtils; +import org.duniter.core.util.Preconditions; +import org.duniter.core.util.StringUtils; +import org.duniter.core.util.concurrent.CompletableFutures; +import org.duniter.core.util.json.JsonAttributeParser; +import org.duniter.core.util.json.JsonSyntaxException; +import org.duniter.core.util.websocket.WebsocketClientEndpoint; +import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.exception.DuplicateIndexIdException; +import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.action.ActionFuture; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchRequestBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.action.update.UpdateRequestBuilder; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.search.SearchHitField; +import org.elasticsearch.search.highlight.HighlightField; +import org.elasticsearch.search.sort.SortOrder; +import org.nuiton.i18n.I18n; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +/** + * Created by Benoit on 30/03/2015. + */ +public class NetworkService extends AbstractService { + + public static final String PEER_TYPE = "peer"; + + private final ProgressionModel nullProgressionModel = new NullProgressionModel(); + + private org.duniter.core.client.service.bma.BlockchainRemoteService blockchainRemoteService; + private org.duniter.core.client.service.local.NetworkService networkService; + private ThreadPool threadPool; + private List<WebsocketClientEndpoint.ConnectionListener> connectionListeners = new ArrayList<>(); + private final WebsocketClientEndpoint.ConnectionListener dispatchConnectionListener; + + private final JsonAttributeParser blockCurrencyParser = new JsonAttributeParser("currency"); + private final JsonAttributeParser blockHashParser = new JsonAttributeParser("hash"); + + private ObjectMapper objectMapper; + + @Inject + public NetworkService(Client client, PluginSettings settings, ThreadPool threadPool, + final ServiceLocator serviceLocator){ + super("duniter.network", client, settings); + this.objectMapper = JacksonUtils.newObjectMapper(); + this.threadPool = threadPool; + threadPool.scheduleOnStarted(() -> { + this.blockchainRemoteService = serviceLocator.getBlockchainRemoteService(); + this.networkService = serviceLocator.getNetworkService(); + }); + dispatchConnectionListener = new WebsocketClientEndpoint.ConnectionListener() { + @Override + public void onSuccess() { + synchronized (connectionListeners) { + connectionListeners.stream().forEach(connectionListener -> connectionListener.onSuccess()); + } + } + @Override + public void onError(Exception e, long lastTimeUp) { + synchronized (connectionListeners) { + connectionListeners.stream().forEach(connectionListener -> connectionListener.onError(e, lastTimeUp)); + } + } + }; + } + + public void registerConnectionListener(WebsocketClientEndpoint.ConnectionListener listener) { + synchronized (connectionListeners) { + connectionListeners.add(listener); + } + } + + public NetworkService indexLastPeers(Peer peer) { + indexLastPeers(peer, nullProgressionModel); + return this; + } + + public NetworkService indexLastPeers(Peer peer, ProgressionModel progressionModel) { + + try { + // Get the blockchain name from node + BlockchainParameters parameter = blockchainRemoteService.getParameters(peer); + if (parameter == null) { + progressionModel.setStatus(ProgressionModel.Status.FAILED); + logger.error(I18n.t("duniter4j.es.networkService.indexPeers.remoteParametersError", peer)); + return this; + } + String currencyName = parameter.getCurrency(); + + indexPeers(currencyName, peer, progressionModel); + + } catch(Exception e) { + logger.error("Error during indexLastPeers: " + e.getMessage(), e); + progressionModel.setStatus(ProgressionModel.Status.FAILED); + } + + return this; + } + + + public NetworkService indexPeers(String currencyName, Peer firstPeer, ProgressionModel progressionModel) { + progressionModel.setStatus(ProgressionModel.Status.RUNNING); + progressionModel.setTotal(100); + long timeStart = System.currentTimeMillis(); + + try { + + progressionModel.setTask(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer)); + logger.info(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer)); + + // Default filter + org.duniter.core.client.service.local.NetworkService.Filter filterDef = new org.duniter.core.client.service.local.NetworkService.Filter(); + filterDef.filterType = null; + filterDef.filterStatus = Peer.PeerStatus.UP; + filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name()); + + // Default sort + org.duniter.core.client.service.local.NetworkService.Sort sortDef = new org.duniter.core.client.service.local.NetworkService.Sort(); + sortDef.sortType = null; + + try { + networkService.asyncGetPeers(firstPeer, threadPool.scheduler()) + .thenCompose(CompletableFutures::allOfToList) + .thenApply(networkService::fillPeerStatsConsensus) + .thenApply(peers -> peers.stream() + // filter, then sort + .filter(networkService.peerFilter(filterDef)) + .map(peer -> savePeer(peer, false)) + .collect(Collectors.toList())) + .thenApply(peers -> { + logger.info(I18n.t("duniter4j.es.networkService.indexPeers.succeed", currencyName, firstPeer, peers.size(), (System.currentTimeMillis() - timeStart))); + progressionModel.setStatus(ProgressionModel.Status.SUCCESS); + return peers; + }); + } catch (InterruptedException | ExecutionException e) { + throw new TechnicalException("Error while loading peers: " + e.getMessage(), e); + } + } catch(Exception e) { + logger.error("Error during indexBlocksFromNode: " + e.getMessage(), e); + progressionModel.setStatus(ProgressionModel.Status.FAILED); + } + + return this; + } + +/* + public void start(Peer peer, FilterAndSortSpec networkSpec) { + Preconditions.checkNotNull(networkSpec); + this.networkSpec = networkSpec; + start(peer); + } + + public void start(Peer peer) { + Preconditions.checkNotNull(peer); + + log.debug("Starting network crawler..."); + + addListeners(peer); + + this.mainPeer = peer; + + try { + this.peers = loadPeers(this.mainPeer).get(); + } + catch(Exception e) { + throw new TechnicalException("Error during start load peers", e); + } + + isStarted = true; + log.info("Network crawler started"); + } + + public void stop() { + if (!isStarted) return; + log.debug("Stopping network crawler..."); + + removeListeners(); + + this.mainPeer = null; + this.mainPeerWsEp = null; + this.isStarted = false; + + this.executorService.shutdown(); + + log.info("Network crawler stopped"); + }*/ + + /** + * Create or update a peer, depending on its existence and hash + * @param peer + * @param wait wait indexBlocksFromNode end + * @throws DuplicateIndexIdException + */ + public Peer savePeer(final Peer peer, boolean wait) throws DuplicateIndexIdException { + Preconditions.checkNotNull(peer, "peer could not be null") ; + Preconditions.checkNotNull(peer.getCurrency(), "peer attribute 'currency' could not be null"); + //Preconditions.checkNotNull(peer.getHash(), "peer attribute 'hash' could not be null"); + Preconditions.checkNotNull(peer.getPubkey(), "peer attribute 'pubkey' could not be null"); + Preconditions.checkNotNull(peer.getHost(), "peer attribute 'host' could not be null"); + Preconditions.checkNotNull(peer.getApi(), "peer 'api' could not be null"); + + Peer existingPeer = getPeerByHash(peer.getCurrency(), peer.getHash()); + + // Currency not exists, or has changed, so create it + if (existingPeer == null) { + if (logger.isTraceEnabled()) { + logger.trace(String.format("Insert new peer [%s]", peer)); + } + + // Index new peer + indexPeer(peer, wait); + } + + // Update existing peer + else { + logger.trace(String.format("Update peer [%s]", peer)); + updatePeer(peer, wait); + } + return peer; + } + + public void indexPeer(Peer peer, boolean wait) { + Preconditions.checkNotNull(peer); + Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency())); + Preconditions.checkNotNull(peer.getHash()); + Preconditions.checkNotNull(peer.getHost()); + Preconditions.checkNotNull(peer.getApi()); + + // Serialize into JSON + // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String) + try { + String json = objectMapper.writeValueAsString(peer); + + // Preparing indexBlocksFromNode + IndexRequestBuilder indexRequest = client.prepareIndex(peer.getCurrency(), PEER_TYPE) + .setId(peer.getHash()) + .setSource(json); + + // Execute indexBlocksFromNode + ActionFuture<IndexResponse> futureResponse = indexRequest + .setRefresh(true) + .execute(); + + if (wait) { + futureResponse.actionGet(); + } + } + catch(JsonProcessingException e) { + throw new TechnicalException(e); + } + } + + public void updatePeer(Peer peer, boolean wait) { + Preconditions.checkNotNull(peer); + Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency())); + Preconditions.checkNotNull(peer.getHash()); + Preconditions.checkNotNull(peer.getHost()); + Preconditions.checkNotNull(peer.getApi()); + + // Serialize into JSON + // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String) + try { + String json = objectMapper.writeValueAsString(peer); + + // Preparing indexBlocksFromNode + UpdateRequestBuilder updateRequest = client.prepareUpdate(peer.getCurrency(), PEER_TYPE, peer.getHash()) + .setDoc(json); + + // Execute indexBlocksFromNode + ActionFuture<UpdateResponse> futureResponse = updateRequest + .setRefresh(true) + .execute(); + + if (wait) { + futureResponse.actionGet(); + } + } + catch(JsonProcessingException e) { + throw new TechnicalException(e); + } + } + + /** + * + * @param currencyName + * @param number the peer hash + * @param json block as JSON + */ + public NetworkService indexPeerFromJson(String currencyName, int number, byte[] json, boolean refresh, boolean wait) { + Preconditions.checkNotNull(json); + Preconditions.checkArgument(json.length > 0); + + // Preparing indexBlocksFromNode + IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, PEER_TYPE) + .setId(String.valueOf(number)) + .setRefresh(refresh) + .setSource(json); + + // Execute indexBlocksFromNode + if (!wait) { + indexRequest.execute(); + } + else { + indexRequest.execute().actionGet(); + } + + return this; + } + + /** + * Index the given block, as the last (current) block. This will check is a fork has occur, and apply a rollback so. + */ + public void onNetworkChanged() { + logger.info("ES network service -> peers changed: TODO: index new peers"); + } + + /** + * + * @param json block as json + * @param refresh Enable ES update with 'refresh' tag ? + * @param wait need to wait until processed ? + */ + public NetworkService indexPeer(Peer peer, String json, boolean refresh, boolean wait) { + Preconditions.checkNotNull(json); + Preconditions.checkArgument(json.length() > 0); + + String currencyName = blockCurrencyParser.getValueAsString(json); + String hash = blockHashParser.getValueAsString(json); + + logger.info(I18n.t("duniter4j.es.networkService.indexPeer", currencyName, peer)); + if (logger.isTraceEnabled()) { + logger.trace(json); + } + + + // Preparing index + IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, PEER_TYPE) + .setId(hash) + .setRefresh(refresh) + .setSource(json); + + // Execute indexBlocksFromNode + if (!wait) { + indexRequest.execute(); + } + else { + indexRequest.execute().actionGet(); + } + + return this; + } + + public List<Peer> findPeersByHash(String currencyName, String query) { + String[] queryParts = query.split("[\\t ]+"); + + // Prepare request + SearchRequestBuilder searchRequest = client + .prepareSearch(currencyName) + .setTypes(PEER_TYPE) + .setSearchType(SearchType.DFS_QUERY_THEN_FETCH); + + // If only one term, search as prefix + if (queryParts.length == 1) { + searchRequest.setQuery(QueryBuilders.prefixQuery("hash", query)); + } + + // If more than a word, search on terms match + else { + searchRequest.setQuery(QueryBuilders.matchQuery("hash", query)); + } + + // Sort as score/memberCount + searchRequest.addSort("_score", SortOrder.DESC) + .addSort("number", SortOrder.DESC); + + // Highlight matched words + searchRequest.setHighlighterTagsSchema("styled") + .addHighlightedField("hash") + .addFields("hash") + .addFields("*", "_source"); + + // Execute query + SearchResponse searchResponse = searchRequest.execute().actionGet(); + + // Read query result + return toBlocks(searchResponse, true); + } + + /* -- Internal methods -- */ + + public static XContentBuilder createPeerTypeMapping() { + try { + XContentBuilder mapping = XContentFactory.jsonBuilder() + .startObject() + .startObject(PEER_TYPE) + .startObject("properties") + + // currency + .startObject("currency") + .field("sortType", "string") + .endObject() + + // pubkey + .startObject("pubkey") + .field("sortType", "string") + .field("index", "not_analyzed") + .endObject() + + // api + .startObject("api") + .field("sortType", "string") + .field("index", "not_analyzed") + .endObject() + + // uid + .startObject("uid") + .field("sortType", "string") + .endObject() + + // dns + .startObject("dns") + .field("sortType", "string") + .endObject() + + // ipv4 + .startObject("ipv4") + .field("sortType", "string") + .endObject() + + // ipv6 + .startObject("ipv6") + .field("sortType", "string") + .endObject() + + .endObject() + .endObject().endObject(); + + return mapping; + } + catch(IOException ioe) { + throw new TechnicalException("Error while getting mapping for peer index: " + ioe.getMessage(), ioe); + } + } + + public Peer getPeerByHash(String currencyName, String hash) { + + // Prepare request + SearchRequestBuilder searchRequest = client + .prepareSearch(currencyName) + .setTypes(PEER_TYPE) + .setSearchType(SearchType.DFS_QUERY_THEN_FETCH); + + // If more than a word, search on terms match + searchRequest.setQuery(QueryBuilders.matchQuery("_id", hash)); + + // Execute query + try { + SearchResponse searchResponse = searchRequest.execute().actionGet(); + List<Peer> blocks = toBlocks(searchResponse, false); + if (CollectionUtils.isEmpty(blocks)) { + return null; + } + + // Return the unique result + return CollectionUtils.extractSingleton(blocks); + } + catch(JsonSyntaxException e) { + throw new TechnicalException(String.format("Error while getting indexed peer #%s for [%s]", hash, currencyName), e); + } + + } + + protected List<Peer> toBlocks(SearchResponse response, boolean withHighlight) { + // Read query result + List<Peer> result = Lists.newArrayList(); + response.getHits().forEach(searchHit -> { + Peer peer; + if (searchHit.source() != null) { + String jsonString = new String(searchHit.source()); + try { + peer = objectMapper.readValue(jsonString, Peer.class); + } catch(Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Error while parsing peer from JSON:\n" + jsonString); + } + throw new JsonSyntaxException("Error while read peer from JSON: " + e.getMessage(), e); + } + } + else { + peer = new Peer(); + SearchHitField field = searchHit.getFields().get("hash"); + peer.setHash(field.getValue()); + } + result.add(peer); + + // If possible, use highlights + if (withHighlight) { + Map<String, HighlightField> fields = searchHit.getHighlightFields(); + for (HighlightField field : fields.values()) { + String blockNameHighLight = field.getFragments()[0].string(); + peer.setHash(blockNameHighLight); + } + } + }); + + return result; + } + + + protected void reportIndexPeersProgress(ProgressionModel progressionModel, String currencyName, Peer peer, int offset, int total) { + int pct = offset * 100 / total; + progressionModel.setCurrent(pct); + + progressionModel.setMessage(I18n.t("duniter4j.es.networkService.indexPeers.progress", currencyName, peer, offset, pct)); + if (logger.isInfoEnabled()) { + logger.info(I18n.t("duniter4j.es.networkService.indexPeers.progress", currencyName, peer, offset, pct)); + } + + } + +} diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java index 23609da14728cbbdb23236312bbf14b34f5c0bb2..b264f91d209a4c23edb2f867aa50260b5879c327 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java @@ -33,17 +33,15 @@ import org.duniter.core.client.service.DataContext; import org.duniter.core.client.service.HttpService; import org.duniter.core.client.service.HttpServiceImpl; import org.duniter.core.client.service.bma.*; +import org.duniter.core.client.service.local.*; +import org.duniter.core.client.service.local.NetworkService; import org.duniter.core.client.service.local.CurrencyService; -import org.duniter.core.client.service.local.CurrencyServiceImpl; -import org.duniter.core.client.service.local.PeerService; -import org.duniter.core.client.service.local.PeerServiceImpl; import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; import org.duniter.core.service.Ed25519CryptoServiceImpl; import org.duniter.core.service.MailService; import org.duniter.core.service.MailServiceImpl; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.inject.Singleton; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLoggerFactory; @@ -95,6 +93,7 @@ public class ServiceLocator .bind(MailService.class, MailServiceImpl.class) .bind(PeerService.class, PeerServiceImpl.class) .bind(CurrencyService.class, CurrencyServiceImpl.class) + .bind(NetworkService.class, NetworkServiceImpl.class) .bind(HttpService.class, HttpServiceImpl.class) .bind(CurrencyDao.class, MemoryCurrencyDaoImpl.class) .bind(PeerDao.class, MemoryPeerDaoImpl.class) diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java index fd377ed5be4acb49d245acff3519979cbc9470d0..68d7ccbb7f533da43ce6deed98c7f4c65ae874e3 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java @@ -52,8 +52,9 @@ public class ServiceModule extends AbstractModule implements Module { bind(PluginInit.class).asEagerSingleton(); bind(ChangeService.class).asEagerSingleton(); - // indexation services + // blockchain indexation services bind(BlockchainService.class).asEagerSingleton(); + bind(NetworkService.class).asEagerSingleton(); // Duniter Client API beans bindWithLocator(BlockchainRemoteService.class); diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java index 4818cbf50e5e81c7031c56738f450dbf70e4790d..3025a909921d79b086aae5ab22a37467ba2beef9 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java @@ -98,7 +98,7 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { public void doClose() {} /** - * Schedules an rest when node is started (all services and modules ready) + * Schedules an rest when node is started (allOfToList services and modules ready) * * @param job the rest to execute when node started * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled @@ -233,4 +233,7 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { return canContinue; } + public ScheduledExecutorService scheduler() { + return delegate.scheduler(); + } } diff --git a/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean index 0f39d76137c59998de28bf005b0fb022bd52251e..1cf6cb3c777043f71eecb37be3c997efedf6cae7 100644 --- a/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean +++ b/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean @@ -9,6 +9,7 @@ org.duniter.core.client.service.HttpServiceImpl org.duniter.core.client.service.DataContext org.duniter.core.client.service.local.PeerServiceImpl org.duniter.core.client.service.local.CurrencyServiceImpl +org.duniter.core.client.service.local.NetworkServiceImpl org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl org.duniter.core.client.dao.mem.MemoryPeerDaoImpl diff --git a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties index a6067b359c9b1b3288467e75f3b34faaa2ce093d..f7bd9849ff9e2736d36bd984c22e02aabd7ae8b3 100644 --- a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties +++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties @@ -34,6 +34,11 @@ duniter4j.config.option.tasks.queueCapacity.description= duniter4j.config.option.temp.directory.description= duniter4j.config.option.version.description= duniter4j.config.parse.error= +duniter4j.es.networkService.indexPeer= +duniter4j.es.networkService.indexPeers.progress= +duniter4j.es.networkService.indexPeers.remoteParametersError= +duniter4j.es.networkService.indexPeers.succeed= +duniter4j.es.networkService.indexPeers.task= duniter4j.executor.task.waitingExecution= duniter4j.job.stopped= duniter4j.job.stopping= diff --git a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties index 7e5877795b675d15bc9d49e6fd0be56f4cb548c5..f8468a743c945cf81976dfdda02a1fd0a1171493 100644 --- a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties +++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties @@ -34,6 +34,11 @@ duniter4j.config.option.tasks.queueCapacity.description= duniter4j.config.option.temp.directory.description= duniter4j.config.option.version.description= duniter4j.config.parse.error= +duniter4j.es.networkService.indexPeer=[%s] Indexing peer [%s]... +duniter4j.es.networkService.indexPeers.progress=[%s] [%s] Indexing peers (%s%%)... +duniter4j.es.networkService.indexPeers.remoteParametersError=[%s] Error when calling [/blockchain/parameters]\: %s +duniter4j.es.networkService.indexPeers.succeed=[%s] [%s] All peers indexed\: found [%s] in [%s ms] +duniter4j.es.networkService.indexPeers.task=[%s] [%s] Indexing peers... duniter4j.executor.task.waitingExecution= duniter4j.job.stopped= duniter4j.job.stopping= diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java index dadcc55a39214c4e77bd548a5d38971c925841ad..b4b30dfb38565b4d839cdfad89567f409cb43f54 100644 --- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java @@ -105,7 +105,7 @@ public class CitiesRegistryService extends AbstractService { public void initCities() { if (log.isDebugEnabled()) { - log.debug("Initializing all registry cities"); + log.debug("Initializing allOfToList registry cities"); } //File bulkFile = createCitiesBulkFile2(); diff --git a/pom.xml b/pom.xml index 3896935deef855cc2f3e33b77e429572635d59b5..c5c5a9db4363cd31a6d667af45de2e10577fb8df 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,7 @@ <module>duniter4j-es-user</module> <module>duniter4j-es-gchange</module> <module>duniter4j-es-assembly</module> + <module>duniter4j-cmd</module> </modules> <scm> @@ -408,7 +409,7 @@ <!-- This is need to override the option version, in configuration classes --> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries> - <!-- Main class, configured in sub-modules --> + <!-- fr.duniter.cmd.Main class, configured in sub-modules --> <mainClass>${maven.jar.main.class}</mainClass> </manifest> <manifestEntries>