Skip to content

Commit

Permalink
Enforce block version supermajority for BIP 66 onwards.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ross Nicoll authored and schildbach committed Jul 28, 2015
1 parent 5a9bd2d commit 6f03669
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 28 deletions.
20 changes: 19 additions & 1 deletion core/src/main/java/org/bitcoinj/core/AbstractBlockChain.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.concurrent.locks.ReentrantLock;

import static com.google.common.base.Preconditions.*;
import org.bitcoinj.utils.VersionTally;

/**
* <p>An AbstractBlockChain holds a series of {@link Block} objects, links them together, and knows how to verify that
Expand Down Expand Up @@ -136,6 +137,8 @@ class OrphanBlock {
private double falsePositiveTrend;
private double previousFalsePositiveRate;

private final VersionTally versionTally;

/** See {@link #AbstractBlockChain(Context, List, BlockStore)} */
public AbstractBlockChain(NetworkParameters params, List<BlockChainListener> listeners,
BlockStore blockStore) throws BlockStoreException {
Expand All @@ -153,6 +156,8 @@ public AbstractBlockChain(Context context, List<BlockChainListener> listeners,
this.params = context.getParams();
this.listeners = new CopyOnWriteArrayList<ListenerRegistration<BlockChainListener>>();
for (BlockChainListener l : listeners) addListener(l, Threading.SAME_THREAD);
this.versionTally = new VersionTally(context.getParams());
this.versionTally.initialize(blockStore, chainHead);
}

/**
Expand Down Expand Up @@ -458,13 +463,26 @@ private void connectBlock(final Block block, StoredBlock storedPrev, boolean exp
}
if (expensiveChecks && block.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(head, blockStore))
throw new VerificationException("Block's timestamp is too early");


// BIP 66: Enforce block version 3 once it's a supermajority of blocks
// NOTE: This requires 1,000 blocks since the last checkpoint (on main
// net, less on test) in order to be applied. It is also limited to
// stopping addition of new v2 blocks to the tip of the chain.
if (block.getVersion() == Block.BLOCK_VERSION_BIP34) {
final Integer count = versionTally.getCount(Block.BLOCK_VERSION_BIP66);
if (count != null
&& count >= params.getMajorityRejectBlockOutdated()) {
throw new VerificationException.BlockVersionOutOfDate(block.getVersion());
}
}

// This block connects to the best known block, it is a normal continuation of the system.
TransactionOutputChanges txOutChanges = null;
if (shouldVerifyTransactions())
txOutChanges = connectTransactions(storedPrev.getHeight() + 1, block);
StoredBlock newStoredBlock = addToBlockStore(storedPrev,
block.transactions == null ? block : block.cloneAsHeader(), txOutChanges);
versionTally.add(block.getVersion());
setChainHead(newStoredBlock);
log.debug("Chain is now {} blocks high, running listeners", newStoredBlock.getHeight());
informListenersForNewBlock(block, NewBlockType.BEST_CHAIN, filteredTxHashList, filteredTxn, newStoredBlock);
Expand Down
30 changes: 18 additions & 12 deletions core/src/main/java/org/bitcoinj/core/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ public class Block extends Message {
/** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */
public static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL;

public static final long BLOCK_VERSION_GENESIS = 1;
/** Block version introduced in BIP 34: Height in coinbase */
public static final long BLOCK_VERSION_BIP34 = 2;
/** Block version introduced in BIP 66: Strict DER signatures */
public static final long BLOCK_VERSION_BIP66 = 3;

// Fields defined as part of the protocol format.
private long version;
private Sha256Hash prevBlockHash;
Expand Down Expand Up @@ -99,10 +105,10 @@ public class Block extends Message {
protected int optimalEncodingMessageSize;

/** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
Block(NetworkParameters params) {
Block(NetworkParameters params, long setVersion) {
super(params);
// Set up a few basic things. We are not complete after this though.
version = 1;
version = setVersion;
difficultyTarget = 0x1d07fff8L;
time = System.currentTimeMillis() / 1000;
prevBlockHash = Sha256Hash.ZERO_HASH;
Expand Down Expand Up @@ -579,7 +585,7 @@ public BigInteger getWork() throws VerificationException {
/** Returns a copy of the block, but without any transactions. */
public Block cloneAsHeader() {
maybeParseHeader();
Block block = new Block(params);
Block block = new Block(params, BLOCK_VERSION_GENESIS);
copyBitcoinHeaderTo(block);
return block;
}
Expand Down Expand Up @@ -1002,17 +1008,17 @@ void addCoinbaseTransaction(byte[] pubKeyTo, Coin value) {
* Returns a solved block that builds on top of this one. This exists for unit tests.
*/
@VisibleForTesting
public Block createNextBlock(Address to, long time) {
return createNextBlock(to, null, time, pubkeyForTesting, FIFTY_COINS);
public Block createNextBlock(Address to, long version, long time) {
return createNextBlock(to, version, null, time, pubkeyForTesting, FIFTY_COINS);
}

/**
* Returns a solved block that builds on top of this one. This exists for unit tests.
* In this variant you can specify a public key (pubkey) for use in generating coinbase blocks.
*/
Block createNextBlock(@Nullable Address to, @Nullable TransactionOutPoint prevOut, long time,
byte[] pubKey, Coin coinbaseValue) {
Block b = new Block(params);
Block createNextBlock(@Nullable Address to, long version, @Nullable TransactionOutPoint prevOut,
long time, byte[] pubKey, Coin coinbaseValue) {
Block b = new Block(params, version);
b.setDifficultyTarget(difficultyTarget);
b.addCoinbaseTransaction(pubKey, coinbaseValue);

Expand Down Expand Up @@ -1054,12 +1060,12 @@ Block createNextBlock(@Nullable Address to, @Nullable TransactionOutPoint prevOu

@VisibleForTesting
public Block createNextBlock(@Nullable Address to, TransactionOutPoint prevOut) {
return createNextBlock(to, prevOut, getTimeSeconds() + 5, pubkeyForTesting, FIFTY_COINS);
return createNextBlock(to, 1, prevOut, getTimeSeconds() + 5, pubkeyForTesting, FIFTY_COINS);
}

@VisibleForTesting
public Block createNextBlock(@Nullable Address to, Coin value) {
return createNextBlock(to, null, getTimeSeconds() + 5, pubkeyForTesting, value);
return createNextBlock(to, 1, null, getTimeSeconds() + 5, pubkeyForTesting, value);
}

@VisibleForTesting
Expand All @@ -1069,7 +1075,7 @@ public Block createNextBlock(@Nullable Address to) {

@VisibleForTesting
public Block createNextBlockWithCoinbase(byte[] pubKey, Coin coinbaseValue) {
return createNextBlock(null, null, Utils.currentTimeSeconds(), pubKey, coinbaseValue);
return createNextBlock(null, 1, null, Utils.currentTimeSeconds(), pubKey, coinbaseValue);
}

/**
Expand All @@ -1078,7 +1084,7 @@ public Block createNextBlockWithCoinbase(byte[] pubKey, Coin coinbaseValue) {
*/
@VisibleForTesting
Block createNextBlockWithCoinbase(byte[] pubKey) {
return createNextBlock(null, null, Utils.currentTimeSeconds(), pubKey, FIFTY_COINS);
return createNextBlock(null, 1, null, Utils.currentTimeSeconds(), pubKey, FIFTY_COINS);
}

@VisibleForTesting
Expand Down
33 changes: 32 additions & 1 deletion core/src/main/java/org/bitcoinj/core/NetworkParameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ public abstract class NetworkParameters {
protected int bip32HeaderPub;
protected int bip32HeaderPriv;

/** Used to check majorities for block version upgrade */
protected int majorityEnforceBlockUpgrade;
protected int majorityRejectBlockOutdated;
protected int majorityWindow;

/**
* See getId(). This may be null for old deserialized wallets. In that case we derive it heuristically
* by looking at the port number.
Expand All @@ -112,7 +117,7 @@ protected NetworkParameters() {
}

private static Block createGenesis(NetworkParameters n) {
Block genesisBlock = new Block(n);
Block genesisBlock = new Block(n, Block.BLOCK_VERSION_GENESIS);
Transaction t = new Transaction(n);
try {
// A script containing the difficulty bits and the following message:
Expand Down Expand Up @@ -445,4 +450,30 @@ public final MessageSerializer getDefaultSerializer() {
* Construct and return a custom serializer.
*/
public abstract BitcoinSerializer getSerializer(boolean parseLazy, boolean parseRetain);

/**
* The number of blocks in the last {@link getMajorityWindow()} blocks
* at which to trigger a notice to the user to upgrade their client, where
* the client does not understand those blocks.
*/
public int getMajorityEnforceBlockUpgrade() {
return majorityEnforceBlockUpgrade;
}

/**
* The number of blocks in the last {@link getMajorityWindow()} blocks
* at which to enforce the requirement that all new blocks are of the
* newer type (i.e. outdated blocks are rejected).
*/
public int getMajorityRejectBlockOutdated() {
return majorityRejectBlockOutdated;
}

/**
* The sampling window from which the version numbers of blocks are taken
* in order to determine if a new block version is now the majority.
*/
public int getMajorityWindow() {
return majorityWindow;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public LargerThanMaxBlockSize() {
}
}


public static class DuplicatedOutPoint extends VerificationException {
public DuplicatedOutPoint() {
super("Duplicated outpoint");
Expand All @@ -68,6 +67,14 @@ public CoinbaseScriptSizeOutOfRange() {
}
}


public static class BlockVersionOutOfDate extends VerificationException {
public BlockVersionOutOfDate(final long version) {
super("Block version #"
+ version + " is outdated.");
}
}

public static class UnexpectedCoinbaseInput extends VerificationException {
public UnexpectedCoinbaseInput() {
super("Coinbase input as input in non-coinbase transaction");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block

if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
newTargetCompact + " vs " + receivedTargetCompact);
Long.toHexString(newTargetCompact) + " vs " + Long.toHexString(receivedTargetCompact));
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/bitcoinj/params/MainNetParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
* Parameters for the main production network on which people trade goods and services.
*/
public class MainNetParams extends AbstractBitcoinNetParams {
public static final int MAINNET_MAJORITY_WINDOW = 1000;
public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = 950;
public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 750;

public MainNetParams() {
super();
interval = INTERVAL;
Expand All @@ -42,6 +46,10 @@ public MainNetParams() {
bip32HeaderPub = 0x0488B21E; //The 4 byte header that serializes in base58 to "xpub".
bip32HeaderPriv = 0x0488ADE4; //The 4 byte header that serializes in base58 to "xprv"

majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MAINNET_MAJORITY_WINDOW;

genesisBlock.setDifficultyTarget(0x1d00ffffL);
genesisBlock.setTime(1231006505L);
genesisBlock.setNonce(2083236893);
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/org/bitcoinj/params/RegTestParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public RegTestParams() {
subsidyDecreaseBlockCount = 150;
port = 18444;
id = ID_REGTEST;

majorityEnforceBlockUpgrade = MainNetParams.MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = MainNetParams.MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = MainNetParams.MAINNET_MAJORITY_WINDOW;
}

@Override
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/bitcoinj/params/TestNet2Params.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
* based on it.
*/
public class TestNet2Params extends AbstractBitcoinNetParams {
public static final int TESTNET_MAJORITY_WINDOW = 100;
public static final int TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED = 75;
public static final int TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 51;

public TestNet2Params() {
super();
id = ID_TESTNET;
Expand All @@ -48,6 +52,10 @@ public TestNet2Params() {
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;

majorityEnforceBlockUpgrade = TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE;
majorityRejectBlockOutdated = TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED;
majorityWindow = TESTNET_MAJORITY_WINDOW;
}

private static TestNet2Params instance;
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/bitcoinj/params/UnitTestParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
* {@link org.bitcoinj.core.Block#solve()} by setting difficulty to the easiest possible.
*/
public class UnitTestParams extends AbstractBitcoinNetParams {
public static final int UNITNET_MAJORITY_WINDOW = 8;
public static final int TESTNET_MAJORITY_REJECT_BLOCK_OUTDATED = 6;
public static final int TESTNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = 4;

// A simple static key/address for re-use in unit tests, to speed things up.
public static ECKey TEST_KEY = new ECKey();
public static Address TEST_ADDRESS;
Expand All @@ -50,6 +54,10 @@ public UnitTestParams() {
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;

majorityEnforceBlockUpgrade = 3;
majorityRejectBlockOutdated = 4;
majorityWindow = 7;
}

private static UnitTestParams instance;
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/java/org/bitcoinj/testing/FakeTxBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,12 @@ public static class BlockPair {
}

/** Emulates receiving a valid block that builds on top of the chain. */
public static BlockPair createFakeBlock(BlockStore blockStore, long timeSeconds, Transaction... transactions) {
public static BlockPair createFakeBlock(BlockStore blockStore, long version, long timeSeconds,
Transaction... transactions) {
try {
Block chainHead = blockStore.getChainHead().getHeader();
Address to = new ECKey().toAddress(chainHead.getParams());
Block b = chainHead.createNextBlock(to, timeSeconds);
Block b = chainHead.createNextBlock(to, version, timeSeconds);
// Coinbase tx was already added.
for (Transaction tx : transactions) {
tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
Expand All @@ -191,8 +192,9 @@ public static BlockPair createFakeBlock(BlockStore blockStore, long timeSeconds,
}
}

/** Emulates receiving a valid block that builds on top of the chain. */
public static BlockPair createFakeBlock(BlockStore blockStore, Transaction... transactions) {
return createFakeBlock(blockStore, Utils.currentTimeSeconds(), transactions);
return createFakeBlock(blockStore, Block.BLOCK_VERSION_GENESIS, Utils.currentTimeSeconds(), transactions);
}

public static Block makeSolvedTestBlock(BlockStore blockStore, Address coinsTo) throws BlockStoreException {
Expand Down

0 comments on commit 6f03669

Please sign in to comment.