/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt; import nxt.db.DbIterator; import nxt.util.Logger; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class BlockchainProcessorTest extends AbstractBlockchainTest { private static final String defaultTraceFile = "nxt-trace-default.csv"; private static final String testTraceFile = "nxt-trace.csv"; private static final int maxHeight = Constants.LAST_KNOWN_BLOCK; private static final int startHeight = 0; private static final long[] testLesseeAccounts = new long[]{1460178482, -318308835203526404L, 3312398282095696184L, 6373452498729869295L, 1088641461782019913L, -7984504957518839920L, 814976497827634325L}; private static final long[] testAssets = new long[]{6775372232354238105L, 3061160746493230502L, -5981557335608550881L, 4551058913252105307L, -318057271556719590L, -2234297255166670436L}; private static DebugTrace debugTrace; @BeforeClass public static void init() { AbstractBlockchainTest.init(newTestProperties()); debugTrace = DebugTrace.addDebugTrace(Collections.<Long>emptySet(), BlockchainProcessorTest.testTraceFile); } @AfterClass public static void shutdown() { AbstractBlockchainTest.shutdown(); } public void reset(int height) { debugTrace.resetLog(); if (blockchain.getHeight() > height) { blockchainProcessor.popOffTo(height); Assert.assertEquals(height, blockchain.getHeight()); } Assert.assertTrue(blockchain.getHeight() <= height); } @Test public void fullDownloadAndRescanTest() { reset(startHeight); download(startHeight, maxHeight); blockchainProcessor.scan(0, true); Assert.assertEquals(maxHeight, blockchain.getHeight()); Logger.logMessage("Successfully rescanned blockchain from 0 to " + maxHeight); compareTraceFiles(); debugTrace.resetLog(); } @Test public void multipleRescanTest() { reset(startHeight); int start = startHeight; int end; downloadTo(start); while ((end = start + 2000) <= maxHeight) { download(start, end); rescan(500); rescan(900); rescan(720); rescan(1439); rescan(200); rescan(1); rescan(2); start = end; } } @Test public void multiplePopOffTest() { reset(startHeight); int start = startHeight; int end; downloadTo(start); while ((end = start + 2000) <= maxHeight) { download(start, end); redownload(800, false); redownload(1440, false); redownload(720, false); redownload(1, false); start = end; } } @Test public void reprocessTransactionsTest() { int start = Constants.LAST_KNOWN_BLOCK - 2000; reset(start); int end; downloadTo(start); while (blockchain.getLastBlock().getTimestamp() < Nxt.getEpochTime() - 7200) { end = start + 100; download(start, end); redownload(100, true); redownload(800, true); redownload(1440, true); redownload(2, true); redownload(1024, true); redownload(10, true); redownload(720, true); redownload(1, true); start = end; } } private static void download(final int startHeight, final int endHeight) { Assert.assertEquals(startHeight, blockchain.getHeight()); downloadTo(endHeight); Logger.logMessage("Successfully downloaded blockchain from " + startHeight + " to " + endHeight); compareTraceFiles(); debugTrace.resetLog(); } private static void rescan(final int numBlocks) { if (numBlocks > Constants.MAX_ROLLBACK) { return; } int endHeight = blockchain.getHeight(); int rescanHeight = endHeight - numBlocks; blockchainProcessor.scan(rescanHeight, true); Assert.assertEquals(endHeight, blockchain.getHeight()); Logger.logMessage("Successfully rescanned blockchain from " + rescanHeight + " to " + endHeight); compareTraceFiles(); debugTrace.resetLog(); } private static void redownload(final int numBlocks, boolean preserveTransactions) { if (numBlocks > Constants.MAX_ROLLBACK) { return; } int endHeight = blockchain.getHeight(); List<List<Long>> allLessorsBefore = new ArrayList<>(); List<List<Long>> allLessorBalancesBefore = new ArrayList<>(); for (long accountId : testLesseeAccounts) { List<Long> lessors = new ArrayList<>(); List<Long> balances = new ArrayList<>(); allLessorsBefore.add(lessors); allLessorBalancesBefore.add(balances); Account account = Account.getAccount(accountId); if (account == null) { continue; } try (DbIterator<Account> iter = account.getLessors(endHeight - numBlocks)) { for (Account lessor : iter) { lessors.add(lessor.getId()); balances.add(lessor.getGuaranteedBalanceNQT(Constants.GUARANTEED_BALANCE_CONFIRMATIONS, endHeight - numBlocks)); } } } List<List<TestAccountAsset>> allAccountAssetsBefore = new ArrayList<>(); for (long assetId : testAssets) { List<TestAccountAsset> accountAssets = new ArrayList<>(); allAccountAssetsBefore.add(accountAssets); Asset asset = Asset.getAsset(assetId); if (asset == null) { continue; } try (DbIterator<Account.AccountAsset> iter = asset.getAccounts(endHeight - numBlocks, 0, -1)) { for (Account.AccountAsset accountAsset : iter) { accountAssets.add(new TestAccountAsset(accountAsset)); } } } List<BlockImpl> poppedBlocks = blockchainProcessor.popOffTo(endHeight - numBlocks); if (preserveTransactions) { for (BlockImpl block : poppedBlocks) { TransactionProcessorImpl.getInstance().processLater(block.getTransactions()); } } Assert.assertEquals(endHeight - numBlocks, blockchain.getHeight()); List<List<Long>> allLessorsAfter = new ArrayList<>(); List<List<Long>> allLessorBalancesAfter = new ArrayList<>(); for (long accountId : testLesseeAccounts) { List<Long> lessors = new ArrayList<>(); List<Long> balances = new ArrayList<>(); allLessorsAfter.add(lessors); allLessorBalancesAfter.add(balances); Account account = Account.getAccount(accountId); if (account == null) { continue; } try (DbIterator<Account> iter = account.getLessors()) { for (Account lessor : iter) { lessors.add(lessor.getId()); balances.add(lessor.getGuaranteedBalanceNQT()); } } } Assert.assertEquals(allLessorsBefore, allLessorsAfter); Assert.assertEquals(allLessorBalancesBefore, allLessorBalancesAfter); List<List<TestAccountAsset>> allAccountAssetsAfter = new ArrayList<>(); for (long assetId : testAssets) { List<TestAccountAsset> accountAssets = new ArrayList<>(); allAccountAssetsAfter.add(accountAssets); Asset asset = Asset.getAsset(assetId); if (asset == null) { continue; } try (DbIterator<Account.AccountAsset> iter = asset.getAccounts(0, -1)) { for (Account.AccountAsset accountAsset : iter) { accountAssets.add(new TestAccountAsset(accountAsset)); } } } Assert.assertEquals(allAccountAssetsBefore, allAccountAssetsAfter); //Logger.logDebugMessage("Assets Before: " + allAccountAssetsBefore); //Logger.logDebugMessage("Assets After: " + allAccountAssetsAfter); downloadTo(endHeight); Logger.logMessage("Successfully redownloaded blockchain from " + (endHeight - numBlocks) + " to " + endHeight); compareTraceFiles(); debugTrace.resetLog(); } private static void compareTraceFiles() { try (BufferedReader defaultReader = new BufferedReader(new FileReader(defaultTraceFile)); BufferedReader testReader = new BufferedReader(new FileReader(testTraceFile))) { defaultReader.readLine(); testReader.readLine(); String testLine = testReader.readLine(); if (testLine == null) { Logger.logMessage("Empty trace file, nothing to compare"); return; } int height = parseHeight(testLine); String defaultLine; while ((defaultLine = defaultReader.readLine()) != null) { if (parseHeight(defaultLine) >= height) { break; } } if (defaultLine == null) { Logger.logMessage("End of default trace file, can't compare further"); return; } int endHeight = height; Assert.assertEquals(defaultLine, testLine); while ((testLine = testReader.readLine()) != null) { defaultLine = defaultReader.readLine(); if (defaultLine == null) { Logger.logMessage("End of default trace file, can't compare further"); return; } endHeight = parseHeight(testLine); Assert.assertEquals(defaultLine, testLine); } if ((defaultLine = defaultReader.readLine()) != null) { Assert.assertTrue(parseHeight(defaultLine) > endHeight); } Logger.logMessage("Comparison with default trace file passed from height " + height + " to " + endHeight); } catch (IOException e) { throw new RuntimeException(e.toString(), e); } } private static int parseHeight(String line) { return Integer.parseInt(line.substring(1, line.indexOf(DebugTrace.SEPARATOR) - 1)); } private static final class TestAccountAsset { private final Account.AccountAsset accountAsset; private TestAccountAsset(Account.AccountAsset accountAsset) { this.accountAsset = accountAsset; } @Override public boolean equals(Object o) { if (! (o instanceof TestAccountAsset)) { return false; } Account.AccountAsset other = ((TestAccountAsset)o).accountAsset; return this.accountAsset.getAccountId() == other.getAccountId() && this.accountAsset.getAssetId() == other.getAssetId() && this.accountAsset.getQuantityQNT() == other.getQuantityQNT(); } @Override public String toString() { return accountAsset.toString(); } } }