Java tutorial
/* * 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.PoolBean; import burstcoin.observer.event.PoolUpdateEvent; import burstcoin.observer.service.model.Account; import burstcoin.observer.service.model.AccountIds; import burstcoin.observer.service.model.Block; import burstcoin.observer.service.model.BlockchainStatus; import burstcoin.observer.service.model.Blocks; import burstcoin.observer.service.model.RewardRecipient; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; 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; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @Component public class PoolService { private static Log LOG = LogFactory.getLog(PoolService.class); private static final String SOLO_KEY = "Solo-Miners"; @Autowired private ObjectMapper objectMapper; @Autowired private HttpClient httpClient; @Autowired private ApplicationEventPublisher publisher; private Timer timer = new Timer(); private List<Block> blocks; @PostConstruct private void postConstruct() { LOG.info("Started repeating 'check pools' task."); startCheckPoolsTask(); } private void startCheckPoolsTask() { timer.schedule(new TimerTask() { @Override public void run() { try { blocks = getBlocks(10 /*last 10 days*/); final Map<String, Integer> countLookup = new HashMap<>(); Map<String, Long> rewardLookup = new HashMap<>(); for (Block block : PoolService.this.blocks) { String generatorRS = block.getGeneratorRS(); Long reward = Long.valueOf(block.getBlockReward()); Long fee = Long.valueOf(block.getTotalFeeNQT()); if (!countLookup.containsKey(generatorRS)) { countLookup.put(generatorRS, 1); rewardLookup.put(generatorRS, reward + fee); } else { Integer count = countLookup.get(generatorRS) + 1; countLookup.put(generatorRS, count); Long rewards = rewardLookup.get(generatorRS) + reward + fee; rewardLookup.put(generatorRS, rewards); } } Map<String, Set<String>> rewardAssignmentLookup = new HashMap<>(); rewardAssignmentLookup.put(SOLO_KEY, new HashSet<>()); for (Block block : blocks) { if (block != null) { final String numericGeneratorAccountId = block.getGenerator(); // check if numericGeneratorAccountId is known already boolean generatorKnown = false; Iterator<Map.Entry<String, Set<String>>> iterator = rewardAssignmentLookup.entrySet() .iterator(); while (!generatorKnown && iterator.hasNext()) { generatorKnown = iterator.next().getValue().contains(numericGeneratorAccountId); } if (!generatorKnown) { RewardRecipient rewardRecipient = getRewardRecipient(numericGeneratorAccountId); final String rewardRecipientAccounId = rewardRecipient.getRewardRecipient(); if (numericGeneratorAccountId.equals(rewardRecipientAccounId)) { // solo rewardAssignmentLookup.get(SOLO_KEY).add(rewardRecipientAccounId); } else { // pool - get all reward Assignments of new found pool; rewardAssignmentLookup.put(rewardRecipientAccounId, getAccountsWithRewardRecipient(rewardRecipientAccounId)); } } } } List<PoolBean> poolBeans = onRewardAssignmentLookup(rewardAssignmentLookup); publisher.publishEvent(new PoolUpdateEvent(poolBeans)); } catch (Exception e) { LOG.error("Failed updating Pool data."); } } }, 200, ObserverProperties.getPoolRefreshInterval()); } private List<PoolBean> onRewardAssignmentLookup(Map<String, Set<String>> assignmentLookup) { // generatorRs -> foundBlocks Map<String, Integer> countLookup = new HashMap<>(); // generatorRS -> reward+fee Map<String, Long> rewardLookup = new HashMap<>(); for (Block block : blocks) { String generatorId = block.getGenerator(); Long reward = Long.valueOf(block.getBlockReward() + "00000000"); Long fee = Long.valueOf(block.getTotalFeeNQT()); // one id for all solo miners if (assignmentLookup.get(SOLO_KEY).contains(generatorId)) { generatorId = SOLO_KEY; } if (!countLookup.containsKey(generatorId)) { countLookup.put(generatorId, 1); rewardLookup.put(generatorId, reward + fee); } else { Integer count = countLookup.get(generatorId) + 1; countLookup.put(generatorId, count); Long rewards = rewardLookup.get(generatorId) + reward + fee; rewardLookup.put(generatorId, rewards); } } Map<String, Account> accountLookup = new HashMap<>(); for (String poolAccount : assignmentLookup.keySet()) { if (!SOLO_KEY.equals(poolAccount)) { Account account = getAccount(poolAccount); accountLookup.put(account.getAccount(), account); } } List<PoolBean> pools = new ArrayList<>(); // create models for (String poolAccountId : assignmentLookup.keySet()) { if (SOLO_KEY.equals(poolAccountId)) { pools.add(new PoolBean(poolAccountId, poolAccountId, "Solo-Miners", "", "0", assignmentLookup.get(poolAccountId).size(), countLookup.get(SOLO_KEY), assignmentLookup.get(poolAccountId).size(), formatAmountNQT(rewardLookup.get(SOLO_KEY)))); } else { Account account = accountLookup.get(poolAccountId); Integer minedBlocks = 0; int numberOfPoolBlockFinder = 0; Long earnedReward = 0L; for (String poolMinerAccountId : assignmentLookup.get(poolAccountId)) { if (countLookup.containsKey(poolMinerAccountId)) { numberOfPoolBlockFinder += 1; minedBlocks += countLookup.get(poolMinerAccountId); earnedReward += rewardLookup.get(poolMinerAccountId); } } pools.add(new PoolBean(poolAccountId, account.getAccountRS(), account.getName(), account.getDescription(), formatAmountNQT(Long.valueOf(account.getBalanceNQT())), assignmentLookup.get(poolAccountId).size(), minedBlocks, numberOfPoolBlockFinder, formatAmountNQT(earnedReward))); } } Collections.sort(pools, new Comparator<PoolBean>() { @Override public int compare(PoolBean o1, PoolBean o2) { return Integer.compare(o2.getFoundBlocks(), o1.getFoundBlocks()); } }); return pools; } private String formatAmountNQT(Long amount) { String amountStr = String.valueOf(amount); return amount != null && amountStr.length() > 8 ? amountStr.substring(0, amountStr.length() - 8) : "0"; } private List<Block> getBlocks(int days) { List<Block> allBlocks = new ArrayList<>(); BlockchainStatus blockchainStatus = getBlockchainStatus(); if (blockchainStatus != null) { int limit = 360 * days; int offset = 0; // getBlocks, max. 100 per request if (offset + limit > blockchainStatus.getNumberOfBlocks()) { limit = blockchainStatus.getNumberOfBlocks() - offset; } final int steps = limit / 100; final int lastStepLimit = limit % 100; for (int step = 0; step <= steps; step++) { allBlocks.addAll(getBlocks(offset + step * 100, offset + step * 100 + (step == steps ? lastStepLimit : 100))); } LOG.info("Blocks form " + allBlocks.get(allBlocks.size() - 1).getHeight() + " to " + allBlocks.get(0).getHeight() + " received."); } return allBlocks; } private Account getAccount(String accountId) { Account result = null; try { ContentResponse response; response = httpClient .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getAccount&account=" + accountId) .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send(); result = objectMapper.readValue(response.getContentAsString(), Account.class); } catch (TimeoutException timeoutException) { LOG.warn("Unable to get account caused by connectionTimeout, currently '" + (ObserverProperties.getConnectionTimeout() / 1000) + " sec.' try increasing it!"); } catch (Exception e) { LOG.trace("Unable to get mining info from wallet: " + e.getMessage()); } return result; } private RewardRecipient getRewardRecipient(String account) { RewardRecipient result = null; try { ContentResponse response; response = httpClient .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getRewardRecipient&account=" + account) .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send(); result = objectMapper.readValue(response.getContentAsString(), RewardRecipient.class); } catch (TimeoutException timeoutException) { LOG.warn("Unable to get reward recipient caused by connectionTimeout, currently '" + (ObserverProperties.getConnectionTimeout() / 1000) + " sec.' try increasing it!"); } catch (Exception e) { LOG.trace("Unable to get mining info from wallet: " + e.getMessage()); } return result; } private Set<String> getAccountsWithRewardRecipient(String poolAccount) { AccountIds result = null; try { ContentResponse response; response = httpClient .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getAccountsWithRewardRecipient&account=" + poolAccount) .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send(); result = objectMapper.readValue(response.getContentAsString(), AccountIds.class); } catch (TimeoutException timeoutException) { LOG.warn("Unable to get accounts with reward recipient caused by connectionTimeout, currently '" + (ObserverProperties.getConnectionTimeout() / 1000) + " sec.' try increasing it!"); } catch (Exception e) { LOG.trace("Unable to get mining info from wallet: " + e.getMessage()); } return result != null ? result.getAccounts() : new HashSet<>(); } private BlockchainStatus getBlockchainStatus() { BlockchainStatus result = null; try { ContentResponse response; response = httpClient .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getBlockchainStatus") .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send(); result = objectMapper.readValue(response.getContentAsString(), BlockchainStatus.class); } catch (TimeoutException timeoutException) { LOG.warn("Unable to get blockchain status caused by connectionTimeout, currently '" + (ObserverProperties.getConnectionTimeout() / 1000) + " sec.' try increasing it!"); } catch (Exception e) { LOG.trace("Unable to get blockchain status from wallet: " + e.getMessage()); } return result; } private List<Block> getBlocks(int firstIndex, int lastIndex) { Blocks result = null; try { ContentResponse response; response = httpClient .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getBlocks" + "&firstIndex=" + firstIndex + "&lastIndex=" + lastIndex + "&includeTransactions=false") .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send(); result = objectMapper.readValue(response.getContentAsString(), Blocks.class); } catch (TimeoutException timeoutException) { LOG.warn("Unable to get blockchain status caused by connectionTimeout, currently '" + (ObserverProperties.getConnectionTimeout() / 1000) + " sec.' try increasing it!"); } catch (Exception e) { LOG.trace("Unable to get mining info from wallet (maybe devV2): " + e.getMessage()); } return result != null ? result.getBlocks() : new ArrayList<>(); } }