<commit_before>/*
 * This file is part of Bitsquare.
 *
 * Bitsquare is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bitsquare 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 Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
 */

package io.bitsquare.arbitration;

import com.google.inject.Inject;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.BootstrapListener;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.user.User;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.bitcoinj.core.Utils.HEX;

public class ArbitratorManager {
    private static final Logger log = LoggerFactory.getLogger(ArbitratorManager.class);

    ///////////////////////////////////////////////////////////////////////////////////////////
    // Static
    ///////////////////////////////////////////////////////////////////////////////////////////

    private static final long REPUBLISH_MILLIS = Arbitrator.TTL / 2;
    private static final long RETRY_REPUBLISH_SEC = 5;
    private static final long REPEATED_REPUBLISH_AT_STARTUP_SEC = 60;

    // Keys for invited arbitrators in bootstrapping phase (before registration is open to anyone and security payment is implemented)
    // For developers we add here 2 test keys so one can setup an arbitrator by adding that test pubKey 
    // to the publicKeys list and use the test PrivKey for arbitrator registration.
    // PrivKey for dev testing: 6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a
    // Matching pubKey for dev testing: 027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee
    private static final List<String> publicKeys = new ArrayList<>(Arrays.asList(
            "<KEY>",
            "0365c6af94681dbee69de1851f98d4684063bf5c2d64b1c73ed5d90434f375a054",
            "<KEY>",
            "02c1e5a242387b6d5319ce27246cea6edaaf51c3550591b528d2578a4753c56c2c",
            "025c319faf7067d9299590dd6c97fe7e56cd4dac61205ccee1cd1fc390142390a2",
            "<KEY>",
            "<KEY>",
            "<KEY>",
            "<KEY>",
            "02349a51512c1c04c67118386f4d27d768c5195a83247c150a4b722d161722ba81",
            "03f718a2e0dc672c7cdec0113e72c3322efc70412bb95870750d25c32cd98de17d",
            "028ff47ee2c56e66313928975c58fa4f1b19a0f81f3a96c4e9c9c3c6768075509e",
            "02b517c0cbc3a49548f448ddf004ed695c5a1c52ec110be1bfd65fa0ca0761c94b",
            "03df837a3a0f3d858e82f3356b71d1285327f101f7c10b404abed2abc1c94e7169",
            "0203a90fb2ab698e524a5286f317a183a84327b8f8c3f7fa4a98fec9e1cefd6b72",
            "023c99cc073b851c892d8c43329ca3beb5d2213ee87111af49884e3ce66cbd5ba5",
            "0274f772a98d23e7a0251ab30d7121897b5aebd11a2f1e45ab654aa57503173245"
    ));


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Instance fields
    ///////////////////////////////////////////////////////////////////////////////////////////

    private final KeyRing keyRing;
    private final ArbitratorService arbitratorService;
    private final User user;
    private final ObservableMap<NodeAddress, Arbitrator> arbitratorsObservableMap = FXCollections.observableHashMap();
    private Timer republishArbitratorTimer, retryRepublishArbitratorTimer;


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Constructor
    ///////////////////////////////////////////////////////////////////////////////////////////

    @Inject
    public ArbitratorManager(KeyRing keyRing, ArbitratorService arbitratorService, User user) {
        this.keyRing = keyRing;
        this.arbitratorService = arbitratorService;
        this.user = user;

        arbitratorService.addHashSetChangedListener(new HashMapChangedListener() {
            @Override
            public void onAdded(ProtectedStorageEntry data) {
                applyArbitrators();
            }

            @Override
            public void onRemoved(ProtectedStorageEntry data) {
                applyArbitrators();
            }
        });
    }

