3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-15 11:45:51 +00:00

Enforce block version supermajority for BIP 66 onwards.

This commit is contained in:
Ross Nicoll 2015-07-12 08:38:32 +01:00 committed by Andreas Schildbach
parent 5a9bd2d797
commit 6f03669fbd
14 changed files with 395 additions and 28 deletions

View File

@ -35,6 +35,7 @@ import java.util.concurrent.Executor;
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
@ -136,6 +137,8 @@ public abstract class AbstractBlockChain {
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 {
@ -153,6 +156,8 @@ public abstract class AbstractBlockChain {
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);
}
/**
@ -458,13 +463,26 @@ public abstract class AbstractBlockChain {
}
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);

View File

@ -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;
@ -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;
@ -579,7 +585,7 @@ public class Block extends Message {
/** 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;
}
@ -1002,17 +1008,17 @@ public class Block extends Message {
* 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);
@ -1054,12 +1060,12 @@ public class Block extends Message {
@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
@ -1069,7 +1075,7 @@ public class Block extends Message {
@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);
}
/**
@ -1078,7 +1084,7 @@ public class Block extends Message {
*/
@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

View File

@ -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.
@ -112,7 +117,7 @@ public abstract class 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:
@ -445,4 +450,30 @@ public abstract class NetworkParameters {
* 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;
}
}

View File

