/** * Copyright 2012 multibit.org * * Licensed under the MIT license (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://opensource.org/licenses/mit-license.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.multibit.functionaltests; import com.google.bitcoin.core.DumpedPrivateKey; import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.Wallet; import com.google.bitcoin.core.Wallet.BalanceType; import junit.framework.TestCase; import org.junit.Test; import org.multibit.ApplicationDataDirectoryLocator; import org.multibit.Constants; import org.multibit.CreateControllers; import org.multibit.file.FileHandler; import org.multibit.model.bitcoin.WalletData; import org.multibit.model.bitcoin.WalletInfoData; import org.multibit.network.MultiBitService; import org.multibit.network.ReplayManager; import org.multibit.network.ReplayTask; import org.multibit.store.MultiBitWalletVersion; import org.multibit.viewsystem.simple.SimpleViewSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.*; /** * Functional test to check that Mining Coinbase Transactions can be seen. * * See bug report: https://github.com/jim618/multibit/issues/21. * * @author jim * */ public class MiningCoinBaseTransactionsSeenTest extends TestCase { private static final Logger log = LoggerFactory.getLogger(MiningCoinBaseTransactionsSeenTest.class); // The address for this private key is "1GqtGtn4fctXuKxsVzRPSLmYWN1YioLi9y". private static final String MINING_PRIVATE_KEY = "5JDxPrBRghF1EvSBjDigywqfmAjpHPmTJxYtQTYJxJRHLLQA4mG"; private static final String START_OF_REPLAY_PERIOD = "2012-03-03T13:00:00Z"; private static final BigInteger BALANCE_AT_START = BigInteger.ZERO; private SimpleDateFormat formatter; @Test public void testReplayMiningTransaction() throws Exception { // Get the system property runFunctionalTest to see if the functional tests need running. String runFunctionalTests = System.getProperty(Constants.RUN_FUNCTIONAL_TESTS_PARAMETER); if (Boolean.TRUE.toString().equalsIgnoreCase(runFunctionalTests)) { // Date format is UTC with century, T time separator and Z for UTC timezone. formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); File multiBitDirectory = createMultiBitRuntime(); // Set the application data directory to be the one we just created. ApplicationDataDirectoryLocator applicationDataDirectoryLocator = new ApplicationDataDirectoryLocator(multiBitDirectory); log.debug("applicationDataDirectoryLocator = " + applicationDataDirectoryLocator); // Create MultiBit controller. final CreateControllers.Controllers controllers = CreateControllers.createControllers(applicationDataDirectoryLocator); log.debug("Creating Bitcoin service"); // Create the MultiBitService that connects to the bitcoin network. MultiBitService multiBitService = new MultiBitService(controllers.bitcoinController); log.debug("multiBitService = " + multiBitService); controllers.bitcoinController.setMultiBitService(multiBitService); // Add the simple view system (no Swing). SimpleViewSystem simpleViewSystem = new SimpleViewSystem(); controllers.coreController.registerViewSystem(simpleViewSystem); log.debug("simpleViewSystem = " + simpleViewSystem); ReplayManager.INSTANCE.initialise(controllers.bitcoinController, true); // // MultiBit runtime is now setup and running. // String miningWalletPath = multiBitDirectory.getAbsolutePath() + File.separator + "mining.wallet"; // Create a new wallet. Wallet miningWallet = new Wallet(NetworkParameters.prodNet()); // Add in the mining key that has the coinbase transactions. DumpedPrivateKey miningPrivateKey = new DumpedPrivateKey(NetworkParameters.prodNet(), MINING_PRIVATE_KEY); miningWallet.addKey(miningPrivateKey.getKey()); WalletData perWalletModelData = new WalletData(); perWalletModelData.setWalletInfo(new WalletInfoData(miningWalletPath, miningWallet, MultiBitWalletVersion.PROTOBUF)); perWalletModelData.setWallet(miningWallet); perWalletModelData.setWalletFilename(miningWalletPath); perWalletModelData.setWalletDescription("testReplayMiningTransaction test"); // Save the new wallet. controllers.bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, true); // Get the multibitService to load it up and hook it up to the blockchain. controllers.bitcoinController.getMultiBitService().addWalletFromFilename(miningWalletPath); controllers.bitcoinController.getModel().setActiveWalletByFilename(miningWalletPath); log.debug("Mining wallet = \n" + miningWallet.toString()); assertEquals(BALANCE_AT_START, miningWallet.getBalance()); // Wait for a peer connection. log.debug("Waiting for peer connection. . . "); while (!simpleViewSystem.isOnline()) { Thread.sleep(1000); } log.debug("Now online."); log.debug("Replaying blockchain"); //multiBitService.replayBlockChain(formatter.parse(START_OF_REPLAY_PERIOD)); List<WalletData> perWalletModelDataList = new ArrayList<WalletData>(); perWalletModelDataList.add(controllers.bitcoinController.getModel().getActivePerWalletModelData()); ReplayTask replayTask = new ReplayTask(perWalletModelDataList, formatter.parse(START_OF_REPLAY_PERIOD), ReplayTask.UNKNOWN_START_HEIGHT); ReplayManager.INSTANCE.offerReplayTask(replayTask); // Run for a while. log.debug("Twiddling thumbs for 90 seconds ..."); Thread.sleep(90000); log.debug("... 90 seconds later."); // Check new balance on wallet - estimated balance should be at least the // expected (may have later tx too).. log.debug("Mining wallet estimated balance is:\n" + controllers.bitcoinController.getModel().getActiveWallet().getBalance(BalanceType.ESTIMATED).toString()); log.debug("Mining wallet spendable balance is:\n" + controllers.bitcoinController.getModel().getActiveWallet().getBalance().toString()); log.debug("Mining wallet is:\n" + controllers.bitcoinController.getModel().getActiveWallet().toString()); assertTrue("There were no transactions after replay", controllers.bitcoinController.getModel().getActiveWallet().getTransactions(true).size() > 0); // See if the first transaction is a coinbase. miningWallet = controllers.bitcoinController.getModel().getActiveWallet(); Set<Transaction> transactions = miningWallet.getTransactions(true); assertTrue("Transactions are missing", !(transactions == null || transactions.isEmpty())); Transaction transaction = transactions.iterator().next(); assertNotNull("First transaction is null", transaction); System.out.println("First transaction before roundtrip\n" + transaction); assertTrue("The first transaction in the wallet is not a coinbase but it should be", transaction.isCoinBase()); // Force save the wallet, reload it and check the transaction is still coinbase. controllers.bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, true); WalletData rebornPerWalletModelData = controllers.bitcoinController.getFileHandler().loadFromFile(new File(miningWalletPath)); assertNotNull("No reborn perWalletModelData", rebornPerWalletModelData);; assertNotNull("No reborn wallet", rebornPerWalletModelData.getWallet()); Wallet rebornMiningWallet = rebornPerWalletModelData.getWallet(); // See if the first transaction in the reborn wallet is a coinbase. Set<Transaction> rebornTransactions = rebornMiningWallet.getTransactions(true); assertTrue("No reborn transactions", ! (rebornTransactions == null || rebornTransactions.isEmpty())); Transaction rebornTransaction = rebornTransactions.iterator().next(); assertNotNull("No reborn first transaction", rebornTransaction); System.out.println("First transaction after roundtrip\n" + rebornTransaction); assertTrue("The first transaction in the wallet is not a coinbase but it should be", rebornTransaction.isCoinBase()); // Tidy up. multiBitService.getPeerGroup().stop(); controllers.bitcoinController.getFileHandler().deleteWalletAndWalletInfo(controllers.bitcoinController.getModel().getActivePerWalletModelData()); } else { log.debug("Not running functional test: MiningCoinBaseTransactionsSeenTest#testReplayMiningTransaction. Add '-DrunFunctionalTests=true' to run"); } } /** * Create a working, portable runtime of MultiBit in a temporary directory. * * @return the temporary directory the multibit runtime has been created in */ private File createMultiBitRuntime() throws IOException { File multiBitDirectory = FileHandler.createTempDirectory("multibit"); String multiBitDirectoryPath = multiBitDirectory.getAbsolutePath(); System.out.println("Building MultiBit runtime in : " + multiBitDirectory.getAbsolutePath()); // Create an empty multibit.properties. File multibitProperties = new File(multiBitDirectoryPath + File.separator + "multibit.properties"); multibitProperties.createNewFile(); multibitProperties.deleteOnExit(); // Copy in the checkpoints stored in git - this is in source/main/resources/. File multibitCheckpoints = new File(multiBitDirectoryPath + File.separator + "multibit.checkpoints"); FileHandler.copyFile(new File("./src/main/resources/multibit.checkpoints"), multibitCheckpoints); multibitCheckpoints.deleteOnExit(); return multiBitDirectory; } }