burstcoin.observer.service.NetworkService.java Source code

Java tutorial

Introduction

Here is the source code for burstcoin.observer.service.NetworkService.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 by luxe - https://github.com/de-luxe - BURST-LUXE-RED2-G6JW-H4HG5
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package burstcoin.observer.service;

import burstcoin.observer.ObserverProperties;
import burstcoin.observer.bean.NetworkBean;
import burstcoin.observer.bean.NetworkState;
import burstcoin.observer.event.NetworkUpdateEvent;
import burstcoin.observer.service.model.MiningInfo;
import burstcoin.observer.service.network.GetMiningInfoTask;
import burstcoin.observer.service.network.MiningInfoEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

@Component
public class NetworkService {
    private static Log LOG = LogFactory.getLog(NetworkService.class);

    @Autowired
    private ApplicationEventPublisher publisher;

    @Autowired
    private BeanFactory beanFactory;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    @Qualifier("networkTaskExecutor")
    private SimpleAsyncTaskExecutor taskExecutor;

    private Timer timer = new Timer();
    // blockHeight -> genSig -> domains
    private Map<Long, Map<String, Set<String>>> genSigLookup = new HashMap<>();
    private boolean forkMailSend = false;
    private Long lastBlockWithSameGenSigMailSend = 0L;
    private Map<String, NetworkState> previousStateLookup = new HashMap<>();

    private static final Map<String, MiningInfo> miningInfoLookup = new HashMap<>();

    private Date lastUpdate = new Date();

    @PostConstruct
    private void postConstruct() {
        LOG.info("Started repeating 'check network' task.");
        startCheckNetworkTask();
    }

