mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-22 14:22:45 +01:00
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:
parent
1f904d28e1
commit
2a33065e31
3 changed files with 253 additions and 0 deletions
46
bitcoind-comparison.patch
Normal file
46
bitcoind-comparison.patch
Normal 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)
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue