Commit cd731ca9 authored by Benoit Lavenier's avatar Benoit Lavenier

[enh] Add post/get to '/network/peering'

parent 56438b41
......@@ -180,12 +180,35 @@ duniter.p2p.includes.endpoints: [
#
# Pass a list of pubkeys to always synchronize (default: <empty>)
#
# duniter.p2p.includes.pubkeys: [""]
# duniter.p2p.includes.pubkeys: [
# "38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE"
# ]
#
# Need to full resync using P2P endpoints, at startup. Useful when new pods has been added
# Enable a full synchro. This will compare each documents from other peers.
#
# duniter.p2p.fullResyncAtStartup: true
#
# Enable publishing of pod endpoints to the network (see the peer document in Duniter protocol). (Default: '${duniter.p2p.enable}')
#
# duniter.p2p.peering.enable: false
#
# Define targeted API (for peers slection) where to send the peer document (if peering is enable). (Default: ["BASIC_MERKLED_API", "BMAS"])
# This API should accept a POST request to '/network/peering' (will send a see the Duniter protocol)
#
# duniter.p2p.peering.targetedApis: [
# "BASIC_MERKLED_API", "BMAS"
# ]
#
# Define cluster API to publish (if peering is enable). By default, all compatible API
#
# duniter.p2p.peering.publishedApis: [
# "ES_CORE_API", "ES_USER_API", "ES_SUBSCRIPTION_API"
# ]
#
# Interval for publishing peer document to the network, in seconds. (Default: 7200 =2h)
#
# duniter.p2p.peering.interval: 7200
#
# ---------------------------------- Duniter4j document moderation ---------------
#
# Filter too old document, if time older that 'maxPastDelta' (in seconds). (default: 7200 =2h)
......@@ -255,25 +278,25 @@ duniter.subscription.enable: false
#
# Email subscription: URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
#
# duniter.subscription.email.link.url: 'http://domain.com/cesium'
# duniter.subscription.email.link.url: 'https://domain.com/cesium'
#
# ---------------------------------- Duniter4j User (profile, message) module -------------------
# ---------------------------------- Duniter4j Share module -------------------
#
#
# Share link: `og:site_name` (default: 'Cesium')
# Share title: `og:site_name` (default: 'Cesium')
#
# duniter.share.site.name: 'Cesium - Ğ1'
#
# Share page link : URL to a web site, for links to a page (default: https://g1.duniter.fr/#/app/page/view/{id}/{title} )
# Usable variables are: {id} and {title}
# URL to a page (default: https://g1.duniter.fr/#/app/page/view/{id}/{title} )
# Note: available variables are {id} and {title}
#
# duniter.share.page.link.url: 'https://domain.com/cesium/#/app/page/view/{id}/{title}'
#
# Share user link : URL to a web site, for links to a user (default: https://g1.duniter.fr/#https://g1.duniter.fr/#/app/wot/{pubkey}/{title} )
# Usable variables are: {pubkey} and {title}
# URL to a user profile (default: https://g1.duniter.fr/#/app/wot/{pubkey}/{title} )
# Note: available variables are {pubkey} and {title}
#
# duniter.share.user.link.url: 'https://domain.com/cesium/#/app/wot/{pubkey}/{title}'
#
# Share default image: URL of an image (min size of 200x200px) to use as default image for `og:image` (default: https://g1.duniter.fr/img/logo_200px.png)
# Default image to share (min size of 200x200px) for `og:image` (default: https://g1.duniter.fr/img/logo_200px.png)
#
# duniter.share.image.default.url: https://g1.duniter.fr/img/logo_200px.png
\ No newline at end of file
# duniter.share.image.default.url: 'https://domain.com/cesium/img/logo_200px.png'
\ No newline at end of file
#!/bin/sh
curl -XPOST 'http://localhost:9200/network/peering' -d 'Version: 10
Type: Peer
Currency: g1
PublicKey: G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU
Block: 162921-000001698A08C8877FAF02A3C4547CD932765CF3994FF4747F3C1EC0EA303C7E
Endpoints:
ES_USER_API localhost 9201
ES_SUBSCRIPTION_API localhost 9201
ES_CORE_API localhost 9201
YzUtzvZEzcaKvrb5TCWnR7+J2L+AUkp9JX0EnKzbw4RstVzT4tYXMBUCfMgQm2TwkbZPk/SCnQ38aixv+CfZBQ=='
\ No newline at end of file
......@@ -26,10 +26,7 @@ import org.duniter.core.client.model.elasticsearch.Currency;
import org.duniter.core.client.model.local.Peer;
import org.duniter.elasticsearch.dao.*;
import org.duniter.elasticsearch.rest.security.RestSecurityController;
import org.duniter.elasticsearch.service.BlockchainService;
import org.duniter.elasticsearch.service.CurrencyService;
import org.duniter.elasticsearch.service.DocStatService;
import org.duniter.elasticsearch.service.PeerService;
import org.duniter.elasticsearch.service.*;
import org.duniter.elasticsearch.synchro.SynchroService;
import org.duniter.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
......@@ -255,10 +252,15 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
.indexLastBlocks(peer)
.listenAndIndexNewBlock(peer);
// Index peers (and listen if new peer appear)
injector.getInstance(PeerService.class)
.listenAndIndexPeers(peer);
if (logger.isInfoEnabled()) {
logger.info(String.format("[%s] Indexing blockchain [OK]", currencyName));
}
// Index peers (and listen if new peer appear)
if (pluginSettings.enableSynchroDiscovery()) {
injector.getInstance(PeerService.class)
.listenAndIndexPeers(peer);
}
// Start synchro
if (pluginSettings.enableSynchro()) {
......@@ -266,8 +268,10 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
.startScheduling();
}
if (logger.isInfoEnabled()) {
logger.info(String.format("[%s] Indexing blockchain [OK]", currencyName));
// Start publish peering
if (pluginSettings.enablePeering()) {
injector.getInstance(NetworkService.class)
.startPublishingPeerDocumentToNetwork();
}
} catch(Throwable e){
......
......@@ -28,9 +28,14 @@ import org.apache.commons.io.FileUtils;
import org.duniter.core.client.config.Configuration;
import org.duniter.core.client.config.ConfigurationOption;
import org.duniter.core.client.config.ConfigurationProvider;
import org.duniter.core.client.model.bma.EndpointApi;
import org.duniter.core.client.model.local.Peer;
import org.duniter.core.exception.TechnicalException;
import org.duniter.core.service.CryptoService;
import org.duniter.core.util.CollectionUtils;
import org.duniter.core.util.StringUtils;
import org.duniter.core.util.crypto.CryptoUtils;
import org.duniter.core.util.crypto.KeyPair;
import org.duniter.elasticsearch.i18n.I18nInitializer;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
......@@ -43,10 +48,8 @@ import org.nuiton.i18n.I18n;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static org.nuiton.i18n.I18n.t;
......@@ -57,11 +60,14 @@ import static org.nuiton.i18n.I18n.t;
*/
public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
protected final Settings settings;
private static KeyPair nodeKeyPair;
private static boolean isRandomNodeKeyPair;
private static String nodePubkey;
protected final Settings settings;
private List<String> i18nBundleNames = new ArrayList<>(); // Default
private String clusterRemoteUrl;
private final CryptoService cryptoService;
/**
* Delegate application config.
......@@ -70,10 +76,12 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
protected final org.duniter.core.client.config.Configuration clientConfig;
@Inject
public PluginSettings(org.elasticsearch.common.settings.Settings settings) {
public PluginSettings(org.elasticsearch.common.settings.Settings settings,
CryptoService cryptoService) {
super(settings);
this.settings = settings;
this.cryptoService = cryptoService;
this.applicationConfig = new ApplicationConfig();
// Cascade the application config to the client module
......@@ -265,6 +273,44 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
return settings.getAsBoolean("duniter.p2p.ws.enable", true);
}
public boolean enablePeering() {
return this.settings.getAsBoolean("duniter.p2p.peering.enable", enableSynchro());
}
/**
* Endpoint API to publish, in the emitted peer document. By default, plugins will defined their own API
* @return
*/
public List<EndpointApi> getPeeringPublishedApis() {
String[] targetedApis = settings.getAsArray("duniter.p2p.peering.publishedApis");
if (CollectionUtils.isEmpty(targetedApis)) return null;
return Arrays.stream(targetedApis).map(EndpointApi::valueOf).collect(Collectors.toList());
}
/**
* Targeted API where to send the peer document.
* This API should accept a POST request to '/network/peering' (like Duniter node, but can also be a pod)
* @return
*/
public List<EndpointApi> getPeeringTargetedApis() {
String[] targetedApis = settings.getAsArray("duniter.p2p.peering.targetedApis", new String[]{
EndpointApi.BASIC_MERKLED_API.name(),
EndpointApi.BMAS.name()
});
if (CollectionUtils.isEmpty(targetedApis)) return null;
return Arrays.stream(targetedApis).map(EndpointApi::valueOf).collect(Collectors.toList());
}
/**
* Interval (in seconds) between publications of the peer document
* @return
*/
public int getPeeringInterval() {
return this.settings.getAsInt("duniter.p2p.peering.interval", 7200 /*=2h*/);
}
public boolean fullResyncAtStartup() {
return settings.getAsBoolean("duniter.p2p.fullResyncAtStartup", false);
}
......@@ -293,6 +339,10 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
return settings.getAsInt("duniter.retry.count", 5);
}
/**
* Time before retry (in millis)
* @return
*/
public int getNodeRetryWaitDuration() {
return settings.getAsInt("duniter.retry.waitDuration", 5000);
}
......@@ -432,4 +482,42 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
implementationVersion);
}
}
public KeyPair getNodeKeypair() {
initNodeKeyring();
return this.nodeKeyPair;
}
public boolean isRandomNodeKeypair() {
initNodeKeyring();
return this.isRandomNodeKeyPair;
}
public String getNodePubkey() {
initNodeKeyring();
return this.nodePubkey;
}
protected synchronized void initNodeKeyring() {
if (this.nodeKeyPair != null) return;
if (StringUtils.isNotBlank(getKeyringSalt()) &&
StringUtils.isNotBlank(getKeyringPassword())) {
this.nodeKeyPair = cryptoService.getKeyPair(getKeyringSalt(), getKeyringPassword());
this.nodePubkey = CryptoUtils.encodeBase58(this.nodeKeyPair.getPubKey());
this.isRandomNodeKeyPair = false;
}
else {
// Use a ramdom keypair
this.nodeKeyPair = cryptoService.getRandomKeypair();
this.nodePubkey = CryptoUtils.encodeBase58(this.nodeKeyPair.getPubKey());
this.isRandomNodeKeyPair = true;
logger.warn(String.format("No keyring in config. salt/password (or keyring) is need to signed user event documents. Will use a generated key [%s]", this.nodePubkey));
if (logger.isDebugEnabled()) {
logger.debug(String.format(" salt: " + getKeyringSalt().replaceAll(".", "*")));
logger.debug(String.format("password: " + getKeyringPassword().replaceAll(".", "*")));
}
}
}
}
......@@ -219,10 +219,10 @@ public class PeerDaoImpl extends AbstractDao implements PeerDao {
}
@Override
public void updatePeersAsDown(String currencyName, long upTimeLimit) {
public void updatePeersAsDown(String currencyName, long upTimeLimitInSec) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("[%s] Setting peers as DOWN, if older than [%s]...", currencyName, new Date(upTimeLimit*1000)));
logger.debug(String.format("[%s] Setting peers as DOWN, if older than [%s]...", currencyName, new Date(upTimeLimitInSec*1000)));
}
SearchRequestBuilder searchRequest = client.prepareSearch(currencyName)
......@@ -232,7 +232,7 @@ public class PeerDaoImpl extends AbstractDao implements PeerDao {
// Query = filter on lastUpTime
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
// where lastUpTime < upTimeLimit
.filter(QueryBuilders.rangeQuery(Peer.PROPERTY_STATS + "." + Peer.Stats.PROPERTY_LAST_UP_TIME).lte(upTimeLimit))
.filter(QueryBuilders.rangeQuery(Peer.PROPERTY_STATS + "." + Peer.Stats.PROPERTY_LAST_UP_TIME).lte(upTimeLimitInSec))
// AND status = UP
.filter(QueryBuilders.termQuery(Peer.PROPERTY_STATS + "." + Peer.Stats.PROPERTY_STATUS, Peer.PeerStatus.UP.name()));
searchRequest.setQuery(QueryBuilders.nestedQuery(Peer.PROPERTY_STATS, QueryBuilders.constantScoreQuery(boolQuery)));
......
......@@ -23,7 +23,8 @@ package org.duniter.elasticsearch.rest;
*/
import org.duniter.elasticsearch.rest.attachment.RestImageAttachmentAction;
import org.duniter.elasticsearch.rest.currency.RestCurrencyIndexAction;
import org.duniter.elasticsearch.rest.network.RestNetworkPeeringGetAction;
import org.duniter.elasticsearch.rest.network.RestNetworkPeeringPostAction;
import org.duniter.elasticsearch.rest.node.RestNodeSummaryGetAction;
import org.duniter.elasticsearch.rest.security.RestSecurityAuthAction;
import org.duniter.elasticsearch.rest.security.RestSecurityController;
......@@ -51,5 +52,10 @@ public class RestModule extends AbstractModule implements Module {
// Currency
//bind(RestCurrencyIndexAction.class).asEagerSingleton();
// Network
bind(RestNetworkPeeringGetAction.class).asEagerSingleton();
bind(RestNetworkPeeringPostAction.class).asEagerSingleton();
}
}
\ No newline at end of file
package org.duniter.elasticsearch.rest.network;
/*
* #%L
* duniter4j-elasticsearch-plugin
* %%
* Copyright (C) 2014 - 2016 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.databind.ObjectMapper;
import org.apache.http.entity.ContentType;
import org.duniter.core.client.model.bma.NetworkPeering;
import org.duniter.core.client.model.bma.jackson.JacksonUtils;
import org.duniter.core.exception.TechnicalException;
import org.duniter.core.util.StringUtils;
import org.duniter.elasticsearch.PluginSettings;
import org.duniter.elasticsearch.rest.security.RestSecurityController;
import org.duniter.elasticsearch.service.NetworkService;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.*;
import org.nuiton.i18n.I18n;
import java.io.IOException;
/**
* A rest to post a request to process a new currency/peer.
*
*/
public class RestNetworkPeeringGetAction extends BaseRestHandler {
private NetworkService networkService;
@Inject
public RestNetworkPeeringGetAction(Settings settings, PluginSettings pluginSettings, RestController controller, Client client, RestSecurityController securityController,
NetworkService networkService) {
super(settings, controller, client);
if (StringUtils.isBlank(pluginSettings.getClusterRemoteHost())) {
logger.warn(I18n.t("duniter.p2p.error.noRemoteUrl"));
}
else {
securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/network/peering");
controller.registerHandler(RestRequest.Method.GET, "/network/peering", this);
controller.registerHandler(RestRequest.Method.GET, "/{currency}/network/peering", this);
}
this.networkService = networkService;
}
@Override
protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
String currency = request.param("currency");
NetworkPeering peering = networkService.getLastPeering(currency);
try {
channel.sendResponse(new BytesRestResponse(RestStatus.OK,
ContentType.APPLICATION_JSON.toString(),
getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(peering)));
}
catch(IOException ioe) {
throw new TechnicalException(String.format("Error while generating JSON for [/network/peering]: %s", ioe.getMessage()), ioe);
}
}
protected ObjectMapper getObjectMapper() {
return JacksonUtils.getThreadObjectMapper();
}
}
\ No newline at end of file
package org.duniter.elasticsearch.rest.network;
/*
* #%L
* duniter4j-elasticsearch-plugin
* %%
* Copyright (C) 2014 - 2016 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.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.http.entity.ContentType;
import org.duniter.core.client.model.bma.NetworkPeering;
import org.duniter.core.client.model.bma.NetworkPeerings;
import org.duniter.core.client.model.bma.jackson.JacksonUtils;
import org.duniter.core.client.model.local.Peer;
import org.duniter.core.exception.TechnicalException;
import org.duniter.core.service.CryptoService;
import org.duniter.core.util.CollectionUtils;
import org.duniter.core.util.StringUtils;
import org.duniter.elasticsearch.PluginSettings;
import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
import org.duniter.elasticsearch.rest.security.RestSecurityController;
import org.duniter.elasticsearch.service.NetworkService;
import org.duniter.elasticsearch.service.PeerService;
import org.duniter.elasticsearch.service.ServiceLocator;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.*;
import org.nuiton.i18n.I18n;
import org.yaml.snakeyaml.util.UriEncoder;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
/**
* A rest to post a request to process a new currency/peer.
*
*/
public class RestNetworkPeeringPostAction extends BaseRestHandler {
private NetworkService networkService;
@Inject
public RestNetworkPeeringPostAction(Settings settings, PluginSettings pluginSettings, RestController controller, Client client,
RestSecurityController securityController,
NetworkService networkService) {
super(settings, controller, client);
if (StringUtils.isBlank(pluginSettings.getClusterRemoteHost())) {
logger.warn(I18n.t("duniter.p2p.error.noRemoteUrl"));
}
else {
securityController.allow(RestRequest.Method.POST, "/network/peering");
controller.registerHandler(RestRequest.Method.POST, "/network/peering", this);
}
this.networkService = networkService;
}
@Override
protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
try {
Properties content = new Properties();
content.load(new StringReader(request.content().toUtf8()));
String peerDocument = content.getProperty("peer");
if (StringUtils.isBlank(peerDocument)) {
throw new TechnicalException("Inavlid request: 'peer' property not found");
}
// Decode content
peerDocument = UriEncoder.decode(peerDocument);
logger.debug("Received peer document: " + peerDocument);
NetworkPeering peering = networkService.checkAndSavePeering(peerDocument);
channel.sendResponse(new BytesRestResponse(
RestStatus.OK,
ContentType.APPLICATION_JSON.toString(),
getObjectMapper()
.writerWithDefaultPrettyPrinter() // enable pretty printer
.writeValueAsBytes(peering)));
}
catch(Exception e) {
logger.debug("Error while parsing peer document: " + e.getMessage());
channel.sendResponse(new XContentThrowableRestResponse(request, e));
}
}
protected ObjectMapper getObjectMapper() {
return JacksonUtils.getThreadObjectMapper();
}
}
\ No newline at end of file
......@@ -104,7 +104,7 @@ public abstract class AbstractService implements Bean {
protected void waitReady() {
try {
while (!ready) {
Thread.sleep(500);
Thread.sleep(1000 /*1sec*/);
}
} catch (InterruptedException e){
// Silent
......
......@@ -48,10 +48,15 @@ import org.duniter.elasticsearch.client.Duniter4jClient;
import org.duniter.elasticsearch.dao.BlockDao;
import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
import org.duniter.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfoAction;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.rest.action.admin.cluster.node.info.RestNodesInfoAction;
import org.nuiton.i18n.I18n;
import java.io.IOException;
......
......@@ -53,7 +53,7 @@ public class PeerService extends AbstractService {
private ThreadPool threadPool;
// Define endpoint API to include
private List<String> includeEndpointApis = Lists.newArrayList(
private static List<String> includeEndpointApis = Lists.newArrayList(
EndpointApi.BASIC_MERKLED_API.name(),
EndpointApi.BMAS.name(),
EndpointApi.WS2P.name());
......@@ -133,6 +133,14 @@ public class PeerService extends AbstractService {
return this;
}
public void save(final Peer peer) {
delegate.save(peer);
}
public void save(final String currencyId, final List<Peer> peers, boolean isFullList) {
delegate.save(currencyId, peers, isFullList);
}
public void listenAndIndexPeers(final Peer mainPeer) {
// Get the blockchain name from node
BlockchainParameters parameter = blockchainRemoteService.getParameters(mainPeer);
......
......@@ -32,6 +32,7 @@ import org.duniter.core.client.service.HttpServiceImpl;
import org.duniter.core.client.service.bma.*;
import org.duniter.core.client.service.local.CurrencyService;
import org.duniter.core.client.service.local.*;
import org.duniter.core.client.service.local.NetworkService;
import org.duniter.core.exception.TechnicalException;
import org.duniter.core.service.CryptoService;
import org.duniter.core.service.Ed25519CryptoServiceImpl;
......@@ -43,7 +44,6 @@ import org.duniter.elasticsearch.dao.impl.BlockDaoImpl;
import org.duniter.elasticsearch.dao.impl.CurrencyDaoImpl;
import org.duniter.elasticsearch.dao.impl.PeerDaoImpl;
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;
......
cesium-plus-pod-core.config=
duniter.p2p.error.noRemoteUrl=The cluster address can not be published on the network. /\!\\ Fill in the options [cluster.remote.xxx] in the configuration (recommended).
duniter4j.blockIndexerService.detectFork.invalidBlock=[%s] [%s] Detecting fork\: block \#%s -> new hash [%s]
duniter4j.blockIndexerService.detectFork.invalidBlockchain=[%s] [%s] Peer has another blockchain (no common blocks \!). Skipping block \#%s - hash [%s].
duniter4j.blockIndexerService.detectFork.remoteBlockNotFound=[%s] [%s] Unable to get block \#%s from peer\: %s
......
cesium-plus-pod-core.config=
duniter.p2p.error.noRemoteUrl=L'adresse publique du cluster ne peut pas être publiée sur le réseau. /\!\\ Renseignez les options [cluster.remote.xxx] dans la configuration (conseillé).
duniter4j.blockIndexerService.detectFork.invalidBlock=[%s] [%s] Detecting fork\: block \#%s -> new hash [%s]
duniter4j.blockIndexerService.detectFork.invalidBlockchain=[%s] [%s] Peer has another blockchain (no common blocks \!). Skipping block \#%s - hash [%s].
duniter4j.blockIndexerService.detectFork.remoteBlockNotFound=[%s] [%s] Unable to get block \#%s from peer\: %s
......