mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-29 11:01:22 +00:00
First part of block chain handling rework.
- Store the block chain using a get/put interface keyed by hash, so we can add disk storage later. - Add unit tests for difficulty transitions. Move some stuff into NetworkParameters to make that easier. - Track the best chain using total work done. Inform the wallet when a re-org takes place. Wallet currently doesn't do anything with this beyond informing the event listeners. With this patch we're getting closer to a correct SPV implementation.
This commit is contained in:
@@ -18,28 +18,35 @@ package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.bouncycastle.util.encoders.Hex;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
// Tests still to write:
|
||||
// - Rest of checkDifficultyTransitions: verify we don't accept invalid transitions.
|
||||
// - Fragmented chains can be joined together.
|
||||
// - Longest chain is selected based on total difficulty not length.
|
||||
// - Longest testNetChain is selected based on total difficulty not length.
|
||||
// - Many more ...
|
||||
public class BlockChainTest {
|
||||
private static final NetworkParameters params = NetworkParameters.testNet();
|
||||
private static final NetworkParameters testNet = NetworkParameters.testNet();
|
||||
private BlockChain testNetChain;
|
||||
|
||||
private Wallet wallet;
|
||||
private BlockChain chain;
|
||||
private Address coinbaseTo;
|
||||
private NetworkParameters unitTestParams;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Wallet wallet = new Wallet(params);
|
||||
chain = new BlockChain(params, wallet);
|
||||
testNetChain = new BlockChain(testNet, new Wallet(testNet));
|
||||
|
||||
unitTestParams = NetworkParameters.unitTests();
|
||||
wallet = new Wallet(unitTestParams);
|
||||
wallet.addKey(new ECKey());
|
||||
coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams);
|
||||
chain = new BlockChain(unitTestParams, wallet);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -47,7 +54,7 @@ public class BlockChainTest {
|
||||
// Check that we can plug a few blocks together.
|
||||
// Block 1 from the testnet.
|
||||
Block b1 = getBlock1();
|
||||
assertTrue(chain.add(b1));
|
||||
assertTrue(testNetChain.add(b1));
|
||||
// Block 2 from the testnet.
|
||||
Block b2 = getBlock2();
|
||||
|
||||
@@ -55,37 +62,60 @@ public class BlockChainTest {
|
||||
long n = b2.getNonce();
|
||||
try {
|
||||
b2.setNonce(12345);
|
||||
chain.add(b2);
|
||||
testNetChain.add(b2);
|
||||
fail();
|
||||
} catch (VerificationException e) {
|
||||
b2.setNonce(n);
|
||||
}
|
||||
assertTrue(chain.add(b2));
|
||||
// Now it works because we reset the nonce.
|
||||
assertTrue(testNetChain.add(b2));
|
||||
}
|
||||
|
||||
private Block createNextBlock(Address to, Block prev) throws VerificationException {
|
||||
return createNextBlock(to, prev, Block.EASIEST_DIFFICULTY_TARGET, System.currentTimeMillis() / 1000);
|
||||
}
|
||||
|
||||
private Block createNextBlock(Address to, Block prev, long difficultyTarget,
|
||||
long time) throws VerificationException {
|
||||
Block b = new Block(prev.params);
|
||||
b.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
|
||||
b.setDifficultyTarget(difficultyTarget);
|
||||
b.addCoinbaseTransaction(to);
|
||||
b.setPrevBlockHash(prev.getHash());
|
||||
b.setTime(time);
|
||||
b.solve();
|
||||
b.verify();
|
||||
return b;
|
||||
}
|
||||
|
||||
@Test @Ignore
|
||||
public void testForking() throws Exception {
|
||||
// Check that if the block chain forks, we end up using the right one.
|
||||
NetworkParameters unitTestParams = NetworkParameters.unitTests();
|
||||
Wallet wallet = new Wallet(unitTestParams);
|
||||
wallet.addKey(new ECKey());
|
||||
Address coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams);
|
||||
// Start by building a couple of blocks on top of the genesis block.
|
||||
@Test
|
||||
public void testUnconnectedBlocks() throws Exception {
|
||||
Block b1 = createNextBlock(coinbaseTo, unitTestParams.genesisBlock);
|
||||
Block b2 = createNextBlock(coinbaseTo, b1);
|
||||
chain = new BlockChain(unitTestParams, wallet);
|
||||
chain.add(b1);
|
||||
chain.add(b2);
|
||||
Block b3 = createNextBlock(coinbaseTo, b2);
|
||||
// Connected.
|
||||
assertTrue(chain.add(b1));
|
||||
// Unconnected.
|
||||
assertFalse(chain.add(b3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForking() throws Exception {
|
||||
// Check that if the block chain forks, we end up using the right one.
|
||||
// Start by building a couple of blocks on top of the genesis block.
|
||||
final boolean[] flags = new boolean[1];
|
||||
flags[0] = false;
|
||||
wallet.addEventListener(new WalletEventListener() {
|
||||
@Override
|
||||
public void onReorganize() {
|
||||
flags[0] = true;
|
||||
}
|
||||
});
|
||||
|
||||
Block b1 = createNextBlock(coinbaseTo, unitTestParams.genesisBlock);
|
||||
Block b2 = createNextBlock(coinbaseTo, b1);
|
||||
assertTrue(chain.add(b1));
|
||||
assertTrue(chain.add(b2));
|
||||
assertFalse(flags[0]);
|
||||
// We got two blocks which generated 50 coins each, to us.
|
||||
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
// We now have the following chain:
|
||||
@@ -99,33 +129,65 @@ public class BlockChainTest {
|
||||
// Nothing should happen at this point. We saw b2 first so it takes priority.
|
||||
Address someOtherGuy = new ECKey().toAddress(unitTestParams);
|
||||
Block b3 = createNextBlock(someOtherGuy, b1);
|
||||
chain.add(b3);
|
||||
assertTrue(chain.add(b3));
|
||||
assertFalse(flags[0]); // No re-org took place.
|
||||
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
// Now we add another block to make the alternative chain longer.
|
||||
chain.add(createNextBlock(someOtherGuy, b3));
|
||||
assertTrue(chain.add(createNextBlock(someOtherGuy, b3)));
|
||||
assertTrue(flags[0]); // Re-org took place.
|
||||
flags[0] = false;
|
||||
//
|
||||
// genesis -> b1 -> b2
|
||||
// \-> b3 -> b4
|
||||
//
|
||||
// We lost some coins! b2 is no longer a part of the best chain so our balance should drop to 50 again.
|
||||
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
// ... and back to the first chain
|
||||
Block b5 = createNextBlock(coinbaseTo, b2);
|
||||
Block b6 = createNextBlock(coinbaseTo, b5);
|
||||
chain.add(b5);
|
||||
chain.add(b6);
|
||||
//
|
||||
// genesis -> b1 -> b2 -> b5 -> b6
|
||||
// \-> b3 -> b4
|
||||
//
|
||||
assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
if (false) {
|
||||
// These tests do not pass currently, as wallet handling of re-orgs isn't implemented.
|
||||
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
// ... and back to the first testNetChain
|
||||
Block b5 = createNextBlock(coinbaseTo, b2);
|
||||
Block b6 = createNextBlock(coinbaseTo, b5);
|
||||
assertTrue(chain.add(b5));
|
||||
assertTrue(chain.add(b6));
|
||||
//
|
||||
// genesis -> b1 -> b2 -> b5 -> b6
|
||||
// \-> b3 -> b4
|
||||
//
|
||||
assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifficultyTransitions() throws Exception {
|
||||
// Add a bunch of blocks in a loop until we reach a difficulty transition point. The unit test params have an
|
||||
// artificially shortened period.
|
||||
Block prev = unitTestParams.genesisBlock;
|
||||
Block.fakeClock = System.currentTimeMillis() / 1000;
|
||||
for (int i = 0; i < unitTestParams.interval - 1; i++) {
|
||||
Block newBlock = createNextBlock(coinbaseTo, prev, Block.EASIEST_DIFFICULTY_TARGET, Block.fakeClock);
|
||||
assertTrue(chain.add(newBlock));
|
||||
prev = newBlock;
|
||||
// The fake chain should seem to be "fast" for the purposes of difficulty calculations.
|
||||
Block.fakeClock += 2;
|
||||
}
|
||||
// Now add another block that has no difficulty adjustment, it should be rejected.
|
||||
try {
|
||||
chain.add(createNextBlock(coinbaseTo, prev));
|
||||
fail();
|
||||
} catch (VerificationException e) {
|
||||
}
|
||||
// Create a new block with the right difficulty target given our blistering speed relative to the huge amount
|
||||
// of time it's supposed to take (set in the unit test network parameters).
|
||||
Block b = createNextBlock(coinbaseTo, prev, 0x201fFFFFL, Block.fakeClock);
|
||||
assertTrue(chain.add(b));
|
||||
// Successfully traversed a difficulty transition period.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadDifficulty() throws Exception {
|
||||
assertTrue(chain.add(getBlock1()));
|
||||
assertTrue(testNetChain.add(getBlock1()));
|
||||
Block b2 = getBlock2();
|
||||
assertTrue(chain.add(b2));
|
||||
assertTrue(testNetChain.add(b2));
|
||||
NetworkParameters params2 = NetworkParameters.testNet();
|
||||
Block bad = new Block(params2);
|
||||
// Merkle root can be anything here, doesn't matter.
|
||||
@@ -139,7 +201,7 @@ public class BlockChainTest {
|
||||
// solutions.
|
||||
bad.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET);
|
||||
try {
|
||||
chain.add(bad);
|
||||
testNetChain.add(bad);
|
||||
// The difficulty target above should be rejected on the grounds of being easier than the networks
|
||||
// allowable difficulty.
|
||||
fail();
|
||||
@@ -151,7 +213,7 @@ public class BlockChainTest {
|
||||
params2.proofOfWorkLimit = new BigInteger
|
||||
("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
|
||||
try {
|
||||
chain.add(bad);
|
||||
testNetChain.add(bad);
|
||||
// We should not get here as the difficulty target should not be changing at this point.
|
||||
fail();
|
||||
} catch (VerificationException e) {
|
||||
@@ -161,8 +223,9 @@ public class BlockChainTest {
|
||||
// TODO: Test difficulty change is not out of range when a transition period becomes valid.
|
||||
}
|
||||
|
||||
// Some blocks from the test net.
|
||||
private Block getBlock2() throws Exception {
|
||||
Block b2 = new Block(params);
|
||||
Block b2 = new Block(testNet);
|
||||
b2.setMerkleRoot(Hex.decode("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
|
||||
b2.setNonce(2642058077L);
|
||||
b2.setTime(1296734343L);
|
||||
@@ -173,7 +236,7 @@ public class BlockChainTest {
|
||||
}
|
||||
|
||||
private Block getBlock1() throws Exception {
|
||||
Block b1 = new Block(params);
|
||||
Block b1 = new Block(testNet);
|
||||
b1.setMerkleRoot(Hex.decode("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
|
||||
b1.setNonce(236038445);
|
||||
b1.setTime(1296734340);
|
||||
|
Reference in New Issue
Block a user