mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-14 19:25:51 +00: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
|
// 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.
|
// out of scope we will reclaim the used memory.
|
||||||
checkDifficultyTransitions(storedPrev, block);
|
checkDifficultyTransitions(storedPrev, block);
|
||||||
connectBlock(block, storedPrev);
|
connectBlock(block, storedPrev, shouldVerifyTransactions());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tryConnecting)
|
if (tryConnecting)
|
||||||
@ -282,14 +282,24 @@ public abstract class AbstractBlockChain {
|
|||||||
return true;
|
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 {
|
throws BlockStoreException, VerificationException, PrunedException {
|
||||||
// Check that we aren't connecting a block that fails a checkpoint check
|
// Check that we aren't connecting a block that fails a checkpoint check
|
||||||
if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
|
if (!params.passesCheckpoint(storedPrev.getHeight() + 1, block.getHash()))
|
||||||
throw new VerificationException("Block failed checkpoint lockin at " + (storedPrev.getHeight() + 1));
|
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();
|
StoredBlock head = getChainHead();
|
||||||
if (storedPrev.equals(head)) {
|
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.
|
// This block connects to the best known block, it is a normal continuation of the system.
|
||||||
TransactionOutputChanges txOutChanges = null;
|
TransactionOutputChanges txOutChanges = null;
|
||||||
if (shouldVerifyTransactions())
|
if (shouldVerifyTransactions())
|
||||||
@ -347,12 +357,26 @@ public abstract class AbstractBlockChain {
|
|||||||
sendTransactionsToWallet(newBlock, NewBlockType.SIDE_CHAIN, wallet, block.transactions);
|
sendTransactionsToWallet(newBlock, NewBlockType.SIDE_CHAIN, wallet, block.transactions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (haveNewBestChain)
|
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)
|
* Disconnect each transaction in the block (after reading it from the block store)
|
||||||
* Only called if(shouldVerifyTransactions())
|
* Only called if(shouldVerifyTransactions())
|
||||||
@ -367,7 +391,7 @@ public abstract class AbstractBlockChain {
|
|||||||
* if (shouldVerifyTransactions)
|
* if (shouldVerifyTransactions)
|
||||||
* Either newChainHead needs to be in the block store as a FullStoredBlock, or (block != null && block.transactions != null)
|
* 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 {
|
throws BlockStoreException, VerificationException, PrunedException {
|
||||||
// This chain has overtaken the one we currently believe is best. Reorganize is required.
|
// 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.
|
// Walk in ascending chronological order.
|
||||||
for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext();) {
|
for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext();) {
|
||||||
cursor = it.next();
|
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;
|
TransactionOutputChanges txOutChanges;
|
||||||
if (cursor != newChainHead || block == null)
|
if (cursor != newChainHead || block == null)
|
||||||
txOutChanges = connectTransactions(cursor);
|
txOutChanges = connectTransactions(cursor);
|
||||||
|
@ -920,7 +920,11 @@ public class Block extends Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.setPrevBlockHash(getHash());
|
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();
|
b.solve();
|
||||||
try {
|
try {
|
||||||
b.verifyHeader();
|
b.verifyHeader();
|
||||||
|
@ -44,6 +44,9 @@ import static com.google.bitcoin.core.Utils.*;
|
|||||||
public class Transaction extends ChildMessage implements Serializable {
|
public class Transaction extends ChildMessage implements Serializable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Transaction.class);
|
private static final Logger log = LoggerFactory.getLogger(Transaction.class);
|
||||||
private static final long serialVersionUID = -8567546957352643140L;
|
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.
|
// These are serialized in both bitcoin and java serialization.
|
||||||
private long version;
|
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");
|
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…
x
Reference in New Issue
Block a user