mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
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:
parent
5e0d4a168e
commit
7ca87c078c
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user