From 3d618c63703df25622346f3ee3548440a75a6ae9 Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Mon, 27 Mar 2017 18:21:18 +0200
Subject: [PATCH] - Update command line client - Client: could now broadcast TX
 sent

---
 .../src/main/java/fr/duniter/cmd/Main.java    |  19 +-
 .../fr/duniter/cmd/actions/NetworkAction.java | 159 +++++++++----
 .../duniter/cmd/actions/SentMoneyAction.java  |  76 -------
 .../cmd/actions/TransactionAction.java        | 213 ++++++++++++++++++
 .../cmd/actions/params/AuthParameters.java    |  67 ++++++
 .../cmd/actions/params/PeerParameters.java    |  67 ++++++
 .../cmd/actions/params/WalletParameters.java  |  14 --
 .../src/main/resources/log4j.properties       |  32 +--
 .../duniter/core/client/model/local/Peer.java |   6 +
 .../service/bma/TransactionRemoteService.java |  22 ++
 .../bma/TransactionRemoteServiceImpl.java     |  44 +++-
 .../src/main/resources/log4j.properties       |   2 +-
 .../duniter/core/service/CryptoService.java   |   9 +
 .../service/Ed25519CryptoServiceImpl.java     |  17 +-
 .../org/duniter/core/util/StringUtils.java    |   7 +
 .../main/filtered-resources/log4j.properties  |   1 +
 .../org/duniter/elasticsearch/PluginInit.java |   2 +-
 .../service/BlockchainService.java            |  13 --
 .../service/EndpointService.java              |  81 ++-----
 19 files changed, 607 insertions(+), 244 deletions(-)
 delete mode 100644 duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java
 create mode 100644 duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/TransactionAction.java
 create mode 100644 duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/AuthParameters.java
 create mode 100644 duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/PeerParameters.java
 delete mode 100644 duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java

diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java
index 7208a3dc..8dca2049 100644
--- a/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java
@@ -2,10 +2,11 @@ package fr.duniter.cmd;
 
 import com.beust.jcommander.JCommander;
 import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterDescription;
 import com.beust.jcommander.ParameterException;
 import com.google.common.collect.Lists;
 import fr.duniter.cmd.actions.NetworkAction;
-import fr.duniter.cmd.actions.SentMoneyAction;
+import fr.duniter.cmd.actions.TransactionAction;
 import org.apache.commons.io.FileUtils;
 import org.duniter.core.client.config.Configuration;
 import org.duniter.core.client.service.ServiceLocator;
@@ -13,8 +14,6 @@ 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;
@@ -34,6 +33,7 @@ public class Main {
     @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);
