Add block timestamp and transaction finalization checks.

This brings bitcoinj's block connection up to the reference
client's AcceptBlock().
This commit is contained in:
Matt Corallo 2012-07-26 14:32:22 +02:00 committed by Mike Hearn
parent 5e0d4a168e
commit 7ca87c078c
3 changed files with 54 additions and 6 deletions

View File

@ -272,7 +272,7 @@ public abstract class AbstractBlockChain {
// Create a new StoredBlock from this block. It will throw away the transaction data so when block goes
// out of scope we will reclaim the used memory.
checkDifficultyTransitions(storedPrev, block);
connectBlock(block, storedPrev);
connectBlock(block, storedPrev, shouldVerifyTransactions());
}
if (tryConnecting)
@ -282,14 +282,24 @@ public abstract class AbstractBlockChain {
return true;
}
private void connectBlock(Block block, StoredBlock storedPrev)
// expensiveChecks enables checks that require looking at blocks further back in the chain
// than the previous one when connecting (eg median timestamp check)
// It could be exposed, but for now we just set it to shouldVerifyTransactions()
private void connectBlock(Block block, StoredBlock storedPrev, boolean expensiveChecks)
throws BlockStoreException, VerificationException, PrunedException {
// Check that we aren't connecting a block that fails a checkpoint check
if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
throw new VerificationException("Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1));
if (shouldVerifyTransactions())
for (Transaction tx : block.transactions)
if (!tx.isFinal(storedPrev.getHeight() + 1, block.getTimeSeconds()))
throw new VerificationException("Block contains non-final transaction");
StoredBlock head = getChainHead();
if (storedPrev.equals(head)) {
if (expensiveChecks && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head))
throw new VerificationException("Block's timestamp is too early");
// This block connects to the best known block, it is a normal continuation of the system.
TransactionOutputChanges txOutChanges = null;
if (shouldVerifyTransactions())
@ -347,12 +357,26 @@ public abstract class AbstractBlockChain {
sendTransactionsToWallet(newBlock, NewBlockType.SIDE_CHAIN, wallet, block.transactions);
}
}
if (haveNewBestChain)
handleNewBestChain(storedPrev, newBlock, block);
handleNewBestChain(storedPrev, newBlock, block, expensiveChecks);
}
}
/**
* Gets the median timestamp of the last 11 blocks
*/
private long getMedianTimestampOfRecentBlocks(StoredBlock storedBlock) throws BlockStoreException {
long[] timestamps = new long[11];
int unused = 9;
timestamps[10] = storedBlock.getHeader().getTimeSeconds();
while (unused >= 0 && (storedBlock = storedBlock.getPrev(blockStore)) != null)
timestamps[unused--] = storedBlock.getHeader().getTimeSeconds();
Arrays.sort(timestamps, unused+1, 10);
return timestamps[unused + (11-unused)/2];
}
/**
* Disconnect each transaction in the block (after reading it from the block store)
* Only called if(shouldVerifyTransactions())
@ -367,7 +391,7 @@ public abstract class AbstractBlockChain {
* if (shouldVerifyTransactions)
* Either newChainHead needs to be in the block store as a FullStoredBlock, or (block != null && block.transactions != null)
*/
private void handleNewBestChain(StoredBlock storedPrev, StoredBlock newChainHead, Block block)
private void handleNewBestChain(StoredBlock storedPrev, StoredBlock newChainHead, Block block, boolean expensiveChecks)
throws BlockStoreException, VerificationException, PrunedException {
// This chain has overtaken the one we currently believe is best. Reorganize is required.
//
@ -400,6 +424,8 @@ public abstract class AbstractBlockChain {
// Walk in ascending chronological order.
for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext();) {
cursor = it.next();
if (expensiveChecks && cursor.getHeader().getTimeSeconds() <= getMedianTimestampOfRecentBlocks(cursor.getPrev(blockStore)))
throw new VerificationException("Block's timestamp is too early during reorg");
TransactionOutputChanges txOutChanges;
if (cursor != newChainHead || block == null)
txOutChanges = connectTransactions(cursor);

View File

@ -920,7 +920,11 @@ public class Block extends Message {
}
b.setPrevBlockHash(getHash());
b.setTime(time);
// Don't let timestamp go backwards
if (getTimeSeconds() >= time)
b.setTime(getTimeSeconds() + 1);
else
b.setTime(time);
b.solve();
try {
b.verifyHeader();

View File

@ -44,6 +44,9 @@ import static com.google.bitcoin.core.Utils.*;
public class Transaction extends ChildMessage implements Serializable {
private static final Logger log = LoggerFactory.getLogger(Transaction.class);
private static final long serialVersionUID = -8567546957352643140L;
// Threshold for lockTime: below this value it is interpreted as block number, otherwise as timestamp.
static final int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC
// These are serialized in both bitcoin and java serialization.
private long version;
@ -914,4 +917,19 @@ public class Transaction extends ChildMessage implements Serializable {
throw new VerificationException("Coinbase input as input in non-coinbase transaction");
}
}
/**
* Returns true if this transaction is considered finalized and can be placed in a block
*/
public boolean isFinal(int height, long blockTimeSeconds) {
// Time based nLockTime implemented in 0.1.6
if (lockTime == 0)
return true;
if (lockTime < (lockTime < LOCKTIME_THRESHOLD ? height : blockTimeSeconds))
return true;
for (TransactionInput in : inputs)
if (in.hasSequence())
return false;
return true;
}
}