mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 07:12:17 +00:00
Use a generic block chain creator for Full Block Chain tests.
This commit is contained in:
parent
9585729398
commit
45b89a1935
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012 Google Inc.
|
* Copyright 2012 Google Inc.
|
||||||
|
* Copyright 2012 Matt Corallo.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,13 +17,20 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.bitcoin.core.Transaction.SigHash;
|
||||||
import com.google.bitcoin.store.FullPrunedBlockStore;
|
import com.google.bitcoin.store.FullPrunedBlockStore;
|
||||||
import com.google.bitcoin.store.MemoryFullPrunedBlockStore;
|
import com.google.bitcoin.store.MemoryFullPrunedBlockStore;
|
||||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import java.lang.ref.WeakReference;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@ -31,17 +39,14 @@ import static org.junit.Assert.*;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class FullPrunedBlockChainTest {
|
public class FullPrunedBlockChainTest {
|
||||||
// The size of spendableOutputs
|
private static final Logger log = LoggerFactory.getLogger(FullPrunedBlockChainTest.class);
|
||||||
private static final int MAX_BLOCK_HEIGHT = 5;
|
|
||||||
|
// The number of undoable blocks to keep around
|
||||||
|
private static final int UNDOABLE_BLOCKS_STORED = 10;
|
||||||
|
|
||||||
private NetworkParameters unitTestParams;
|
private NetworkParameters unitTestParams;
|
||||||
private Wallet wallet;
|
|
||||||
private Address walletAddress;
|
|
||||||
private FullPrunedBlockChain chain;
|
private FullPrunedBlockChain chain;
|
||||||
private FullPrunedBlockStore store;
|
private FullPrunedBlockStore store;
|
||||||
private ECKey someOtherGuyKey;
|
|
||||||
private Block testBase;
|
|
||||||
private TransactionOutPoint[] spendableOutputs;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@ -49,100 +54,42 @@ public class FullPrunedBlockChainTest {
|
|||||||
unitTestParams = NetworkParameters.unitTests();
|
unitTestParams = NetworkParameters.unitTests();
|
||||||
unitTestParams.interval = 10000;
|
unitTestParams.interval = 10000;
|
||||||
|
|
||||||
wallet = new Wallet(unitTestParams);
|
store = new MemoryFullPrunedBlockStore(unitTestParams, UNDOABLE_BLOCKS_STORED);
|
||||||
wallet.addKey(new ECKey());
|
chain = new FullPrunedBlockChain(unitTestParams, store);
|
||||||
walletAddress = wallet.keychain.get(0).toAddress(unitTestParams);
|
|
||||||
|
|
||||||
store = new MemoryFullPrunedBlockStore(unitTestParams, MAX_BLOCK_HEIGHT);
|
|
||||||
chain = new FullPrunedBlockChain(unitTestParams, wallet, store);
|
|
||||||
|
|
||||||
someOtherGuyKey = new ECKey();
|
|
||||||
byte[] someOtherGuyPubKey = someOtherGuyKey.getPubKey();
|
|
||||||
|
|
||||||
spendableOutputs = new TransactionOutPoint[unitTestParams.getSpendableCoinbaseDepth() + MAX_BLOCK_HEIGHT];
|
|
||||||
// Build some blocks on genesis block for later spending
|
|
||||||
// Be lazy to give a simple list of inputs for use, though we could use inputs generated during tests
|
|
||||||
testBase = unitTestParams.genesisBlock.createNextBlockWithCoinbase(someOtherGuyPubKey);
|
|
||||||
chain.add(testBase);
|
|
||||||
spendableOutputs[0] = new TransactionOutPoint(unitTestParams, 0, testBase.getTransactions().get(0).getHash());
|
|
||||||
for (int i = 1; i < unitTestParams.getSpendableCoinbaseDepth() + MAX_BLOCK_HEIGHT; i++) {
|
|
||||||
testBase = testBase.createNextBlockWithCoinbase(someOtherGuyPubKey);
|
|
||||||
chain.add(testBase);
|
|
||||||
spendableOutputs[i] = new TransactionOutPoint(unitTestParams, 0, testBase.getTransactions().get(0).getHash().duplicate());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testForkSpends() throws Exception {
|
public void testGeneratedChain() throws Exception {
|
||||||
// Check that if the block chain forks, we end up using the right chain.
|
// Tests various test cases from FullBlockTestGenerator
|
||||||
// And check that transactions that get spent on one fork or another
|
FullBlockTestGenerator generator = new FullBlockTestGenerator(unitTestParams);
|
||||||
|
List<BlockAndValidity> blockList = generator.getBlocksToTest(false);
|
||||||
// In order for this to be triggered, the reorg has to effect us,
|
for (BlockAndValidity block : blockList) {
|
||||||
// so use walletAddress when creating new blocks as much as possible
|
boolean threw = false;
|
||||||
final boolean[] reorgHappened = new boolean[1];
|
try {
|
||||||
reorgHappened[0] = false;
|
if (chain.add(block.block) != block.connects) {
|
||||||
wallet.addEventListener(new AbstractWalletEventListener() {
|
log.error("Block didn't match connects flag on block " + block.blockName);
|
||||||
@Override
|
fail();
|
||||||
public void onReorganize(Wallet wallet) {
|
}
|
||||||
reorgHappened[0] = true;
|
} catch (VerificationException e) {
|
||||||
|
threw = true;
|
||||||
|
if (!block.throwsException) {
|
||||||
|
log.error("Block didn't match throws flag on block " + block.blockName);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
if (block.connects) {
|
||||||
|
log.error("Block didn't match connects flag on block " + block.blockName);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!threw && block.throwsException) {
|
||||||
|
log.error("Block didn't match throws flag on block " + block.blockName);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
|
||||||
|
log.error("New block head didn't match the correct value after block " + block.blockName);
|
||||||
|
fail();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Start by building a couple of blocks on top of the testBase block.
|
|
||||||
Block b1 = testBase.createNextBlock(walletAddress, spendableOutputs[0]);
|
|
||||||
Block b2 = b1.createNextBlock(walletAddress, spendableOutputs[1]);
|
|
||||||
assertTrue(chain.add(b1));
|
|
||||||
assertTrue(chain.add(b2));
|
|
||||||
assertFalse(reorgHappened[0]);
|
|
||||||
// We now have the following chain (which output is spent is in parentheses):
|
|
||||||
// testBase -> b1 (0) -> b2 (1)
|
|
||||||
//
|
|
||||||
// so fork like this:
|
|
||||||
//
|
|
||||||
// testBase -> b1 (0) -> b2 (1)
|
|
||||||
// \-> b3 (1)
|
|
||||||
//
|
|
||||||
// Nothing should happen at this point. We saw b2 first so it takes priority.
|
|
||||||
Block b3 = b1.createNextBlock(walletAddress, spendableOutputs[1]);
|
|
||||||
assertTrue(chain.add(b3));
|
|
||||||
assertFalse(reorgHappened[0]); // No re-org took place.
|
|
||||||
// Now we add another block to make the alternative chain longer.
|
|
||||||
Block b4 = b3.createNextBlock(walletAddress, spendableOutputs[2]);
|
|
||||||
assertTrue(chain.add(b4));
|
|
||||||
assertTrue(reorgHappened[0]); // Re-org took place.
|
|
||||||
reorgHappened[0] = false;
|
|
||||||
//
|
|
||||||
// testBase -> b1 (0) -> b2 (1)
|
|
||||||
// \-> b3 (1) -> b4 (2)
|
|
||||||
//
|
|
||||||
// ... and back to the first chain.
|
|
||||||
Block b5 = b2.createNextBlock(walletAddress, spendableOutputs[2]);
|
|
||||||
Block b6 = b5.createNextBlock(walletAddress, spendableOutputs[3]);
|
|
||||||
assertTrue(chain.add(b5));
|
|
||||||
assertTrue(chain.add(b6));
|
|
||||||
//
|
|
||||||
// testBase -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
|
|
||||||
// \-> b3 (1) -> b4 (2)
|
|
||||||
//
|
|
||||||
assertTrue(reorgHappened[0]);
|
|
||||||
reorgHappened[0] = false;
|
|
||||||
// Try to create a fork that double-spends
|
|
||||||
// testBase -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
|
|
||||||
// \-> b3 (1) -> b4 (2)
|
|
||||||
// \-> b7 (2) -> b8 (4)
|
|
||||||
//
|
|
||||||
Block b7 = b4.createNextBlock(new ECKey().toAddress(unitTestParams), spendableOutputs[2]);
|
|
||||||
Block b8 = b7.createNextBlock(walletAddress, spendableOutputs[4]);
|
|
||||||
try{
|
|
||||||
chain.add(b7); // This is allowed to fail as there is no guarantee that a fork's inputs will be verified
|
|
||||||
chain.add(b8);
|
|
||||||
fail();
|
|
||||||
} catch(VerificationException e) {
|
|
||||||
// b7 should fail verification because it double-spends output 2.
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e); // Should not happen.
|
|
||||||
}
|
}
|
||||||
assertFalse(reorgHappened[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -150,18 +97,38 @@ public class FullPrunedBlockChainTest {
|
|||||||
// Check that we aren't accidentally leaving any references
|
// Check that we aren't accidentally leaving any references
|
||||||
// to the full StoredUndoableBlock's lying around (ie memory leaks)
|
// to the full StoredUndoableBlock's lying around (ie memory leaks)
|
||||||
|
|
||||||
WeakReference<StoredTransactionOutput> out =
|
ECKey outKey = new ECKey();
|
||||||
new WeakReference<StoredTransactionOutput>(store.getTransactionOutput(spendableOutputs[0].getHash(), spendableOutputs[1].getIndex()));
|
|
||||||
// Create a chain longer than MAX_BLOCK_HEIGHT
|
// Build some blocks on genesis block to create a spendable output
|
||||||
Block block1 = testBase.createNextBlock(walletAddress, spendableOutputs[0]);
|
Block rollingBlock = unitTestParams.genesisBlock.createNextBlockWithCoinbase(outKey.getPubKey());
|
||||||
chain.add(block1);
|
chain.add(rollingBlock);
|
||||||
WeakReference<StoredUndoableBlock> undoBlock = new WeakReference<StoredUndoableBlock>(store.getUndoBlock(block1.getHash()));
|
TransactionOutPoint spendableOutput = new TransactionOutPoint(unitTestParams, 0, rollingBlock.getTransactions().get(0).getHash());
|
||||||
|
byte[] spendableOutputScriptPubKey = rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes();
|
||||||
|
for (int i = 1; i < unitTestParams.getSpendableCoinbaseDepth(); i++) {
|
||||||
|
rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey());
|
||||||
|
chain.add(rollingBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
WeakReference<StoredTransactionOutput> out = new WeakReference<StoredTransactionOutput>
|
||||||
|
(store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex()));
|
||||||
|
rollingBlock = rollingBlock.createNextBlock(null);
|
||||||
|
|
||||||
|
Transaction t = new Transaction(unitTestParams);
|
||||||
|
// Entirely invalid scriptPubKey
|
||||||
|
t.addOutput(new TransactionOutput(unitTestParams, t, Utils.toNanoCoins(50, 0), new byte[] {}));
|
||||||
|
addInputToTransaction(t, spendableOutput, spendableOutputScriptPubKey, outKey);
|
||||||
|
rollingBlock.addTransaction(t);
|
||||||
|
rollingBlock.solve();
|
||||||
|
|
||||||
|
chain.add(rollingBlock);
|
||||||
|
WeakReference<StoredUndoableBlock> undoBlock = new WeakReference<StoredUndoableBlock>(store.getUndoBlock(rollingBlock.getHash()));
|
||||||
assertTrue(undoBlock.get() != null);
|
assertTrue(undoBlock.get() != null);
|
||||||
assertTrue(undoBlock.get().getTransactions() == null);
|
assertTrue(undoBlock.get().getTransactions() == null);
|
||||||
WeakReference<TransactionOutputChanges> changes = new WeakReference<TransactionOutputChanges>(undoBlock.get().getTxOutChanges());
|
WeakReference<TransactionOutputChanges> changes = new WeakReference<TransactionOutputChanges>(undoBlock.get().getTxOutChanges());
|
||||||
assertTrue(changes.get() != null);
|
assertTrue(changes.get() != null);
|
||||||
Block rollingBlock = block1;
|
|
||||||
for (int i = 0; i < MAX_BLOCK_HEIGHT; i++) {
|
// Create a chain longer than UNDOABLE_BLOCKS_STORED
|
||||||
|
for (int i = 0; i < UNDOABLE_BLOCKS_STORED; i++) {
|
||||||
rollingBlock = rollingBlock.createNextBlock(null);
|
rollingBlock = rollingBlock.createNextBlock(null);
|
||||||
chain.add(rollingBlock);
|
chain.add(rollingBlock);
|
||||||
}
|
}
|
||||||
@ -171,4 +138,23 @@ public class FullPrunedBlockChainTest {
|
|||||||
assertTrue(changes.get() == null);
|
assertTrue(changes.get() == null);
|
||||||
assertTrue(out.get() == null);
|
assertTrue(out.get() == null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void addInputToTransaction(Transaction t, TransactionOutPoint prevOut, byte[] prevOutScriptPubKey, ECKey sigKey) {
|
||||||
|
TransactionInput input = new TransactionInput(unitTestParams, t, new byte[]{}, prevOut);
|
||||||
|
t.addInput(input);
|
||||||
|
|
||||||
|
Sha256Hash hash = t.hashTransactionForSignature(0, prevOutScriptPubKey, SigHash.ALL, false);
|
||||||
|
|
||||||
|
// Sign input
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
|
||||||
|
bos.write(sigKey.sign(hash.getBytes()));
|
||||||
|
bos.write(SigHash.ALL.ordinal() + 1);
|
||||||
|
byte[] signature = bos.toByteArray();
|
||||||
|
|
||||||
|
input.setScriptBytes(Script.createInputScript(signature));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e); // Cannot happen.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user