package co.nyzo.verifier; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class BalanceListManager { private static BalanceList genesisList = null; private static final int numberOfRecentLists = 4; private static BalanceList[] recentLists = new BalanceList[numberOfRecentLists]; private static Set<ByteBuffer> accountsInSystem = ConcurrentHashMap.newKeySet(); private static final long maximumMapSize = 6; // This is a map from balance list hash to balance list. private static final Map<ByteBuffer, BalanceList> balanceListMap = new ConcurrentHashMap<>(); public static BalanceList recentBalanceListForHeight(long blockHeight) { // This method is not synchronized. So, store references to the list to avoid the situation where a // particular list is checked and then the array slot is reassigned before the reference is assigned to // the result variable. BalanceList[] recentLists = new BalanceList[numberOfRecentLists]; System.arraycopy(BalanceListManager.recentLists, 0, recentLists, 0, numberOfRecentLists); // We only need check the height, as these are frozen blocks. BalanceList result = null; for (int i = 0; i < numberOfRecentLists && result == null; i++) { if (recentLists[i] != null && recentLists[i].getBlockHeight() == blockHeight) { result = recentLists[i]; } } return result; } public static BalanceList balanceListForBlock(Block block) { BalanceList balanceList = null; if (block != null) { // Get a local reference to the list at the frozen edge. The array is ordered by decreasing block height. BalanceList frozenEdgeList = recentLists[0]; BalanceList recentList = recentBalanceListForHeight(block.getBlockHeight()); if (block.getBlockHeight() == 0) { if (genesisList == null) { genesisList = Block.balanceListForNextBlock(null, null, block.getTransactions(), block.getVerifierIdentifier(), block.getBlockchainVersion()); } balanceList = genesisList; } else if (recentList != null) { balanceList = recentList; } else if (frozenEdgeList == null || block.getBlockHeight() == frozenEdgeList.getBlockHeight() + 1) { // First, try to get the balance list from the map. balanceList = balanceListMap.get(ByteBuffer.wrap(block.getBalanceListHash())); // If the balance list was not in the map, try to derive it. if (balanceList == null) { // Step back to previous blocks until we are able to find a balance list that we have. Block startBlock = block; List<Block> blocks = new ArrayList<>(Collections.singletonList(startBlock)); BalanceList startBalanceList = null; while (startBlock != null && startBalanceList == null) { startBlock = startBlock.getPreviousBlock(); if (startBlock != null) { blocks.add(0, startBlock); startBalanceList = balanceListMap.get(ByteBuffer.wrap(startBlock.getBalanceListHash())); } } // If a suitable start balance list was found, derive the desired list. if (startBalanceList != null) { balanceList = startBalanceList; for (int i = 0; i < blocks.size() - 1; i++) { balanceList = Block.balanceListForNextBlock(blocks.get(i), balanceList, blocks.get(i + 1).getTransactions(), blocks.get(i + 1).getVerifierIdentifier(), blocks.get(i + 1).getBlockchainVersion()); } registerBalanceList(balanceList); } } } } return balanceList; } public static void registerBalanceList(BalanceList balanceList) { if (balanceList != null && balanceListMap.size() < maximumMapSize) { balanceListMap.put(ByteBuffer.wrap(balanceList.getHash()), balanceList); } } public static BalanceList getFrozenEdgeList() { return recentLists[0]; } public static boolean accountIsInSystem(byte[] identifier) { return accountsInSystem.contains(ByteBuffer.wrap(identifier)); } public static void updateFrozenEdge(BalanceList frozenEdgeList) { if (frozenEdgeList != null) { for (int i = numberOfRecentLists - 1; i > 0; i--) { recentLists[i] = recentLists[i - 1]; } recentLists[0] = frozenEdgeList; Set<ByteBuffer> accountsInSystem = ConcurrentHashMap.newKeySet(); for (BalanceListItem item : frozenEdgeList.getItems()) { accountsInSystem.add(ByteBuffer.wrap(item.getIdentifier())); } BalanceListManager.accountsInSystem = accountsInSystem; balanceListMap.clear(); balanceListMap.put(ByteBuffer.wrap(frozenEdgeList.getHash()), frozenEdgeList); } } }