Add a bitcoind/bitcoinj testing tool that compares bitcoind's rules

...by creating blocks and then comparing their acceptance by
bitcoind to their acceptance by bitcoinj.
This commit is contained in:
Matt Corallo 2012-07-15 02:05:43 +02:00 committed by Mike Hearn
parent 1f904d28e1
commit 2a33065e31
3 changed files with 253 additions and 0 deletions

46
bitcoind-comparison.patch Normal file
View file

@ -0,0 +1,46 @@
diff --git a/src/main.cpp b/src/main.cpp
index 23c6d98..af42d91 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -28,8 +28,8 @@ CTxMemPool mempool;
unsigned int nTransactionsUpdated = 0;
map<uint256, CBlockIndex*> mapBlockIndex;
-uint256 hashGenesisBlock("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f");
-static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
+uint256 hashGenesisBlock("0x0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206");
+static CBigNum bnProofOfWorkLimit(~uint256(0) >> 1);
CBlockIndex* pindexGenesisBlock = NULL;
int nBestHeight = -1;
CBigNum bnBestChainWork = 0;
@@ -856,7 +856,7 @@ int64 static GetBlockValue(int nHeight, int64 nFees)
int64 nSubsidy = 50 * COIN;
// Subsidy is cut in half every 210000 blocks, which will occur approximately every 4 years
- nSubsidy >>= (nHeight / 210000);
+ nSubsidy >>= (nHeight / 150);
return nSubsidy + nFees;
}
@@ -2040,9 +2043,9 @@ bool LoadBlockIndex(bool fAllowNew)
block.hashPrevBlock = 0;
block.hashMerkleRoot = block.BuildMerkleTree();
block.nVersion = 1;
- block.nTime = 1231006505;
- block.nBits = 0x1d00ffff;
- block.nNonce = 2083236893;
+ block.nTime = 1296688602;
+ block.nBits = 0x207fffff;
+ block.nNonce = 2;
if (fTestNet)
{
@@ -2368,7 +2371,7 @@ bool static AlreadyHave(CTxDB& txdb, const CInv& inv)
// The message start string is designed to be unlikely to occur in normal data.
// The characters are rarely used upper ASCII, not valid as UTF-8, and produce
// a large 4-byte int at any alignment.
-unsigned char pchMessageStart[4] = { 0xf9, 0xbe, 0xb4, 0xd9 };
+unsigned char pchMessageStart[4] = { 0xfa, 0xbf, 0xb5, 0xda };
bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)

View file

