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:
parent
5a9bd2d797
commit
6f03669fbd
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
126
core/src/main/java/org/bitcoinj/utils/VersionTally.java
Normal file
126
core/src/main/java/org/bitcoinj/utils/VersionTally.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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());
|
||||
|
107
core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java
Normal file
107
core/src/test/java/org/bitcoinj/utils/VersionTallyTest.java
Normal 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());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user