@ -42,7 +42,6 @@ public class VerificationException extends RuntimeException {
}
}
public static class DuplicatedOutPoint extends VerificationException {
public DuplicatedOutPoint() {
super("Duplicated outpoint");
@ -68,6 +67,14 @@ public class VerificationException extends RuntimeException {
}
}
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");

View File

@ -118,7 +118,7 @@ public abstract class AbstractBitcoinNetParams extends NetworkParameters {
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

View File

@ -28,6 +28,10 @@ import static com.google.common.base.Preconditions.*;
* 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;
@ -42,6 +46,10 @@ public class MainNetParams extends AbstractBitcoinNetParams {
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);

View File

@ -35,6 +35,10 @@ public class RegTestParams extends TestNet2Params {
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

View File

@ -25,6 +25,10 @@ import static com.google.common.base.Preconditions.checkState;
* 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;
@ -48,6 +52,10 @@ public class TestNet2Params extends AbstractBitcoinNetParams {
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;

View File

@ -25,6 +25,10 @@ import java.math.BigInteger;
* {@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;
@ -50,6 +54,10 @@ public class UnitTestParams extends AbstractBitcoinNetParams {
addrSeeds = null;
bip32HeaderPub = 0x043587CF;
bip32HeaderPriv = 0x04358394;
majorityEnforceBlockUpgrade = 3;
majorityRejectBlockOutdated = 4;
majorityWindow = 7;
}
private static UnitTestParams instance;

View File

@ -167,11 +167,12 @@ public class FakeTxBuilder {
}
/** 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);
@ -191,8 +192,9 @@ public class FakeTxBuilder {
}
}
/** 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 {

View File

@ -0,0 +1,126 @@
/*
* Copyright 2015 Ross Nicoll.
*
* 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 org.bitcoinj.utils;
import java.util.Stack;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
/**
* Caching counter for the block versions within a moving window. This class
* is NOT thread safe (as if two threads are trying to use it concurrently,
* there's risk of getting versions out of sequence).
*
* @see org.bitcoinj.core.NetworkParameters#getMajorityWindow()
* @see org.bitcoinj.core.NetworkParameters#getMajorityEnforceBlockUpgrade()
* @see org.bitcoinj.core.NetworkParameters#getMajorityRejectBlockOutdated()
*/
public class VersionTally {
/**
* Cache of version numbers.
*/
private final long[] versionWindow;
/**
* Offset within the version window at which the next version will be
* written.
*/
private int versionWriteHead = 0;
/**
* Number of versions written into the tally. Until this matches the length
* of the version window, we do not have sufficient data to return values.
*/
private int versionsStored = 0;
public VersionTally(final NetworkParameters params) {
versionWindow = new long[params.getMajorityWindow()];
}
/**
* Add a new block version to the tally, and return the count for that version
* within the window.
*
* @param version the block version to add.
*/
public void add(final long version) {
versionWindow[versionWriteHead++] = version;
if (versionWriteHead == versionWindow.length) {
versionWriteHead = 0;
}
versionsStored++;
}
/**
* Get the count for a block version within the window.
*
* @param version the block version to query.
* @return the count for the block version, or null if the window is not yet
* full.
*/
public Integer getCount(final long version) {
if (versionsStored < versionWindow.length) {
return null;
}
int count = 0;
for (int versionIdx = 0; versionIdx < versionWindow.length; versionIdx++) {
if (versionWindow[versionIdx] == version) {
count++;
}
}
return count;
}
/**
* Initialize the version tally from the block store. Note this does not
* search backwards past the start of the block store, so if starting from
* a checkpoint this may not fill the window.
*
* @param blockStore block store to load blocks from.
* @param chainHead current chain tip.
*/
public void initialize(final BlockStore blockStore, final StoredBlock chainHead)
throws BlockStoreException {
StoredBlock versionBlock = chainHead;
final Stack<Long> versions = new Stack<Long>();
// We don't know how many blocks back we can go, so load what we can first
versions.push(versionBlock.getHeader().getVersion());
for (int headOffset = 0; headOffset < versionWindow.length; headOffset++) {
versionBlock = versionBlock.getPrev(blockStore);
if (null == versionBlock) {
break;
}
versions.push(versionBlock.getHeader().getVersion());
}
// Replay the versions into the tally
while (!versions.isEmpty()) {
add(versions.pop());
}
}
/**
* Get the size of the version window.
*/
public int size() {
return versionWindow.length;
}
}

View File

@ -26,8 +26,10 @@ import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.testing.FakeTxBuilder;
import org.bitcoinj.utils.BriefLogFormatter;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.rules.ExpectedException;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.math.BigInteger;
@ -43,6 +45,9 @@ import static org.junit.Assert.*;
// Handling of chain splits/reorgs are in ChainSplitTests.
public class BlockChainTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
private BlockChain testNetChain;
private Wallet wallet;
@ -157,7 +162,7 @@ public class BlockChainTest {
Block prev = unitTestParams.getGenesisBlock();
Utils.setMockClock(System.currentTimeMillis()/1000);
for (int i = 0; i < unitTestParams.getInterval() - 1; i++) {
Block newBlock = prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds());
Block newBlock = prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds());
assertTrue(chain.add(newBlock));
prev = newBlock;
// The fake chain should seem to be "fast" for the purposes of difficulty calculations.
@ -165,13 +170,13 @@ public class BlockChainTest {
}
// Now add another block that has no difficulty adjustment, it should be rejected.
try {
chain.add(prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds()));
chain.add(prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds()));
fail();
} catch (VerificationException e) {
}
// Create a new block with the right difficulty target given our blistering speed relative to the huge amount
// of time it's supposed to take (set in the unit test network parameters).
Block b = prev.createNextBlock(coinbaseTo, Utils.currentTimeSeconds());
Block b = prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds());
b.setDifficultyTarget(0x201fFFFFL);
b.solve();
assertTrue(chain.add(b));
@ -183,7 +188,7 @@ public class BlockChainTest {
assertTrue(testNetChain.add(getBlock1()));
Block b2 = getBlock2();
assertTrue(testNetChain.add(b2));
Block bad = new Block(testNet);
Block bad = new Block(testNet, Block.BLOCK_VERSION_GENESIS);
// Merkle root can be anything here, doesn't matter.
bad.setMerkleRoot(Sha256Hash.wrap("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
// Nonce was just some number that made the hash < difficulty limit set below, it can be anything.
@ -218,6 +223,43 @@ public class BlockChainTest {
// TODO: Test difficulty change is not out of range when a transition period becomes valid.
}
/**
* Test that version 2 blocks are rejected once version 3 blocks are a super
* majority.
*/
@Test
public void badBip66Version() throws Exception {
final BlockStore versionBlockStore = new MemoryBlockStore(unitTestParams);
final BlockChain versionChain = new BlockChain(unitTestParams, versionBlockStore);
// Build a historical chain of version 3 blocks
long timeSeconds = 1231006505;
int blockCount = 0;
FakeTxBuilder.BlockPair chainHead = null;
// Put in just enough v2 blocks to be a minority
for (blockCount = 0; blockCount < (unitTestParams.getMajorityWindow() - unitTestParams.getMajorityRejectBlockOutdated()); blockCount++) {
chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, Block.BLOCK_VERSION_BIP34, timeSeconds);
versionChain.add(chainHead.block);
timeSeconds += 60;
}
// Fill the rest of the window with v3 blocks
for (; blockCount < unitTestParams.getMajorityWindow(); blockCount++) {
chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, Block.BLOCK_VERSION_BIP66, timeSeconds);
versionChain.add(chainHead.block);
timeSeconds += 60;
}
chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, Block.BLOCK_VERSION_BIP34, timeSeconds);
// Trying to add a new v2 block should result in rejection
thrown.expect(VerificationException.BlockVersionOutOfDate.class);
try {
versionChain.add(chainHead.block);
} catch(final VerificationException ex) {
throw (Exception) ex.getCause();
}
}
@Test
public void duplicates() throws Exception {
// Adding a block twice should not have any effect, in particular it should not send the block to the wallet.
@ -343,7 +385,7 @@ public class BlockChainTest {
// Some blocks from the test net.
private static Block getBlock2() throws Exception {
Block b2 = new Block(testNet);
Block b2 = new Block(testNet, Block.BLOCK_VERSION_GENESIS);
b2.setMerkleRoot(Sha256Hash.wrap("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
b2.setNonce(2642058077L);
b2.setTime(1296734343L);
@ -354,7 +396,7 @@ public class BlockChainTest {
}
private static Block getBlock1() throws Exception {
Block b1 = new Block(testNet);
Block b1 = new Block(testNet, Block.BLOCK_VERSION_GENESIS);
b1.setMerkleRoot(Sha256Hash.wrap("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
b1.setNonce(236038445);
b1.setTime(1296734340);

View File

@ -890,7 +890,7 @@ public class FullBlockTestGenerator {
TransactionOutPointWithValue out14 = spendableOutputs.poll();
// A valid block created exactly like b44 to make sure the creation itself works
Block b44 = new Block(params);
Block b44 = new Block(params, Block.BLOCK_VERSION_GENESIS);
byte[] outScriptBytes = ScriptBuilder.createOutputScript(ECKey.fromPublicOnly(coinbaseOutKeyPubKey)).getProgram();
{
b44.setDifficultyTarget(b43.block.getDifficultyTarget());
@ -914,7 +914,7 @@ public class FullBlockTestGenerator {
TransactionOutPointWithValue out15 = spendableOutputs.poll();
// A block with a non-coinbase as the first tx
Block b45 = new Block(params);
Block b45 = new Block(params, Block.BLOCK_VERSION_GENESIS);
{
b45.setDifficultyTarget(b44.getDifficultyTarget());
//b45.addCoinbaseTransaction(pubKey, coinbaseValue);
@ -940,7 +940,7 @@ public class FullBlockTestGenerator {
blocks.add(new BlockAndValidity(b45, false, true, b44.getHash(), chainHeadHeight + 15, "b45"));
// A block with no txn
Block b46 = new Block(params);
Block b46 = new Block(params, Block.BLOCK_VERSION_GENESIS);
{
b46.transactions = new ArrayList<Transaction>();
b46.setDifficultyTarget(b44.getDifficultyTarget());

View File

@ -0,0 +1,107 @@
/*
* Copyright 2015 Ross Nicoll.
*
* 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 org.bitcoinj.utils;
import org.bitcoinj.core.BlockChain;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.testing.FakeTxBuilder;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
public class VersionTallyTest {
private NetworkParameters unitTestParams;
public VersionTallyTest() {
}
@Before
public void setUp() throws Exception {
BriefLogFormatter.initVerbose();
unitTestParams = UnitTestParams.get();
Context context = new Context(unitTestParams);
}
/**
* Verify that the version tally returns null until it collects enough data.
*/
@Test
public void testNullWhileEmpty() {
VersionTally instance = new VersionTally(unitTestParams);
for (int i = 0; i < unitTestParams.getMajorityWindow(); i++) {
assertNull(instance.getCount(1));
instance.add(1);
}
assertEquals(unitTestParams.getMajorityWindow(), instance.getCount(1).intValue());
}
/**
* Verify that the size of the version tally matches the network parameters.
*/
@Test
public void testSize() {
VersionTally instance = new VersionTally(unitTestParams);
assertEquals(unitTestParams.getMajorityWindow(), instance.size());
}
/**
* Verify that version count and substitution works correctly.
*/
@Test
public void testVersionCounts() {
VersionTally instance = new VersionTally(unitTestParams);
// Fill the tally with 1s
for (int i = 0; i < unitTestParams.getMajorityWindow(); i++) {
assertNull(instance.getCount(1));
instance.add(1);
}
assertEquals(unitTestParams.getMajorityWindow(), instance.getCount(1).intValue());
for (int i = 0; i < unitTestParams.getMajorityWindow(); i++) {
assertEquals(unitTestParams.getMajorityWindow() - i, instance.getCount(1).intValue());
assertEquals(i, instance.getCount(2).intValue());
instance.add(2);
}
}
@Test
public void testInitialize() throws BlockStoreException {
final BlockStore blockStore = new MemoryBlockStore(unitTestParams);
final BlockChain chain = new BlockChain(unitTestParams, blockStore);
// Build a historical chain of version 2 blocks
long timeSeconds = 1231006505;
StoredBlock chainHead = null;
for (int blockCount = 0; blockCount < unitTestParams.getMajorityWindow(); blockCount++) {
chainHead = FakeTxBuilder.createFakeBlock(blockStore, 2, timeSeconds).storedBlock;
assertEquals(2, chainHead.getHeader().getVersion());
timeSeconds += 60;
}
VersionTally instance = new VersionTally(unitTestParams);
instance.initialize(blockStore, chainHead);
assertEquals(unitTestParams.getMajorityWindow(), instance.getCount(2).intValue());
}
}