@@ -43,13 +43,24 @@ public class Main {
 
         Map<String, Runnable> actions = new HashMap<>();
         actions.put("network", new NetworkAction());
-        actions.put("send", new SentMoneyAction());
+        actions.put("transaction", new TransactionAction());
 
         // Parsing args
         JCommander jc = new JCommander(this);
         actions.entrySet().stream().forEach(entry -> jc.addCommand(entry.getKey(), entry.getValue()));
         try {
             jc.parse(args);
+
+            jc.getParameters().stream().forEach(param -> {
+              if (param.getParameter().password()
+                      && param.getParameter().required()
+                      && param.getParameter().echoInput()
+                      && !param.isAssigned()) {
+                  System.out.print(param.getParameter().getParameter().description());
+                  //var17.addValue(new String(var11));
+              }
+            });
+            //jc.parse(args);
         }
         catch(ParameterException e) {
             System.err.println(e.getMessage());
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
index ac10b861..2da6b804 100644
--- a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java
@@ -1,15 +1,23 @@
 package fr.duniter.cmd.actions;
 
+import com.beust.jcommander.JCommander;
 import com.beust.jcommander.Parameter;
 import com.beust.jcommander.Parameters;
+import com.beust.jcommander.ParametersDelegate;
+import com.beust.jcommander.internal.Lists;
+import dnl.utils.text.table.SeparatorPolicy;
 import dnl.utils.text.table.TextTable;
+import fr.duniter.cmd.actions.params.PeerParameters;
 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 /**
@@ -17,64 +25,127 @@ import java.util.stream.Collectors;
  */
 @Parameters(commandDescription = "Display network peers")
 public class NetworkAction implements Runnable {
+    private static Logger log = LoggerFactory.getLogger(NetworkAction.class);
 
-    @Parameter(names = "-host", description = "Duniter host")
-    private String host = "g1.duniter.org";
+    @ParametersDelegate
+    public PeerParameters peerParameters = new PeerParameters();
 
-    @Parameter(names = "-port", description = "Duniter port")
-    private int port = 10901;
+    @Parameter(names = "--continue", description = "Continue scanning ?")
+    private boolean autoRefresh = false;
+
+    private int previousRowDisplayed = 0;
 
     @Override
     public void run() {
-        NetworkService service = ServiceLocator.instance().getNetworkService();
-        Peer mainPeer = Peer.newBuilder().setHost(host).setPort(port).build();
 
+        peerParameters.parse();
+        Peer mainPeer = peerParameters.getPeer();
+
+        log.info("Loading peers...");
+        NetworkService service = ServiceLocator.instance().getNetworkService();
         List<Peer> peers = service.getPeers(mainPeer);
+        showPeersTable(peers, autoRefresh);
+
+        if (autoRefresh) {
+            final List<String> knownBlocks = Lists.newArrayList();
+            peers.stream().forEach(peer -> {
+                String buid = peer.getStats().getBlockNumber() + "-" + peer.getStats().getBlockHash();
+                if (!knownBlocks.contains(buid)) {
+                    knownBlocks.add(buid);
+                }
+            });
+
+            // Start listening for new block...
+            CompletableFuture.runAsync(() ->
+                ServiceLocator.instance().getBlockchainRemoteService().addPeerListener(mainPeer, message -> {
+                    List<Peer> updatedPeers = service.getPeers(mainPeer);
+
+                    int knowBlockSize = knownBlocks.size();
+                    updatedPeers.stream().forEach(peer -> {
+                        String buid = peer.getStats().getBlockNumber() + "-" + peer.getStats().getBlockHash();
+                        if (!knownBlocks.contains(buid)) {
+                            knownBlocks.add(buid);
+                        }
+                    });
+
+                    // new block received: refresh console
+                    if (knowBlockSize < knownBlocks.size()) {
+                        showPeersTable(updatedPeers, true);
+                    }
+
+                }));
+
+            try {
+                while(true) {
+                    Thread.sleep(10000); // 10 s
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+
+    }
+
+    /* -- protected methods -- */
+
+    public void showPeersTable(List<Peer> peers, boolean clearConsole) {
+
+        // Clearing console
+        if (clearConsole) {
+            clearConsole();
+        }
 
         if (CollectionUtils.isEmpty(peers)) {
-            System.out.println("No peers found");
+            JCommander.getConsole().println("No peers found");
+            return;
+        }
+        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() != null) ? 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;
         }
-        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();
-        }
+        TextTable tt = new TextTable(columnNames, rows);
+        // this adds the numbering on the left
+        tt.setAddRowNumbering(true);
+        tt.printTable();
 
+        previousRowDisplayed = 3/*header rows*/ + rows.length;
     }
 
+    protected void moveCursor(int nbLinesUp) {
+        System.out.print(String.format("\033[%dA",nbLinesUp)); // Move up
+        System.out.print("\033[2K"); // Erase line content
+    }
 
+    protected void clearConsole() {
+        System.out.print(String.format("\033[2J"));
+    }
 }
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
deleted file mode 100644
index 9b64dcf8..00000000
--- a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java
+++ /dev/null
@@ -1,76 +0,0 @@
-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/TransactionAction.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/TransactionAction.java
new file mode 100644
index 00000000..c738c0df
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/TransactionAction.java
@@ -0,0 +1,213 @@
+package fr.duniter.cmd.actions;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParametersDelegate;
+import com.beust.jcommander.validators.PositiveInteger;
+import com.google.common.collect.ImmutableList;
+import fr.duniter.cmd.actions.params.AuthParameters;
+import fr.duniter.cmd.actions.params.PeerParameters;
+import fr.duniter.cmd.actions.utils.Formatters;
+import org.duniter.core.client.model.bma.EndpointApi;
+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.TransactionRemoteService;
+import org.duniter.core.client.service.local.NetworkService;
+import org.duniter.core.client.service.local.PeerService;
+import org.duniter.core.exception.BusinessException;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.service.CryptoService;
+import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.crypto.KeyPair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class TransactionAction implements Runnable {
+
+    private static Logger log = LoggerFactory.getLogger(TransactionAction.class);
+
+    @ParametersDelegate
+    public AuthParameters authParameters = new AuthParameters();
+
+    @ParametersDelegate
+    public PeerParameters peerParameters = new PeerParameters();
+
+    @Parameter(names = "--amount", description = "Amount", required = true, validateWith = PositiveInteger.class)
+    public int amount;
+
+    @Parameter(names = "--output", description = "Output pubkey", required = true)
+    public String output;
+
+    @Parameter(names = "--comment", description = "TX Comment")
+    public String comment;
+
+    private int mainConsensusPeerCount = 0;
+    private int forkConsensusPeerCount = 0;
+
+    @Override
+    public void run() {
+        try {
+
+            // Make sure auth parameters are filled
+            authParameters.parse();
+            peerParameters.parse();
+
+            // Reducing node timeout when broadcast
+
+
+            Peer peer = peerParameters.getPeer();
+
+            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 = null;
+            if (authParameters.authScrypt) {
+                CryptoService cryptoService = ServiceLocator.instance().getCryptoService();
+                keypair = cryptoService.getKeyPairFromSeed(
+                        cryptoService.getSeed(
+                        new String(authParameters.salt),
+                        new String(authParameters.password),
+                                authParameters.scryptPArams.get(0), // N
+                                authParameters.scryptPArams.get(1), // p
+                                authParameters.scryptPArams.get(2) // r
+                        ));
+            }
+            else {
+                fail("Unknwon authentification type");
+                return;
+            }
+
+            Wallet wallet = new Wallet(
+                    currency.getCurrencyName(),
+                    null,
+                    keypair.getPubKey(),
+                    keypair.getSecKey());
+            wallet.setCurrencyId(currency.getId());
+
+            // Send TX document to ONE peer
+            if (!peerParameters.broadcast) {
+                sendToPeer(peer, wallet);
+            }
+
+            // Sent TX using broadcast
+            else {
+                sendBroadcast(peer, currency, wallet, peerParameters.useSsl);
+            }
+        }
+        catch(BusinessException | TechnicalException e) {
+            fail(e);
+        }
+    }
+
+    protected void fail(String message) {
+        log.error(message);
+        System.exit(-1);
+    }
+
+    protected void sendToPeer(Peer peer, Wallet wallet) {
+        TransactionRemoteService txService = ServiceLocator.instance().getTransactionRemoteService();
+
+        logTxSummary(wallet);
+
+        txService.transfer(peer, wallet, output, amount, comment);
+        JCommander.getConsole().println("Transaction successfully sent.");
+    }
+
+    protected void sendBroadcast(Peer mainPeer, Currency currency, Wallet wallet, boolean useSsl) {
+
+        TransactionRemoteService txService = ServiceLocator.instance().getTransactionRemoteService();
+        PeerService peerService = ServiceLocator.instance().getPeerService();
+
+        log.info("Loading member peers...");
+
+        // Filter to [member + UP] peers
+        NetworkService.Filter peersFilter = new NetworkService.Filter();
+        peersFilter.filterType = NetworkService.FilterType.MEMBER;
+        peersFilter.filterStatus = Peer.PeerStatus.UP;
+        if (useSsl) {
+            peersFilter.filterEndpoints = ImmutableList.of(EndpointApi.BMAS.name());
+        }
+        else {
+            peersFilter.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name());
+        }
+        // Sort by [lesser difficulty first]
+        NetworkService.Sort sortLesserDifficulty = new NetworkService.Sort();
+        sortLesserDifficulty.sortType = NetworkService.SortType.HARDSHIP;
+        sortLesserDifficulty.sortAsc = true;
+
+        // Get the peers list
+        List<Peer> peers = ServiceLocator.instance().getNetworkService().getPeers(mainPeer, peersFilter, sortLesserDifficulty);
+
+        if (CollectionUtils.isEmpty(peers)) {
+            log.warn("No members peers found! Skipping --broadcast option.");
+            sendToPeer(mainPeer, wallet);
+            return;
+        }
+
+        log.info(String.format("%d member peers found for broadcast", peers.size()));
+
+        logTxSummary(wallet);
+
+        peers.stream().forEach(peer -> {
+            peer.setCurrencyId(currency.getId());
+            peer.setCurrency(currency.getCurrencyName());
+            peerService.save(peer);
+
+            log.debug(String.format("Send TX to [%s]...", peer));
+            try {
+                txService.transfer(peer, wallet, output, amount, comment);
+
+                log.warn(String.format("Successfully sent to [%s]", peer));
+
+                if (peer.getStats() != null) {
+                    if (peer.getStats().isMainConsensus()) {
+                        mainConsensusPeerCount++;
+                    } else if (peer.getStats().isForkConsensus()) {
+                        forkConsensusPeerCount++;
+                    }
+                }
+
+            }
+            catch (Exception e) {
+                log.warn(String.format("Could not send transaction to [%s]: %s", peer, e.getMessage()));
+            }
+        });
+
+        if (mainConsensusPeerCount > 0) {
+            JCommander.getConsole().println(String.format("Transaction successfully sent (to %d nodes on the main blockchain consensus).", mainConsensusPeerCount));
+        }
+        else if (forkConsensusPeerCount > 0){
+            fail(String.format("Transaction has NOT been sent to the main consensus BlockChain, but ONLY to %d peers on a fork of the blockchain.", forkConsensusPeerCount));
+        }
+        else {
+            fail(String.format("Transaction has NOT been sent. Not a single peer has accepted the transaction."));
+        }
+
+    }
+
+
+    protected void logTxSummary(Wallet wallet) {
+        // Log TX summary
+        JCommander.getConsole().println(String.format("Generate Transation:\n\t- From:   %s\n\t- To:     %s\n\t- Amount: %s %s",
+                Formatters.formatPubkey(wallet.getPubKeyHash()),
+                Formatters.formatPubkey(output),
+                amount,
+                Formatters.currencySymbol(wallet.getCurrency())));
+    }
+
+    protected void fail(Exception e) {
+        fail(e.getMessage());
+    }
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/AuthParameters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/AuthParameters.java
new file mode 100644
index 00000000..17fd1f99
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/AuthParameters.java
@@ -0,0 +1,67 @@
+package fr.duniter.cmd.actions.params;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.CommaParameterSplitter;
+import com.beust.jcommander.validators.PositiveInteger;
+import com.google.common.collect.ImmutableList;
+import org.duniter.core.service.Ed25519CryptoServiceImpl;
+import org.duniter.core.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class AuthParameters {
+
+    @Parameter(names = "--auth-scrypt", description = "Authenticate using Scrypt ?")
+    public boolean authScrypt = true;
+
+    @Parameter(names = "--salt", description = "Salt (to generate the keypair)", password = true)
+    public char[] salt;
+
+    @Parameter(names = "--passwd", description = "Password (to generate the keypair)", password = true)
+    public char[] password;
+
+    @Parameter(names = "--scrypt-params", description = "Scrypt parameters (N,r,p)",
+            splitter = CommaParameterSplitter.class,
+            validateWith = PositiveInteger.class)
+    public List<Integer> scryptPArams;
+
+    public void parse() {
+        // Compute keypair and wallet
+        if (StringUtils.isBlank(salt) && authScrypt) {
+            JCommander.getConsole().print("Please enter your Scrypt Salt (Secret identifier): ");
+            salt = JCommander.getConsole().readPassword(true);
+        }
+        if (StringUtils.isBlank(password) && authScrypt){
+            JCommander.getConsole().print("Please enter your Scrypt password (masked): ");
+            password = JCommander.getConsole().readPassword(true);
+        }
+        if (scryptPArams == null && authScrypt) {
+            JCommander.getConsole().print(String.format("Please enter your Scrypt parameters (N,r,p): [%d,%d,%d] ",
+                    Ed25519CryptoServiceImpl.SCRYPT_PARAMS_N,
+                    Ed25519CryptoServiceImpl.SCRYPT_PARAMS_r,
+                    Ed25519CryptoServiceImpl.SCRYPT_PARAMS_p));
+            char[] scryptsParamsStr = JCommander.getConsole().readPassword(false);
+            if (StringUtils.isNotBlank(scryptsParamsStr)) {
+                String[] parts = new String(scryptsParamsStr).split(",");
+                if (parts.length != 3) {
+                    throw new ParameterException("Invalid Scrypt parameters (expected 3 values)");
+                }
+                scryptPArams = Arrays.asList(parts).stream().map(part -> Integer.parseInt(part)).collect(Collectors.toList());
+            }
+            else {
+                scryptPArams = ImmutableList.of(
+                        Ed25519CryptoServiceImpl.SCRYPT_PARAMS_N,
+                        Ed25519CryptoServiceImpl.SCRYPT_PARAMS_r,
+                        Ed25519CryptoServiceImpl.SCRYPT_PARAMS_p);
+            }
+        }
+    }
+
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/PeerParameters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/PeerParameters.java
new file mode 100644
index 00000000..a08a8a67
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/PeerParameters.java
@@ -0,0 +1,67 @@
+package fr.duniter.cmd.actions.params;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class PeerParameters {
+
+    private static Logger log = LoggerFactory.getLogger(PeerParameters.class);
+
+    @Parameter(names = {"-p", "--peer"}, description = "Peer address (use format: 'host:port')")
+    public String peerStr;
+
+    @Parameter(names = "--broadcast", description = "Broadcast document sent to all nodes")
+    public boolean broadcast = false;
+
+    @Parameter(names = "--ssl", description = "Using SSL connection to node")
+    public boolean useSsl = false;
+
+    @Parameter(names = "--timeout", description = "HTTP request timeout, in millisecond")
+    public Long timeout = null;
+
+    private Peer peer = null;
+
+    public void parse() {
+        if (StringUtils.isNotBlank(peerStr)) {
+            String[] parts = peerStr.split(":");
+            if (parts.length > 2) {
+                throw new ParameterException("Invalid --peer parameter");
+            }
+            String host = parts[0];
+            Integer port = parts.length == 2 ? Integer.parseInt(parts[1]) : null;
+
+            Peer.Builder peerBuilder = Peer.newBuilder().setHost(host);
+            if (port != null) {
+                peerBuilder.setPort(port);
+            }
+            if (useSsl){
+                peerBuilder.setUseSsl(useSsl);
+            }
+            peer = peerBuilder.build();
+
+            log.info(String.format("Duniter node: [%s:%s]", peer.getHost(), peer.getPort()));
+        }
+        else {
+            Configuration config = Configuration.instance();
+            peer = Peer.newBuilder().setHost(config.getNodeHost())
+                    .setPort(config.getNodePort())
+                    .build();
+            log.info(String.format("Fallback to default Duniter node: [%s:%d]", peer.getHost(), peer.getPort()));
+        }
+    }
+
+    public Peer getPeer() {
+        Preconditions.checkNotNull(peer, "Please call parse() before getPeer().");
+        return peer;
+    }
+}
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
deleted file mode 100644
index 2694cafc..00000000
--- a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java
+++ /dev/null
@@ -1,14 +0,0 @@
-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/resources/log4j.properties b/duniter4j-cmd/src/main/resources/log4j.properties
index f31940a1..d8de0e03 100644
--- a/duniter4j-cmd/src/main/resources/log4j.properties
+++ b/duniter4j-cmd/src/main/resources/log4j.properties
@@ -1,27 +1,33 @@
 ###
 # Global logging configuration
-log4j.rootLogger=ERROR, stdout, file
+log4j.rootLogger=INFO, stdout, file
 
-# Console output
+# Console appender
 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.stdout.layout.ConversionPattern=%p - %m%n
 
+# File appender
 log4j.appender.file=org.apache.log4j.RollingFileAppender
-log4j.appender.file.file=ucoin-client.log
+log4j.appender.file.file=duniter4j-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
 
+# 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
 
+# Other frameworks levels
+log4j.logger.org.apache.http=ERROR
+log4j.logger.org.nuiton.util=WARN
+log4j.logger.org.nuiton.config=WARN
+log4j.logger.org.nuiton.converter=WARN
+log4j.logger.org.nuiton.i18n=ERROR
\ No newline at end of file
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 02eb05cc..d31a8496 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
@@ -24,6 +24,7 @@ package org.duniter.core.client.model.local;
 
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.base.Joiner;
 import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
 import org.duniter.core.util.Preconditions;
@@ -343,6 +344,11 @@ public class Peer implements LocalEntity, Serializable {
         this.stats = stats;
     }
 
+    @JsonIgnore
+    public String computeKey()  {
+        return Joiner.on('-').skipNulls().join(pubkey, dns, ipv4, ipv6, port, useSsl);
+    }
+
     public String toString() {
         StringJoiner joiner = new StringJoiner(" ");
         if (api != null) {
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
index 76242225..882f4c54 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
@@ -33,6 +33,28 @@ import org.duniter.core.client.service.exception.InsufficientCreditException;
 
 public interface TransactionRemoteService extends Service {
 
+    /**
+     * Transfer TX to the given peer, or if null to currency's default peer
+     * @param peer
+     * @param wallet
+     * @param destPubKey
+     * @param amount
+     * @param comment
+     * @return
+     * @throws InsufficientCreditException
+     */
+    String transfer(Peer peer, Wallet wallet, String destPubKey, long amount,
+                    String comment) throws InsufficientCreditException;
+
+    /**
+     * Same, using the default currency's peer
+     * @param wallet
+     * @param destPubKey
+     * @param amount
+     * @param comment
+     * @return
+     * @throws InsufficientCreditException
+     */
 	String transfer(Wallet wallet, String destPubKey, long amount,
                     String comment) throws InsufficientCreditException;
 
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 a6ad175c..4c8e869f 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
@@ -77,24 +77,38 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
         cryptoService = ServiceLocator.instance().getCryptoService();
 	}
 
+
 	public String transfer(Wallet wallet, String destPubKey, long amount,
 						   String comment) throws InsufficientCreditException {
+		Preconditions.checkNotNull(wallet);
+		Preconditions.checkNotNull(wallet.getCurrencyId());
+
+		return transfer(null, wallet, destPubKey, amount, comment);
+	}
+
+	public String transfer(Peer peer, Wallet wallet, String destPubKey, long amount,
+						   String comment) throws InsufficientCreditException {
+		Preconditions.checkNotNull(wallet);
+		Preconditions.checkArgument(peer != null || wallet.getCurrencyId() != null);
 
 		// Get current block
-		BlockchainBlock currentBlock = executeRequest(wallet.getCurrencyId(), BlockchainRemoteServiceImpl.URL_BLOCK_CURRENT, BlockchainBlock.class);
+		BlockchainBlock currentBlock = peer != null ?
+				executeRequest(peer, BlockchainRemoteServiceImpl.URL_BLOCK_CURRENT, BlockchainBlock.class) :
+				executeRequest(wallet.getCurrencyId(), BlockchainRemoteServiceImpl.URL_BLOCK_CURRENT, BlockchainBlock.class);
 
 		// http post /tx/process
-		HttpPost httpPost = new HttpPost(
-				getPath(wallet.getCurrencyId(), URL_TX_PROCESS));
+		HttpPost httpPost = peer != null ?
+				new HttpPost(getPath(peer, URL_TX_PROCESS)) :
+				new HttpPost(getPath(wallet.getCurrencyId(), URL_TX_PROCESS));
 
 		// compute transaction
-		String transaction = getSignedTransaction(wallet, currentBlock, destPubKey, 0, amount,
-                comment);
+		String transaction = getSignedTransaction(peer, wallet, currentBlock, destPubKey, 0, amount,
+				comment);
 
 		if (log.isDebugEnabled()) {
 			log.debug(String.format(
-                "Will send transaction document: \n------\n%s------",
-                transaction));
+					"Will send transaction document: \n------\n%s------",
+					transaction));
 		}
 
 		List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
@@ -107,16 +121,19 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
 		}
 
 		String selfResult = executeRequest(httpPost, String.class);
-		log.info("received from /tx/process: " + selfResult);
+		if (log.isDebugEnabled()) {
+			log.debug("Received from /tx/process: " + selfResult);
+		}
 
 
-        String fingerprint = DigestUtils.sha1Hex(transaction);
+		String fingerprint = DigestUtils.sha1Hex(transaction);
 		if (log.isDebugEnabled()) {
 			log.debug(String.format(
 					"Fingerprint: %s",
 					fingerprint));
 		}
-        return fingerprint;
+		return fingerprint;
+
 	}
 
 	public TxSource getSources(long currencyId, String pubKey) {
@@ -217,7 +234,8 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
 
 	/* -- internal methods -- */
 
-	public String getSignedTransaction(Wallet wallet,
+	protected String getSignedTransaction(Peer peer,
+									   Wallet wallet,
 									   BlockchainBlock block,
 									   String destPubKey,
 									   int locktime,
@@ -228,7 +246,9 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
         Preconditions.checkArgument(StringUtils.isNotBlank(wallet.getPubKeyHash()));
 
 		// Retrieve the wallet sources
-		TxSource sourceResults = getSources(wallet.getCurrencyId(), wallet.getPubKeyHash());
+		TxSource sourceResults = peer != null ?
+				getSources(peer, wallet.getPubKeyHash()) :
+				getSources(wallet.getCurrencyId(), wallet.getPubKeyHash());
 		if (sourceResults == null) {
 			throw new TechnicalException("Unable to load user sources.");
 		}
diff --git a/duniter4j-core-client/src/main/resources/log4j.properties b/duniter4j-core-client/src/main/resources/log4j.properties
index d5aafd24..554a25b8 100644
--- a/duniter4j-core-client/src/main/resources/log4j.properties
+++ b/duniter4j-core-client/src/main/resources/log4j.properties
@@ -7,7 +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
+# Duniter4j levels
 log4j.logger.org.duniter=DEBUG
 log4j.logger.org.duniter.core.client.service.bma.AbstractNetworkService=WARN
 
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/service/CryptoService.java b/duniter4j-core-shared/src/main/java/org/duniter/core/service/CryptoService.java
index 0fcf80a0..4702a46c 100644
--- a/duniter4j-core-shared/src/main/java/org/duniter/core/service/CryptoService.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/service/CryptoService.java
@@ -32,8 +32,17 @@ import org.duniter.core.util.crypto.KeyPair;
  * Created by eis on 10/01/15.
  */
 public interface CryptoService extends Bean {
+
+    /**
+     * generate a crypto seed, using default N,r,p parameters (4096,16,1)
+     * @param salt
+     * @param password
+     * @return
+     */
     byte[] getSeed(String salt, String password);
 
+    byte[] getSeed(String salt, String password, int N, int r, int p);
+
     /**
      * Returns a new signing key pair generated from salt and password.
      * The salt and password must contain enough entropy to be secure.
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/service/Ed25519CryptoServiceImpl.java b/duniter4j-core-shared/src/main/java/org/duniter/core/service/Ed25519CryptoServiceImpl.java
index f822e004..6448f79a 100644
--- a/duniter4j-core-shared/src/main/java/org/duniter/core/service/Ed25519CryptoServiceImpl.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/service/Ed25519CryptoServiceImpl.java
@@ -50,10 +50,10 @@ public class Ed25519CryptoServiceImpl implements CryptoService {
     // Length of a secret key
     private static int SECRETKEY_BYTES = 64;
 
-    // Scrypt parameter
-    private static int SCRYPT_PARAMS_N = 4096;
-    private static int SCRYPT_PARAMS_r = 16;
-    private static int SCRYPT_PARAMS_p = 1;
+    // Scrypt default parameters
+    public static int SCRYPT_PARAMS_N = 4096;
+    public static int SCRYPT_PARAMS_r = 16;
+    public static int SCRYPT_PARAMS_p = 1;
 
     protected final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
 
@@ -68,12 +68,16 @@ public class Ed25519CryptoServiceImpl implements CryptoService {
 
     @Override
     public byte[] getSeed(String salt, String password) {
+        return getSeed(salt, password, SCRYPT_PARAMS_N, SCRYPT_PARAMS_r, SCRYPT_PARAMS_p);
+    }
+
+    @Override
+    public byte[] getSeed(String salt, String password, int N, int r, int p) {
         try {
             byte[] seed = SCrypt.scrypt(
                     CryptoUtils.decodeAscii(password),
                     CryptoUtils.decodeAscii(salt),
-                    SCRYPT_PARAMS_N, SCRYPT_PARAMS_r,
-                    SCRYPT_PARAMS_p, SEED_BYTES);
+                    N, r, p, SEED_BYTES);
             return seed;
         } catch (GeneralSecurityException e) {
             throw new TechnicalException(
@@ -86,7 +90,6 @@ public class Ed25519CryptoServiceImpl implements CryptoService {
         return getKeyPairFromSeed(getSeed(salt, password));
     }
 
-
     @Override
     public KeyPair getKeyPairFromSeed(byte[] seed) {
         byte[] secretKey = CryptoUtils.zeros(SECRETKEY_BYTES);
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/StringUtils.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/StringUtils.java
index 944a5633..f1ba86d0 100644
--- a/duniter4j-core-shared/src/main/java/org/duniter/core/util/StringUtils.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/StringUtils.java
@@ -33,11 +33,18 @@ public class StringUtils {
     public static boolean isNotBlank(String value) {
         return value != null && value.trim().length() > 0;
     }
+    public static boolean isNotBlank(char[] value) {
+        return value != null && value.length > 0;
+    }
 
     public static boolean isBlank(String value) {
         return value == null || value.trim().length() == 0;
     }
 
+    public static boolean isBlank(char[] value) {
+        return value == null || value.length == 0;
+    }
+
     public static boolean isNotEmpty(String value) {
         return value != null && value.length() > 0;
     }
diff --git a/duniter4j-es-core/src/main/filtered-resources/log4j.properties b/duniter4j-es-core/src/main/filtered-resources/log4j.properties
index 7b6667b1..a730face 100644
--- a/duniter4j-es-core/src/main/filtered-resources/log4j.properties
+++ b/duniter4j-es-core/src/main/filtered-resources/log4j.properties
@@ -15,6 +15,7 @@ log4j.logger.org.duniter=INFO
 log4j.logger.org.duniter.elasticsearch=DEBUG
 
 # Other frameworks levels
+log4j.logger.org.apache.http=ERROR
 log4j.logger.org.nuiton.util=WARN
 log4j.logger.org.nuiton.config=WARN
 log4j.logger.org.nuiton.converter=WARN
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 5d6cde48..0468f586 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
@@ -139,7 +139,7 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
             // Index peers (and listen if new peer appear)
             injector.getInstance(EndpointService.class)
-                    .indexLastPeers(peer)/*
+                    .indexAllEndpoints(peer)/*
                     .listenAndIndexNewPeer(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 968b77bf..b4b67c3c 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
@@ -298,19 +298,6 @@ public class BlockchainService extends AbstractService {
         createIndexRequestBuilder.execute().actionGet();
     }
 
-    public void createBlock(BlockchainBlock block) throws org.duniter.elasticsearch.exception.DuplicateIndexIdException {
-        Preconditions.checkNotNull(block, "block could not be null") ;
-        Preconditions.checkNotNull(block.getCurrency(), "block attribute 'blockchain' could not be null");
-        Preconditions.checkNotNull(block.getNumber(), "block attribute 'number' could not be null");
-
-        BlockchainBlock existingBlock = getBlockById(block.getCurrency(), block.getNumber());
-        if (existingBlock != null) {
-            throw new DuplicateIndexIdException(String.format("Block with number [%s] already exists.", block.getNumber()));
-        }
-
-        indexBlock(block, false);
-    }
-
     /**
      * Create or update a block, depending on its existence and hash
      * @param block
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java
index 8df83239..1fb2eb9f 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java
@@ -119,12 +119,12 @@ public class EndpointService extends AbstractService {
         }
     }
 
-    public EndpointService indexLastPeers(Peer peer) {
-        indexLastPeers(peer, nullProgressionModel);
+    public EndpointService indexAllEndpoints(Peer peer) {
+        indexAllEndpoints(peer, nullProgressionModel);
         return this;
     }
 
-    public EndpointService indexLastPeers(Peer peer, ProgressionModel progressionModel) {
+    public EndpointService indexAllEndpoints(Peer peer, ProgressionModel progressionModel) {
 
         try {
             // Get the blockchain name from node
@@ -139,7 +139,7 @@ public class EndpointService extends AbstractService {
             indexPeers(currencyName, peer, progressionModel);
 
         } catch(Exception e) {
-            logger.error("Error during indexLastPeers: " + e.getMessage(), e);
+            logger.error("Error during indexAllEndpoints: " + e.getMessage(), e);
             progressionModel.setStatus(ProgressionModel.Status.FAILED);
         }
 
@@ -243,32 +243,33 @@ public class EndpointService extends AbstractService {
     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());
+        String id = cryptoService.hash(peer.computeKey());
+
+        boolean exists = isDocumentExists(peer.getCurrency(), ENDPOINT_TYPE, id);
 
         // Currency not exists, or has changed, so create it
-        if (existingPeer == null) {
+        if (!exists) {
             if (logger.isTraceEnabled()) {
                 logger.trace(String.format("Insert new peer [%s]", peer));
             }
 
             // Index new peer
-            indexPeer(peer, wait);
+            indexPeer(peer, id, wait);
         }
 
         // Update existing peer
         else {
             logger.trace(String.format("Update peer [%s]", peer));
-            updatePeer(peer, wait);
+            updatePeer(peer, id, wait);
         }
         return peer;
     }
 
-    public void indexPeer(Peer peer, boolean wait) {
+    public void indexPeer(Peer peer, String id, boolean wait) {
         Preconditions.checkNotNull(peer);
         Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
         Preconditions.checkNotNull(peer.getHash());
@@ -282,7 +283,7 @@ public class EndpointService extends AbstractService {
 
             // Preparing indexBlocksFromNode
             IndexRequestBuilder indexRequest = client.prepareIndex(peer.getCurrency(), ENDPOINT_TYPE)
-                    .setId(peer.getHash())
+                    .setId(id)
                     .setSource(json);
 
             // Execute indexBlocksFromNode
@@ -299,7 +300,7 @@ public class EndpointService extends AbstractService {
         }
     }
 
-    public void updatePeer(Peer peer, boolean wait) {
+    public void updatePeer(Peer peer, String id, boolean wait) {
         Preconditions.checkNotNull(peer);
         Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
         Preconditions.checkNotNull(peer.getHash());
@@ -312,7 +313,7 @@ public class EndpointService extends AbstractService {
             String json = objectMapper.writeValueAsString(peer);
 
             // Preparing indexBlocksFromNode
-            UpdateRequestBuilder updateRequest = client.prepareUpdate(peer.getCurrency(), ENDPOINT_TYPE, peer.getHash())
+            UpdateRequestBuilder updateRequest = client.prepareUpdate(peer.getCurrency(), ENDPOINT_TYPE, id)
                     .setDoc(json);
 
             // Execute indexBlocksFromNode
@@ -329,33 +330,6 @@ public class EndpointService extends AbstractService {
         }
     }
 
-    /**
-     *
-     * @param currencyName
-     * @param number the peer hash
-     * @param json block as JSON
-     */
-    public EndpointService 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, ENDPOINT_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.
      */
@@ -432,7 +406,7 @@ public class EndpointService extends AbstractService {
         SearchResponse searchResponse = searchRequest.execute().actionGet();
 
         // Read query result
-        return toBlocks(searchResponse, true);
+        return toPeers(searchResponse, true);
     }
 
     /* -- Internal methods -- */
@@ -491,7 +465,7 @@ public class EndpointService extends AbstractService {
         }
     }
 
-    public Peer getPeerByHash(String currencyName, String hash) {
+    public Peer getPeerByHash(String currencyName, String id) {
 
         // Prepare request
         SearchRequestBuilder searchRequest = client
@@ -500,26 +474,26 @@ public class EndpointService extends AbstractService {
                 .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
 
         // If more than a word, search on terms match
-        searchRequest.setQuery(QueryBuilders.matchQuery("_id", hash));
+        searchRequest.setQuery(QueryBuilders.matchQuery("_id", id));
 
         // Execute query
         try {
             SearchResponse searchResponse = searchRequest.execute().actionGet();
-            List<Peer> blocks = toBlocks(searchResponse, false);
-            if (CollectionUtils.isEmpty(blocks)) {
+            List<Peer> peers = toPeers(searchResponse, false);
+            if (CollectionUtils.isEmpty(peers)) {
                 return null;
             }
 
             // Return the unique result
-            return CollectionUtils.extractSingleton(blocks);
+            return CollectionUtils.extractSingleton(peers);
         }
         catch(JsonSyntaxException e) {
-            throw new TechnicalException(String.format("Error while getting indexed peer #%s for [%s]", hash, currencyName), e);
+            throw new TechnicalException(String.format("Error while getting indexed peer #%s for [%s]", id, currencyName), e);
         }
 
     }
 
-    protected List<Peer> toBlocks(SearchResponse response, boolean withHighlight) {
+    protected List<Peer> toPeers(SearchResponse response, boolean withHighlight) {
         // Read query result
         List<Peer> result = Lists.newArrayList();
         response.getHits().forEach(searchHit -> {
@@ -556,15 +530,4 @@ public class EndpointService extends AbstractService {
     }
 
 
-    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));
-        }
-
-    }
-
 }
-- 
GitLab