/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 com.google.devcoin.core;
import com.google.devcoin.core.TransactionConfidence.ConfidenceType;
import com.google.devcoin.params.UnitTestParams;
import com.google.devcoin.store.MemoryBlockStore;
import com.google.devcoin.utils.BriefLogFormatter;
import com.google.devcoin.utils.TestUtils;
import com.google.devcoin.utils.Threading;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class ChainSplitTest {
private static final Logger log = LoggerFactory.getLogger(ChainSplitTest.class);
private NetworkParameters unitTestParams;
private Wallet wallet;
private BlockChain chain;
private Address coinsTo;
private Address coinsTo2;
private Address someOtherGuy;
private MemoryBlockStore blockStore;
@Before
public void setUp() throws Exception {
BriefLogFormatter.init();
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams);
wallet.addKey(new ECKey());
wallet.addKey(new ECKey());
blockStore = new MemoryBlockStore(unitTestParams);
chain = new BlockChain(unitTestParams, wallet, blockStore);
coinsTo = wallet.getKeys().get(0).toAddress(unitTestParams);
coinsTo2 = wallet.getKeys().get(1).toAddress(unitTestParams);
someOtherGuy = new ECKey().toAddress(unitTestParams);
}
@Test
public void testForking1() throws Exception {
// Check that if the block chain forks, we end up using the right chain. Only tests inbound transactions
// (receiving coins). Checking that we understand reversed spends is in testForking2.
final AtomicBoolean reorgHappened = new AtomicBoolean();
final AtomicInteger walletChanged = new AtomicInteger();
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onReorganize(Wallet wallet) {
reorgHappened.set(true);
}
@Override
public void onWalletChanged(Wallet wallet) {
walletChanged.incrementAndGet();
}
});
// Start by building a couple of blocks on top of the genesis block.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
Block b2 = b1.createNextBlock(coinsTo);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
Threading.waitForUserCode();
assertFalse(reorgHappened.get());
assertEquals(2, walletChanged.get());
// We got two blocks which sent 50 coins each to us.
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// We now have the following chain:
// genesis -> b1 -> b2
//
// so fork like this:
//
// genesis -> b1 -> b2
// \-> b3
//
// Nothing should happen at this point. We saw b2 first so it takes priority.
Block b3 = b1.createNextBlock(someOtherGuy);
assertTrue(chain.add(b3));
Threading.waitForUserCode();
assertFalse(reorgHappened.get()); // No re-org took place.
assertEquals(2, walletChanged.get());
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// Check we can handle multi-way splits: this is almost certainly going to be extremely rare, but we have to
// handle it anyway. The same transaction appears in b7/b8 (side chain) but not b2 or b3.
// genesis -> b1--> b2
// |-> b3
// |-> b7 (x)
// \-> b8 (x)
Block b7 = b1.createNextBlock(coinsTo);
assertTrue(chain.add(b7));
Block b8 = b1.createNextBlock(coinsTo);
final Transaction t = b7.getTransactions().get(1);
final Sha256Hash tHash = t.getHash();
b8.addTransaction(t);
b8.solve();
assertTrue(chain.add(roundtrip(b8)));
Threading.waitForUserCode();
assertEquals(2, wallet.getTransaction(tHash).getAppearsInHashes().size());
assertFalse(reorgHappened.get()); // No re-org took place.
assertEquals(5, walletChanged.get());
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// Now we add another block to make the alternative chain longer.
assertTrue(chain.add(b3.createNextBlock(someOtherGuy)));
Threading.waitForUserCode();
assertTrue(reorgHappened.get()); // Re-org took place.
assertEquals(6, walletChanged.get());
reorgHappened.set(false);
//
// genesis -> b1 -> b2
// \-> b3 -> b4
// We lost some coins! b2 is no longer a part of the best chain so our available balance should drop to 50.
// It's now pending reconfirmation.
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// ... and back to the first chain.
Block b5 = b2.createNextBlock(coinsTo);
Block b6 = b5.createNextBlock(coinsTo);
assertTrue(chain.add(b5));
assertTrue(chain.add(b6));
//
// genesis -> b1 -> b2 -> b5 -> b6
// \-> b3 -> b4
//
Threading.waitForUserCode();
assertTrue(reorgHappened.get());
assertEquals(9, walletChanged.get());
assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
@Test
public void testForking2() throws Exception {
// Check that if the chain forks and new coins are received in the alternate chain our balance goes up
// after the re-org takes place.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(someOtherGuy);
Block b2 = b1.createNextBlock(someOtherGuy);
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
// genesis -> b1 -> b2
// \-> b3 -> b4
assertEquals(BigInteger.ZERO, wallet.getBalance());
Block b3 = b1.createNextBlock(coinsTo);
Block b4 = b3.createNextBlock(someOtherGuy);
assertTrue(chain.add(b3));
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertTrue(chain.add(b4));
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
@Test
public void testForking3() throws Exception {
// Check that we can handle our own spends being rolled back by a fork.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
Address dest = new ECKey().toAddress(unitTestParams);
Transaction spend = wallet.createSend(dest, Utils.toNanoCoins(10, 0));
wallet.commitTx(spend);
// Waiting for confirmation ... make it eligible for selection.
assertEquals(BigInteger.ZERO, wallet.getBalance());
spend.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1, 2, 3, 4})));
spend.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{5,6,7,8})));
assertEquals(ConfidenceType.PENDING, spend.getConfidence().getConfidenceType());
assertEquals(Utils.toNanoCoins(40, 0), wallet.getBalance());
Block b2 = b1.createNextBlock(someOtherGuy);
b2.addTransaction(spend);
b2.solve();
chain.add(roundtrip(b2));
// We have 40 coins in change.
assertEquals(ConfidenceType.BUILDING, spend.getConfidence().getConfidenceType());
// genesis -> b1 (receive coins) -> b2 (spend coins)
// \-> b3 -> b4
Block b3 = b1.createNextBlock(someOtherGuy);
Block b4 = b3.createNextBlock(someOtherGuy);
chain.add(b3);
chain.add(b4);
// b4 causes a re-org that should make our spend go pending again.
assertEquals(Utils.toNanoCoins(40, 0), wallet.getBalance());
assertEquals(ConfidenceType.PENDING, spend.getConfidence().getConfidenceType());
}
@Test
public void testForking4() throws Exception {
// Check that we can handle external spends on an inactive chain becoming active. An external spend is where
// we see a transaction that spends our own coins but we did not broadcast it ourselves. This happens when
// keys are being shared between wallets.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
Address dest = new ECKey().toAddress(unitTestParams);
Transaction spend = wallet.createSend(dest, Utils.toNanoCoins(50, 0));
// We do NOT confirm the spend here. That means it's not considered to be pending because createSend is
// stateless. For our purposes it is as if some other program with our keys created the tx.
//
// genesis -> b1 (receive 50) --> b2
// \-> b3 (external spend) -> b4
Block b2 = b1.createNextBlock(someOtherGuy);
chain.add(b2);
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(spend);
b3.solve();
chain.add(roundtrip(b3));
// The external spend is now pending.
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
Transaction tx = wallet.getTransaction(spend.getHash());
assertEquals(ConfidenceType.PENDING, tx.getConfidence().getConfidenceType());
Block b4 = b3.createNextBlock(someOtherGuy);
chain.add(b4);
// The external spend is now active.
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
assertEquals(ConfidenceType.BUILDING, tx.getConfidence().getConfidenceType());
}
@Test
public void testForking5() throws Exception {
// Test the standard case in which a block containing identical transactions appears on a side chain.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
final Transaction t = b1.transactions.get(1);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// genesis -> b1
// -> b2
Block b2 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
Transaction b2coinbase = b2.transactions.get(0);
b2.transactions.clear();
b2.addTransaction(b2coinbase);
b2.addTransaction(t);
b2.solve();
chain.add(roundtrip(b2));
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
assertTrue(wallet.isConsistent());
assertEquals(2, wallet.getTransaction(t.getHash()).getAppearsInHashes().size());
// -> b2 -> b3
Block b3 = b2.createNextBlock(someOtherGuy);
chain.add(b3);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
private Block roundtrip(Block b2) throws ProtocolException {
return new Block(unitTestParams, b2.bitcoinSerialize());
}
@Test
public void testForking6() throws Exception {
// Test the case in which a side chain block contains a tx, and then it appears in the main chain too.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(someOtherGuy);
chain.add(b1);
// genesis -> b1
// -> b2
Block b2 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b2);
assertEquals(BigInteger.ZERO, wallet.getBalance());
// genesis -> b1 -> b3
// -> b2
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(b2.transactions.get(1));
b3.solve();
chain.add(roundtrip(b3));
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
@Test
public void testDoubleSpendOnFork() throws Exception {
// Check what happens when a re-org happens and one of our confirmed transactions becomes invalidated by a
// double spend on the new best chain.
final boolean[] eventCalled = new boolean[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.DEAD)
eventCalled[0] = true;
}
});
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
Transaction t1 = wallet.createSend(someOtherGuy, Utils.toNanoCoins(10, 0));
Address yetAnotherGuy = new ECKey().toAddress(unitTestParams);
Transaction t2 = wallet.createSend(yetAnotherGuy, Utils.toNanoCoins(20, 0));
wallet.commitTx(t1);
// Receive t1 as confirmed by the network.
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b2.addTransaction(t1);
b2.solve();
chain.add(roundtrip(b2));
// Now we make a double spend become active after a re-org.
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
// Should have seen a double spend.
assertTrue(eventCalled[0]);
assertEquals(Utils.toNanoCoins(30, 0), wallet.getBalance());
}
@Test
public void testDoubleSpendOnForkPending() throws Exception {
// Check what happens when a re-org happens and one of our unconfirmed transactions becomes invalidated by a
// double spend on the new best chain.
final Transaction[] eventDead = new Transaction[1];
final Transaction[] eventReplacement = new Transaction[1];
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
super.onTransactionConfidenceChanged(wallet, tx);
if (tx.getConfidence().getConfidenceType() ==
TransactionConfidence.ConfidenceType.DEAD) {
eventDead[0] = tx;
eventReplacement[0] = tx.getConfidence().getOverridingTransaction();
}
}
});
// Start with 50 coins.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
Transaction t1 = checkNotNull(wallet.createSend(someOtherGuy, Utils.toNanoCoins(10, 0)));
Address yetAnotherGuy = new ECKey().toAddress(unitTestParams);
Transaction t2 = checkNotNull(wallet.createSend(yetAnotherGuy, Utils.toNanoCoins(20, 0)));
wallet.commitTx(t1);
// t1 is still pending ...
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b2);
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
assertEquals(Utils.toNanoCoins(40, 0), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
// Now we make a double spend become active after a re-org.
// genesis -> b1 -> b2 [t1 pending]
// \-> b3 (t2) -> b4
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
// Should have seen a double spend against the pending pool.
// genesis -> b1 -> b2 [t1 dead and exited the miners mempools]
// \-> b3 (t2) -> b4
assertEquals(t1, eventDead[0]);
assertEquals(t2, eventReplacement[0]);
assertEquals(Utils.toNanoCoins(30, 0), wallet.getBalance());
// ... and back to our own parallel universe.
Block b5 = b2.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b5);
Block b6 = b5.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b6);
// genesis -> b1 -> b2 -> b5 -> b6 [t1 still dead]
// \-> b3 [t2 resurrected and now pending] -> b4
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
// t2 is pending - resurrected double spends take precedence over our dead transactions (which are in nobodies
// mempool by this point).
t1 = checkNotNull(wallet.getTransaction(t1.getHash()));
t2 = checkNotNull(wallet.getTransaction(t2.getHash()));
assertEquals(ConfidenceType.DEAD, t1.getConfidence().getConfidenceType());
assertEquals(ConfidenceType.PENDING, t2.getConfidence().getConfidenceType());
}
@Test
public void txConfidenceLevels() throws Exception {
// Check that as the chain forks and re-orgs, the confidence data associated with each transaction is
// maintained correctly.
final ArrayList<Transaction> txns = new ArrayList<Transaction>(3);
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
txns.add(tx);
}
});
// Start by building three blocks on top of the genesis block. All send to us.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
BigInteger work1 = b1.getWork();
Block b2 = b1.createNextBlock(coinsTo2);
BigInteger work2 = b2.getWork();
Block b3 = b2.createNextBlock(coinsTo2);
BigInteger work3 = b3.getWork();
assertTrue(chain.add(b1));
assertTrue(chain.add(b2));
assertTrue(chain.add(b3));
Threading.waitForUserCode();
// Check the transaction confidence levels are correct.
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work2).add(work3), txns.get(0).getConfidence().getWorkDone());
assertEquals(work2.add(work3), txns.get(1).getConfidence().getWorkDone());
assertEquals(work3, txns.get(2).getConfidence().getWorkDone());
// We now have the following chain:
// genesis -> b1 -> b2 -> b3
//
// so fork like this:
//
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5
//
// Nothing should happen at this point. We saw b2 and b3 first so it takes priority.
Block b4 = b1.createNextBlock(someOtherGuy);
BigInteger work4 = b4.getWork();
Block b5 = b4.createNextBlock(someOtherGuy);
BigInteger work5 = b5.getWork();
assertTrue(chain.add(b4));
assertTrue(chain.add(b5));
Threading.waitForUserCode();
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(2, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(1, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work2).add(work3), txns.get(0).getConfidence().getWorkDone());
assertEquals(work2.add(work3), txns.get(1).getConfidence().getWorkDone());
assertEquals(work3, txns.get(2).getConfidence().getWorkDone());
// Now we add another block to make the alternative chain longer.
Block b6 = b5.createNextBlock(someOtherGuy);
BigInteger work6 = b6.getWork();
assertTrue(chain.add(b6));
//
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5 -> b6
//
assertEquals(3, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(4, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(work1.add(work4).add(work5).add(work6), txns.get(0).getConfidence().getWorkDone());
// Transaction 1 (in block b2) is now on a side chain, so it goes pending (not see in chain).
assertEquals(ConfidenceType.PENDING, txns.get(1).getConfidence().getConfidenceType());
try {
txns.get(1).getConfidence().getAppearedAtChainHeight();
fail();
} catch (IllegalStateException e) {}
assertEquals(0, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(BigInteger.ZERO, txns.get(1).getConfidence().getWorkDone());
// ... and back to the first chain.
Block b7 = b3.createNextBlock(coinsTo);
BigInteger work7 = b7.getWork();
Block b8 = b7.createNextBlock(coinsTo);
BigInteger work8 = b7.getWork();
assertTrue(chain.add(b7));
assertTrue(chain.add(b8));
//
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
// \-> b4 -> b5 -> b6
//
// This should be enabled, once we figure out the best way to inform the user of how the wallet is changing
// during the re-org.
//assertEquals(5, txns.size());
assertEquals(1, txns.get(0).getConfidence().getAppearedAtChainHeight());
assertEquals(2, txns.get(1).getConfidence().getAppearedAtChainHeight());
assertEquals(3, txns.get(2).getConfidence().getAppearedAtChainHeight());
assertEquals(5, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(4, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(3, txns.get(2).getConfidence().getDepthInBlocks());
BigInteger newWork1 = work1.add(work2).add(work3).add(work7).add(work8);
assertEquals(newWork1, txns.get(0).getConfidence().getWorkDone());
BigInteger newWork2 = work2.add(work3).add(work7).add(work8);
assertEquals(newWork2, txns.get(1).getConfidence().getWorkDone());
BigInteger newWork3 = work3.add(work7).add(work8);
assertEquals(newWork3, txns.get(2).getConfidence().getWorkDone());
assertEquals("250.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// Now add two more blocks that don't send coins to us. Despite being irrelevant the wallet should still update.
Block b9 = b8.createNextBlock(someOtherGuy);
Block b10 = b9.createNextBlock(someOtherGuy);
chain.add(b9);
chain.add(b10);
BigInteger extraWork = b9.getWork().add(b10.getWork());
assertEquals(7, txns.get(0).getConfidence().getDepthInBlocks());
assertEquals(6, txns.get(1).getConfidence().getDepthInBlocks());
assertEquals(5, txns.get(2).getConfidence().getDepthInBlocks());
assertEquals(newWork1.add(extraWork), txns.get(0).getConfidence().getWorkDone());
assertEquals(newWork2.add(extraWork), txns.get(1).getConfidence().getWorkDone());
assertEquals(newWork3.add(extraWork), txns.get(2).getConfidence().getWorkDone());
}
@Test
public void orderingInsideBlock() throws Exception {
// Test that transactions received in the same block have their ordering preserved when reorganising.
// This covers issue 468.
// Receive some money to the wallet.
Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.COIN, coinsTo);
final Block b1 = TestUtils.makeSolvedTestBlock(unitTestParams.genesisBlock, t1);
chain.add(b1);
// Send a couple of payments one after the other (so the second depends on the change output of the first).
wallet.allowSpendingUnconfirmedTransactions();
Transaction t2 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), Utils.CENT));
wallet.commitTx(t2);
Transaction t3 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), Utils.CENT));
wallet.commitTx(t3);
chain.add(TestUtils.makeSolvedTestBlock(b1, t2, t3));
final BigInteger coins0point98 = Utils.COIN.subtract(Utils.CENT).subtract(Utils.CENT);
assertEquals(coins0point98, wallet.getBalance());
// Now round trip the wallet and force a re-org.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
wallet.saveToFileStream(bos);
wallet = Wallet.loadFromFileStream(new ByteArrayInputStream(bos.toByteArray()));
final Block b2 = TestUtils.makeSolvedTestBlock(b1, t2, t3);
final Block b3 = TestUtils.makeSolvedTestBlock(b2);
chain.add(b2);
chain.add(b3);
// And verify that the balance is as expected. Because signatures are currently non-deterministic if the order
// isn't being stored correctly this should fail 50% of the time.
assertEquals(coins0point98, wallet.getBalance());
}
@Test
public void coinbaseDeath() throws Exception {
// Check that a coinbase tx is marked as dead after a reorg rather than pending as normal non-double-spent
// transactions would be. Also check that a dead coinbase on a sidechain is resurrected if the sidechain
// becomes the best chain once more.
final ArrayList<Transaction> txns = new ArrayList<Transaction>(3);
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
txns.add(tx);
}
});
// Start by building three blocks on top of the genesis block.
// The first block contains a normal transaction that spends to coinTo.
// The second block contains a coinbase transaction that spends to coinTo2.
// The third block contains a normal transaction that spends to coinTo.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
Block b2 = b1.createNextBlockWithCoinbase(wallet.getKeys().get(1).getPubKey());
Block b3 = b2.createNextBlock(coinsTo);
log.debug("Adding block b1");
assertTrue(chain.add(b1));
log.debug("Adding block b2");
assertTrue(chain.add(b2));
log.debug("Adding block b3");
assertTrue(chain.add(b3));
// We now have the following chain:
// genesis -> b1 -> b2 -> b3
//
// Check we have seen the three transactions.
Threading.waitForUserCode();
assertEquals(3, txns.size());
// Check the coinbase transaction is building and in the unspent pool only.
assertEquals(ConfidenceType.BUILDING, txns.get(1).getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(txns.get(1).getHash()));
assertTrue(wallet.unspent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.spent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.dead.containsKey(txns.get(1).getHash()));
// Fork like this:
//
// genesis -> b1 -> b2 -> b3
// \-> b4 -> b5 -> b6
//
// The b4/ b5/ b6 is now the best chain
Block b4 = b1.createNextBlock(someOtherGuy);
Block b5 = b4.createNextBlock(someOtherGuy);
Block b6 = b5.createNextBlock(someOtherGuy);
log.debug("Adding block b4");
assertTrue(chain.add(b4));
log.debug("Adding block b5");
assertTrue(chain.add(b5));
log.debug("Adding block b6");
assertTrue(chain.add(b6));
Threading.waitForUserCode();
// Transaction 1 (in block b2) is now on a side chain and should have confidence type of dead and be in the dead pool only
assertEquals(TransactionConfidence.ConfidenceType.DEAD, txns.get(1).getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.unspent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.spent.containsKey(txns.get(1).getHash()));
assertTrue(wallet.dead.containsKey(txns.get(1).getHash()));
// ... and back to the first chain.
Block b7 = b3.createNextBlock(coinsTo);
Block b8 = b7.createNextBlock(coinsTo);
log.debug("Adding block b7");
assertTrue(chain.add(b7));
log.debug("Adding block b8");
assertTrue(chain.add(b8));
Threading.waitForUserCode();
//
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
// \-> b4 -> b5 -> b6
//
// The coinbase transaction should now have confidence type of building once more and in the unspent pool only.
assertEquals(TransactionConfidence.ConfidenceType.BUILDING, txns.get(1).getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(txns.get(1).getHash()));
assertTrue(wallet.unspent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.spent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.dead.containsKey(txns.get(1).getHash()));
// ... make the side chain dominant again.
Block b9 = b6.createNextBlock(coinsTo);
Block b10 = b9.createNextBlock(coinsTo);
log.debug("Adding block b9");
assertTrue(chain.add(b9));
log.debug("Adding block b10");
assertTrue(chain.add(b10));
Threading.waitForUserCode();
//
// genesis -> b1 -> b2 -> b3 -> b7 -> b8
// \-> b4 -> b5 -> b6 -> b9 -> b10
//
// The coinbase transaction should now have the confidence type of dead and be in the dead pool only.
assertEquals(TransactionConfidence.ConfidenceType.DEAD, txns.get(1).getConfidence().getConfidenceType());
assertTrue(!wallet.pending.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.unspent.containsKey(txns.get(1).getHash()));
assertTrue(!wallet.spent.containsKey(txns.get(1).getHash()));
assertTrue(wallet.dead.containsKey(txns.get(1).getHash()));
}
}