Permalink
Fetching contributors…
Cannot retrieve contributors at this time
414 lines (365 sloc) 14.3 KB
/*
* 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<>();
}
}