    public void shutDown() {
        stopRepublishArbitratorTimer();
        stopRetryRepublishArbitratorTimer();
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // API
    ///////////////////////////////////////////////////////////////////////////////////////////

    public void onAllServicesInitialized() {
        if (user.getRegisteredArbitrator() != null) {
            P2PService p2PService = arbitratorService.getP2PService();
            if (p2PService.isBootstrapped())
                republishArbitrator();
            else
                p2PService.addP2PServiceListener(new BootstrapListener() {
                    @Override
                    public void onBootstrapComplete() {
                        republishArbitrator();
                    }
                });
        }

        republishArbitratorTimer = UserThread.runPeriodically(this::republishArbitrator, REPUBLISH_MILLIS, TimeUnit.MILLISECONDS);

        UserThread.runAfter(this::republishArbitrator, REPEATED_REPUBLISH_AT_STARTUP_SEC);

        applyArbitrators();
    }

    public void applyArbitrators() {
        Map<NodeAddress, Arbitrator> map = arbitratorService.getArbitrators();
        log.trace("Arbitrators . size=" + map.values().size());
        arbitratorsObservableMap.clear();
        Map<NodeAddress, Arbitrator> filtered = map.values().stream()
                .filter(e -> isPublicKeyInList(Utils.HEX.encode(e.getRegistrationPubKey()))
                        && verifySignature(e.getPubKeyRing().getSignaturePubKey(), e.getRegistrationPubKey(), e.getRegistrationSignature()))
                .collect(Collectors.toMap(Arbitrator::getArbitratorNodeAddress, Function.identity()));

        arbitratorsObservableMap.putAll(filtered);
        // we need to remove accepted arbitrators which are not available anymore
        if (user.getAcceptedArbitrators() != null) {
            List<Arbitrator> removeList = user.getAcceptedArbitrators().stream()
                    .filter(e -> !arbitratorsObservableMap.containsValue(e))
                    .collect(Collectors.toList());
            removeList.stream().forEach(user::removeAcceptedArbitrator);

            // if we don't have any arbitrator anymore we set all matching
            if (user.getAcceptedArbitrators().isEmpty()) {
                arbitratorsObservableMap.values().stream()
                        .filter(user::hasMatchingLanguage)
                        .forEach(user::addAcceptedArbitrator);
            }
        }
    }

    public void addArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
        user.setRegisteredArbitrator(arbitrator);
        arbitratorsObservableMap.put(arbitrator.getArbitratorNodeAddress(), arbitrator);<commit_msg>Publish arbitrator after bootstrap completed<commit_after>/*
 * This file is part of Bitsquare.
 *
 * Bitsquare is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bitsquare 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 Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
 */

package io.bitsquare.arbitration;

import com.google.inject.Inject;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.BootstrapListener;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.user.User;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.bitcoinj.core.Utils.HEX;

public class ArbitratorManager {
    private static final Logger log = LoggerFactory.getLogger(ArbitratorManager.class);

    ///////////////////////////////////////////////////////////////////////////////////////////
    // Static
    ///////////////////////////////////////////////////////////////////////////////////////////

    private static final long REPUBLISH_MILLIS = Arbitrator.TTL / 2;
    private static final long RETRY_REPUBLISH_SEC = 5;
    private static final long REPEATED_REPUBLISH_AT_STARTUP_SEC = 60;

    // Keys for invited arbitrators in bootstrapping phase (before registration is open to anyone and security payment is implemented)
    // For developers we add here 2 test keys so one can setup an arbitrator by adding that test pubKey 
    // to the publicKeys list and use the test PrivKey for arbitrator registration.
    // PrivKey for dev testing: 6ac43ea1df2a290c1c8391736aa42e4339c5cb4f110ff0257a13b63211977b7a
    // Matching pubKey for dev testing: 027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee
    private static final List<String> publicKeys = new ArrayList<>(Arrays.asList(
            "<KEY>",
            "0365c6af94681dbee69de1851f98d4684063bf5c2d64b1c73ed5d90434f375a054",
            "<KEY>",
            "02c1e5a242387b6d5319ce27246cea6edaaf51c3550591b528d2578a4753c56c2c",
            "025c319faf7067d9299590dd6c97fe7e56cd4dac61205ccee1cd1fc390142390a2",
            "<KEY>",
            "<KEY>",
            "<KEY>",
            "<KEY>",
            "02349a51512c1c04c67118386f4d27d768c5195a83247c150a4b722d161722ba81",
            "03f718a2e0dc672c7cdec0113e72c3322efc70412bb95870750d25c32cd98de17d",
            "028ff47ee2c56e66313928975c58fa4f1b19a0f81f3a96c4e9c9c3c6768075509e",
            "02b517c0cbc3a49548f448ddf004ed695c5a1c52ec110be1bfd65fa0ca0761c94b",
            "03df837a3a0f3d858e82f3356b71d1285327f101f7c10b404abed2abc1c94e7169",
            "0203a90fb2ab698e524a5286f317a183a84327b8f8c3f7fa4a98fec9e1cefd6b72",
            "023c99cc073b851c892d8c43329ca3beb5d2213ee87111af49884e3ce66cbd5ba5",
            "0274f772a98d23e7a0251ab30d7121897b5aebd11a2f1e45ab654aa57503173245"
    ));


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Instance fields
    ///////////////////////////////////////////////////////////////////////////////////////////

    private final KeyRing keyRing;
    private final ArbitratorService arbitratorService;
    private final User user;
    private final ObservableMap<NodeAddress, Arbitrator> arbitratorsObservableMap = FXCollections.observableHashMap();
    private Timer republishArbitratorTimer, retryRepublishArbitratorTimer;


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Constructor
    ///////////////////////////////////////////////////////////////////////////////////////////

    @Inject
    public ArbitratorManager(KeyRing keyRing, ArbitratorService arbitratorService, User user) {
        this.keyRing = keyRing;
        this.arbitratorService = arbitratorService;
        this.user = user;

        arbitratorService.addHashSetChangedListener(new HashMapChangedListener() {
            @Override
            public void onAdded(ProtectedStorageEntry data) {
                applyArbitrators();
            }

            @Override
            public void onRemoved(ProtectedStorageEntry data) {
                applyArbitrators();
            }
        });
    }

    public void shutDown() {
        stopRepublishArbitratorTimer();
        stopRetryRepublishArbitratorTimer();
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // API
    ///////////////////////////////////////////////////////////////////////////////////////////

    public void onAllServicesInitialized() {
        if (user.getRegisteredArbitrator() != null) {
            P2PService p2PService = arbitratorService.getP2PService();
            if (p2PService.isBootstrapped())
                isBootstrapped();
            else
                p2PService.addP2PServiceListener(new BootstrapListener() {
                    @Override
                    public void onBootstrapComplete() {
                        isBootstrapped();
                    }
                });
        }

        applyArbitrators();
    }

    private void isBootstrapped() {
        if (republishArbitratorTimer == null) {
            republishArbitratorTimer = UserThread.runPeriodically(this::republishArbitrator, REPUBLISH_MILLIS, TimeUnit.MILLISECONDS);
            UserThread.runAfter(this::republishArbitrator, REPEATED_REPUBLISH_AT_STARTUP_SEC);
            republishArbitrator();
        }
    }

    public void applyArbitrators() {
        Map<NodeAddress, Arbitrator> map = arbitratorService.getArbitrators();
        log.trace("Arbitrators . size=" + map.values().size());
        arbitratorsObservableMap.clear();
        Map<NodeAddress, Arbitrator> filtered = map.values().stream()
                .filter(e -> isPublicKeyInList(Utils.HEX.encode(e.getRegistrationPubKey()))
                        && verifySignature(e.getPubKeyRing().getSignaturePubKey(), e.getRegistrationPubKey(), e.getRegistrationSignature()))
                .collect(Collectors.toMap(Arbitrator::getArbitratorNodeAddress, Function.identity()));

        arbitratorsObservableMap.putAll(filtered);
        // we need to remove accepted arbitrators which are not available anymore
        if (user.getAcceptedArbitrators() != null) {
            List<Arbitrator> removeList = user.getAcceptedArbitrators().stream()
                    .filter(e -> !arbitratorsObservableMap.containsValue(e))
                    .collect(Collectors.toList());
            removeList.stream().forEach(user::removeAcceptedArbitrator);

            // if we don't have any arbitrator anymore we set all matching
            if (user.getAcceptedArbitrators().isEmpty()) {
                arbitratorsObservableMap.values().stream()
                        .filter(user::hasMatchingLanguage)
                        .forEach(user::addAcceptedArbitrator);
            }
        }
    }

    public void addArbitrator(Arbitrator arbitrator, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
        user.setRegisteredArbitrator(arbitrator);
        arbitratorsObservableMap.put(arbitrator.getArbitratorNodeAddress(), arbitrator);
{'fraction_non_alphanumeric': 0.15856083890797323, 'fraction_numerical': 0.06978846501536792, 'mean_word_length': 2.2820411392405062, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7249842', 'n_tokens_mistral': 5276, 'n_tokens_neox': 4573, 'n_words': 888}