@ -0,0 +1,186 @@
/*
* Copyright 2012 Matt Corallo.
*
* 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 com.google.bitcoin.core;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.FullPrunedBlockStore;
import com.google.bitcoin.store.MemoryFullPrunedBlockStore;
import com.google.bitcoin.utils.BriefLogFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* A tool for comparing the blocks which are accepted/rejected by bitcoind/bitcoinj
* It is designed to run as a testnet-in-a-box network between a single bitcoind node and bitcoinj
* It is not an automated unit-test because it requires a bit more set-up...read comments below
*/
public class BitcoindComparisonTool {
private static final Logger log = LoggerFactory.getLogger(BitcoindComparisonTool.class);
private static NetworkParameters params;
private static FullPrunedBlockStore store;
private static FullPrunedBlockChain chain;
private static PeerGroup peers;
private static Sha256Hash bitcoindChainHead;
private static Peer bitcoind;
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
new BitcoindComparisonTool(args.length > 0 ? Integer.parseInt(args[0]) : 8333);
}
public BitcoindComparisonTool(int port) throws Exception {
params = NetworkParameters.testNet2();
/**
* The following have been changed from the default and do not match bitcoind's default.
* In order for this test to work, bitcoind should be recompiled with the same values you see here.
* You can also opt to comment out these lines to use the default, however that will cause this tool to be
* very significantly less efficient and useful (it will likely run forever trying to mine new blocks).
*
* You could also simply use git apply to apply the "bitcoind-comparison.patch" file included with bitcoinj
*/
// bnProofOfWorkLimit set in main.cpp
params.proofOfWorkLimit = new BigInteger("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16);
// Also set hashGenesisBlock to 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206 one line up
// constant (210000) in GetBlockValue (main.cpp)
params.setSubsidyDecreaseBlockCount(150);
// block.nNonce/block.nBits in LoadBlockIndex not the ones under "if (fTestNet)"
params.genesisBlock.setNonce(2);
params.genesisBlock.setDifficultyTarget(0x207fFFFFL);
// Also set block.nTime = 1296688602; in the same block
// Only needs to be set in bitcoinj
params.allowEmptyPeerChains = true;
try {
store = new MemoryFullPrunedBlockStore(params, 10);
chain = new FullPrunedBlockChain(params, store);
} catch (BlockStoreException e) {
e.printStackTrace();
System.exit(1);
}
peers = new PeerGroup(params, chain);
peers.setUserAgent("BlockAcceptanceComparisonTool", "1.0");
// bitcoind MUST be on localhost or we will get banned as a DoSer
peers.addAddress(new PeerAddress(InetAddress.getByName("localhost"), port));
peers.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
super.onPeerConnected(peer, peerCount);
log.info("bitcoind connected");
bitcoind = peer;
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
super.onPeerDisconnected(peer, peerCount);
log.error("bitcoind node disconnected!");
System.exit(1);
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
if (m instanceof HeadersMessage) {
for (Block block : ((HeadersMessage) m).getBlockHeaders())
bitcoindChainHead = block.getHash();
return null;
}
else if (m instanceof Block) {
log.error("bitcoind sent us a block it already had, make sure bitcoind has no blocks!");
System.exit(1);
}
return m;
}
});
bitcoindChainHead = params.genesisBlock.getHash();
// Connect to bitcoind and make sure it has no blocks
peers.start();
peers.setMaxConnections(1);
peers.downloadBlockChain();
while (bitcoind == null)
Thread.sleep(50);
Random rng = new Random();
ArrayList<Sha256Hash> locator = new ArrayList<Sha256Hash>(1);
locator.add(params.genesisBlock.getHash());
Sha256Hash hashTo = new Sha256Hash("0000000000000000000000000000000000000000000000000000000000000000");
// Tests various test cases from FullBlockTestGenerator
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
List<BlockAndValidity> blockList = generator.getBlocksToTest(true);
int differingBlocks = 0;
int invalidBlocks = 0;
for (BlockAndValidity block : blockList) {
boolean threw = false;
try {
if (chain.add(block.block) != block.connects) {
log.error("Block didn't match connects flag on block \"" + block.blockName + "\"");
invalidBlocks++;
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.blockName + "\"");
invalidBlocks++;
} else if (block.connects) {
log.error("Block didn't match connects flag on block \"" + block.blockName + "\"");
invalidBlocks++;
}
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.blockName + "\"");
invalidBlocks++;
} else if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block \"" + block.blockName + "\"");
invalidBlocks++;
}
bitcoind.sendMessage(block.block);
locator.clear();
locator.add(bitcoindChainHead);
bitcoind.sendMessage(new GetHeadersMessage(params, locator, hashTo));
bitcoind.ping().get();
if (!chain.getChainHead().getHeader().getHash().equals(bitcoindChainHead)) {
differingBlocks++;
log.error("bitcoind and bitcoinj acceptance differs on block \"" + block.blockName + "\"");
}
log.info("Block \"" + block.blockName + "\" completed processing");
}
log.info("Done testing.\n" +
"Blocks which were not handled the same between bitcoind/bitcoinj: " + differingBlocks + "\n" +
"Blocks which should/should not have been accepted but weren't/were: " + invalidBlocks);
System.exit(differingBlocks > 0 || invalidBlocks > 0 ? 1 : 0);
}
}

View file

@ -1233,6 +1233,27 @@ public class FullBlockTestGenerator {
b70.solve();
blocks.add(new BlockAndValidity(b70, false, true, b69.getHash(), "b70"));
// Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks)
// -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b71 (21)
// \-> b71 (21)
//
Block b72 = createNextBlock(b69, chainHeadHeight + 20, out21, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b72.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b72.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b72.addTransaction(tx);
}
b72.solve();
Block b71 = new Block(params, b72.bitcoinSerialize());
b71.addTransaction(b72.getTransactions().get(2));
Preconditions.checkState(b71.getHash().equals(b72.getHash()));
blocks.add(new BlockAndValidity(b71, false, true, b69.getHash(), "b71"));
blocks.add(new BlockAndValidity(b72, true, false, b72.getHash(), "b72"));
//TODO: Explicitly address MoneyRange() checks
// (finally) return the created chain