/*
* Copyright 2012 Google Inc.
* Copyright 2012 Matt Corallo.
*
* 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.Transaction.SigHash;
import com.google.devcoin.params.MainNetParams;
import com.google.devcoin.params.UnitTestParams;
import com.google.devcoin.script.Script;
import com.google.devcoin.store.BlockStoreException;
import com.google.devcoin.store.FullPrunedBlockStore;
import com.google.devcoin.store.MemoryFullPrunedBlockStore;
import com.google.devcoin.utils.BlockFileLoader;
import com.google.devcoin.utils.BriefLogFormatter;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import static org.junit.Assert.*;
/**
* We don't do any wallet tests here, we leave that to {@link ChainSplitTest}
*/
public class FullPrunedBlockChainTest {
private static final Logger log = LoggerFactory.getLogger(FullPrunedBlockChainTest.class);
private NetworkParameters params;
private FullPrunedBlockChain chain;
private FullPrunedBlockStore store;
@Before
public void setUp() throws Exception {
BriefLogFormatter.init();
params = new UnitTestParams() {
@Override public int getInterval() {
return 10000;
}
};
}
@Test
public void testGeneratedChain() throws Exception {
// Tests various test cases from FullBlockTestGenerator
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
RuleList blockList = generator.getBlocksToTest(false, false, null);
store = new MemoryFullPrunedBlockStore(params, blockList.maximumReorgBlockCount);
chain = new FullPrunedBlockChain(params, store);
for (Rule rule : blockList.list) {
if (!(rule instanceof BlockAndValidity))
continue;
BlockAndValidity block = (BlockAndValidity) rule;
boolean threw = false;
try {
if (chain.add(block.block) != block.connects) {
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block " + block.ruleName);
throw e;
}
if (block.connects) {
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block " + block.ruleName);
fail();
}
if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block " + block.ruleName);
fail();
}
if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("New block head didn't match the correct height after block " + block.ruleName);
fail();
}
}
}
@Test
public void testFinalizedBlocks() throws Exception {
final int UNDOABLE_BLOCKS_STORED = 10;
store = new MemoryFullPrunedBlockStore(params, UNDOABLE_BLOCKS_STORED);
chain = new FullPrunedBlockChain(params, store);
// Check that we aren't accidentally leaving any references
// to the full StoredUndoableBlock's lying around (ie memory leaks)
ECKey outKey = new ECKey();
// Build some blocks on genesis block to create a spendable output
Block rollingBlock = params.getGenesisBlock().createNextBlockWithCoinbase(outKey.getPubKey());
chain.add(rollingBlock);
TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, rollingBlock.getTransactions().get(0).getHash());
byte[] spendableOutputScriptPubKey = rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes();
for (int i = 1; i < params.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(params);
// Entirely invalid scriptPubKey
t.addOutput(new TransactionOutput(params, 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()));
StoredUndoableBlock storedUndoableBlock = undoBlock.get();
assertNotNull(storedUndoableBlock);
assertNull(storedUndoableBlock.getTransactions());
WeakReference<TransactionOutputChanges> changes = new WeakReference<TransactionOutputChanges>(storedUndoableBlock.getTxOutChanges());
assertNotNull(changes.get());
storedUndoableBlock = null; // Blank the reference so it can be GCd.
// Create a chain longer than UNDOABLE_BLOCKS_STORED
for (int i = 0; i < UNDOABLE_BLOCKS_STORED; i++) {
rollingBlock = rollingBlock.createNextBlock(null);
chain.add(rollingBlock);
}
// Try to get the garbage collector to run
System.gc();
assertNull(undoBlock.get());
assertNull(changes.get());
assertNull(out.get());
}
private void addInputToTransaction(Transaction t, TransactionOutPoint prevOut, byte[] prevOutScriptPubKey, ECKey sigKey) throws ScriptException {
TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut);
t.addInput(input);
Sha256Hash hash = t.hashForSignature(0, prevOutScriptPubKey, SigHash.ALL, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(sigKey.sign(hash).encodeToDER());
bos.write(SigHash.ALL.ordinal() + 1);
byte[] signature = bos.toByteArray();
input.setScriptBytes(Script.createInputScript(signature));
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
@Test
public void testFirst100KBlocks() throws BlockStoreException, VerificationException, PrunedException {
NetworkParameters params = MainNetParams.get();
File blockFile = new File(getClass().getResource("first-100k-blocks.dat").getFile());
BlockFileLoader loader = new BlockFileLoader(params, Arrays.asList(new File[] {blockFile}));
store = new MemoryFullPrunedBlockStore(params, 10);
chain = new FullPrunedBlockChain(params, store);
for (Block block : loader)
chain.add(block);
}
}