    private void startCheckNetworkTask() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // only start if previous is finished
                if (miningInfoLookup.isEmpty() || new Date().getTime() - (5 * 60 * 1000) > lastUpdate.getTime()) {
                    miningInfoLookup.clear();
                    try {
                        lastUpdate = new Date();
                        for (String networkServerUrl : ObserverProperties.getNetworkServerUrls()) {
                            GetMiningInfoTask getMiningInfoTask = beanFactory.getBean(GetMiningInfoTask.class,
                                    networkServerUrl);
                            taskExecutor.execute(getMiningInfoTask);
                        }
                    } catch (Exception e) {
                        LOG.error("Failed receiving Network data.");
                    }
                }
            }
        }, 1200, ObserverProperties.getNetworkRefreshInterval());
    }

    @EventListener
    public void onMiningInfoEvent(MiningInfoEvent miningInfoEvent) {
        synchronized (miningInfoLookup) {
            miningInfoLookup.put(miningInfoEvent.getNetworkServerUrl(), miningInfoEvent.getMiningInfo());

            LOG.debug("MiningInfo:" + miningInfoEvent.getNetworkServerUrl() + " #" + miningInfoLookup.size() + "/"
                    + ObserverProperties.getNetworkServerUrls().size());

            if (ObserverProperties.getNetworkServerUrls().size() == miningInfoLookup.size()) {
                LOG.info("Received MiningInfo from all newtworkServerUrls ...");
                processMiningInfoLookup(miningInfoLookup);
                miningInfoLookup.clear();
            }
        }
    }

    private void processMiningInfoLookup(Map<String, MiningInfo> miningInfoLookup) {
        List<NetworkBean> networkBeans = new ArrayList<>();
        for (Map.Entry<String, MiningInfo> entry : miningInfoLookup.entrySet()) {
            MiningInfo miningInfo = entry.getValue();

            String https = entry.getKey().contains("https://") ? "Yes" : "No";
            String domain = entry.getKey().replace("http://", "").replace("https://", "");
            if (miningInfo != null && miningInfo.getGenerationSignature() != null) {
                networkBeans.add(new NetworkBean(String.valueOf(miningInfo.getHeight()), domain, entry.getKey(),
                        miningInfo.getBaseTarget(), miningInfo.getGenerationSignature().substring(0, 25) + "...",
                        String.valueOf(miningInfo.getTargetDeadline()), https));
            } else {
                networkBeans.add(new NetworkBean(domain));
            }
        }

        Collections.sort(networkBeans, new Comparator<NetworkBean>() {
            @Override
            public int compare(NetworkBean o1, NetworkBean o2) {
                return o1.getBaseTarget().compareTo(o2.getBaseTarget());
            }
        });
        Collections.sort(networkBeans, new Comparator<NetworkBean>() {
            @Override
            public int compare(NetworkBean o1, NetworkBean o2) {
                return o1.getType().compareTo(o2.getType());
            }
        });
        Collections.sort(networkBeans, new Comparator<NetworkBean>() {
            @Override
            public int compare(NetworkBean o1, NetworkBean o2) {
                return o2.getHeight().compareTo(o1.getHeight());
            }
        });

        // update genSig Lookup
        int numberOfNotAvailableDomains = 0;
        for (NetworkBean networkBean : networkBeans) {
            if (networkBean.getAvailable()) {
                Long height = Long.valueOf(networkBean.getHeight());
                if (!genSigLookup.containsKey(height)) {
                    genSigLookup.put(height, new HashMap<>());
                }
                Map<String, Set<String>> subMap = genSigLookup.get(height);
                if (!subMap.containsKey(networkBean.getGenerationSignature())) {
                    subMap.put(networkBean.getGenerationSignature(), new HashSet<>());
                }
                Set<String> domains = subMap.get(networkBean.getGenerationSignature());
                domains.add(networkBean.getDomain());
            } else {
                // N/A
                numberOfNotAvailableDomains++;
            }
        }

        List<Long> order = new ArrayList<>(genSigLookup.keySet());
        Collections.sort(order);
        Collections.reverse(order);

        Iterator<Long> iterator = order.iterator();
        Long lastBlockWithSameGenSig = null;
        while (iterator.hasNext() && lastBlockWithSameGenSig == null) {
            Long nextHeight = iterator.next();
            if (genSigLookup.get(nextHeight).size() == 1) // only one known genSig for height
            {
                // number of domains with same genSig = all domains without N/A
                if (networkBeans.size() - numberOfNotAvailableDomains == genSigLookup.get(nextHeight).values()
                        .iterator().next().size()) {
                    lastBlockWithSameGenSig = nextHeight;
                }
            }
        }

        long maxHeight = 0;

        for (NetworkBean networkBean : networkBeans) {
            if (networkBean.getAvailable()) {
                maxHeight = Math.max(Long.valueOf(networkBean.getHeight()), maxHeight);
            }
        }

        boolean appStartedAfterForkHappened = lastBlockWithSameGenSig == null;

        boolean sendMail = false;

        for (NetworkBean networkBean : networkBeans) {
            if (networkBean.getAvailable()) {
                Long height = Long.valueOf(networkBean.getHeight());
                Set<String> domainsWithSameGenSigForBlock = genSigLookup.get(height)
                        .get(networkBean.getGenerationSignature());
                if (genSigLookup.get(height).size() > 1 && domainsWithSameGenSigForBlock
                        .size() < (networkBeans.size() - numberOfNotAvailableDomains) / 2) {
                    networkBean.setState(NetworkState.FORKED);
                    sendMail = true;
                }

                if (height + 4 < maxHeight) // when the wallet is 4 blocks behind -> stuck
                {
                    if (!networkBean.getState().equals(NetworkState.FORKED)) // if it's forked, then ignore the stuck-check, because forks may also be behind
                    {
                        networkBean.setState(NetworkState.STUCK);

                        if (ObserverProperties.isEnableStuckNotify() && (!previousStateLookup
                                .containsKey(networkBean.getDomain())
                                || !previousStateLookup.get(networkBean.getDomain()).equals(NetworkState.STUCK))) //send only once
                        {
                            sendMessage("Burstcoin-Observer - Stuck at block: " + networkBean.getHeight(),
                                    networkBean.getDomain() + "\r\n" + "Please check: "
                                            + ObserverProperties.getObserverUrl());
                        }
                    }
                }
            }
        }

        if (sendMail && !forkMailSend) {
            if (ObserverProperties.isEnableForkNotify()
                    && (appStartedAfterForkHappened || (lastBlockWithSameGenSigMailSend != null
                            && !lastBlockWithSameGenSig.equals(lastBlockWithSameGenSigMailSend)))) {
                forkMailSend = true;
                // ensure only one mail send per lastBlockWithSameGenSig e.g. if forked wallet pops off blocks over and over again
                lastBlockWithSameGenSigMailSend = lastBlockWithSameGenSig;

                sendMessage("Burstcoin-Observer - Fork after block: " + lastBlockWithSameGenSig,
                        "Please check: " + ObserverProperties.getObserverUrl());
            }
        } else if (!sendMail && !appStartedAfterForkHappened) {
            forkMailSend = false;
        }

        // store the network state for next check-loop
        for (NetworkBean networkBean : networkBeans) {
            if (networkBean.getAvailable() && networkBean.getDomain() != null) {
                previousStateLookup.put(networkBean.getDomain(), networkBean.getState());
            }
        }

        publisher.publishEvent(
                new NetworkUpdateEvent(networkBeans, lastBlockWithSameGenSig, createSenkeyChartData()));
    }

    // create multi senkey chart data
    private List<List> createSenkeyChartData() {
        List<List> multiSenkeyData = new ArrayList<>();
        int numberOfBlocks = 11;
        List<Long> orderedHeight = new ArrayList<>(genSigLookup.keySet());
        Collections.sort(orderedHeight);
        List<Long> heightSub = orderedHeight.subList(
                (orderedHeight.size() > numberOfBlocks ? orderedHeight.size() - numberOfBlocks : 0),
                orderedHeight.size());

        Map<String, Long> sourcePerGenSigLookup = new HashMap<>();
        Map<Long, Map<String, String>> remainingSourcesWithoutTarget = new HashMap<>();
        for (Long source : heightSub) {
            remainingSourcesWithoutTarget.put(source, new HashMap<>());

            if (heightSub.indexOf(source) < heightSub.size() - 1) {
                Long target = heightSub.get(heightSub.indexOf(source) + 1);
                Map<String, Set<String>> sourceGenSigDomainLookup = genSigLookup.get(source);
                // domain -> gensig
                Map<String, String> sourceMapping = new HashMap<>();

                for (Map.Entry<String, Set<String>> sourceEntry : sourceGenSigDomainLookup.entrySet()) {
                    for (String domain : sourceEntry.getValue()) {
                        sourceMapping.put(domain, sourceEntry.getKey());
                    }
                }

                Map<String, Set<String>> targetGenSigDomainLookup = genSigLookup.get(target);
                // domain -> gensig
                Map<String, String> targetMapping = new HashMap<>();
                for (Map.Entry<String, Set<String>> targetEntry : targetGenSigDomainLookup.entrySet()) {
                    for (String domain : targetEntry.getValue()) {
                        targetMapping.put(domain, targetEntry.getKey());
                    }
                }

                // sourceGenSig -> targetGensig -> domains
                Map<String, Map<String, List<String>>> fromToCounter = new HashMap<>();

                // domain ->genSig
                for (Map.Entry<String, String> entry : sourceMapping.entrySet()) {
                    String domain = entry.getKey();
                    String sourceGenSig = entry.getValue();
                    String targetGenSig = targetMapping.get(domain);

                    // remember source block height by genSig
                    sourcePerGenSigLookup.put(sourceGenSig, source);

                    // skip if from/to is not available
                    if (targetGenSig != null) {
                        if (!fromToCounter.containsKey(sourceGenSig)) {
                            fromToCounter.put(sourceGenSig, new HashMap<>());
                        }
                        if (!fromToCounter.get(sourceGenSig).containsKey(targetGenSig)) {
                            fromToCounter.get(sourceGenSig).put(targetGenSig, new ArrayList<>());
                        }
                        fromToCounter.get(sourceGenSig).get(targetGenSig).add(domain);
                    } else {
                        // no target found
                        remainingSourcesWithoutTarget.get(source).put(entry.getKey(), entry.getValue());
                    }
                }

                // check if we have a target for previous sources without target now
                List<Long> previousSources = new ArrayList<>(remainingSourcesWithoutTarget.keySet());
                // ignore current source
                if (previousSources.contains(source)) {
                    previousSources.remove(source);
                }
                Collections.sort(previousSources);
                for (Long previousSource : previousSources) {
                    Iterator<Map.Entry<String, String>> iter = remainingSourcesWithoutTarget.get(previousSource)
                            .entrySet().iterator();

                    while (iter.hasNext()) {
                        Map.Entry<String, String> entry = iter.next();
                        String domain = entry.getKey();
                        String sourceGenSig = entry.getValue();
                        String targetGenSig = targetMapping.get(domain);

                        // skip if from/to is not available
                        if (targetGenSig != null) {
                            if (!fromToCounter.containsKey(sourceGenSig)) {
                                fromToCounter.put(sourceGenSig, new HashMap<>());
                            }
                            if (!fromToCounter.get(sourceGenSig).containsKey(targetGenSig)) {
                                fromToCounter.get(sourceGenSig).put(targetGenSig, new ArrayList<>());
                            }
                            fromToCounter.get(sourceGenSig).get(targetGenSig).add(domain);

                            iter.remove();
                        }
                    }

                    if (remainingSourcesWithoutTarget.get(previousSource).isEmpty()) {
                        remainingSourcesWithoutTarget.remove(previousSource);
                    }
                }

                for (String sourceGenSig : fromToCounter.keySet()) {
                    for (Map.Entry<String, List<String>> entry : fromToCounter.get(sourceGenSig).entrySet()) {
                        Long currentSource = sourcePerGenSigLookup.get(sourceGenSig);
                        String sourceName = currentSource + " [" + sourceGenSig.substring(0, 4) + "..]";
                        String targetName = target + " [" + entry.getKey().substring(0, 4) + "..]";

                        String tooltip = entry.getValue().size() + " nodes," + " from " + currentSource + " ["
                                + sourceGenSig.substring(0, 6) + "..]," + " to " + target + " ["
                                + entry.getKey().substring(0, 6) + "..]";

                        multiSenkeyData
                                .add(Arrays.asList(sourceName, targetName, entry.getValue().size(), tooltip));
                    }
                }
            }
        }
        return multiSenkeyData;
    }

    private void sendMessage(String subject, String message) {
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo(ObserverProperties.getMailReceiver());
        mailMessage.setReplyTo(ObserverProperties.getMailReplyTo());
        mailMessage.setFrom(ObserverProperties.getMailSender());
        mailMessage.setSubject(subject);
        mailMessage.setText(message);
        mailSender.send(mailMessage);
    }
}