From 3cd01fdf0e540c4e06cd27b6c0d6b6abc00767d1 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 9 May 2012 17:24:44 +0000 Subject: [PATCH 1/3] CreateNewBlock: Check that the produced CBlock is acceptable (except for proof-of-work and merkletree, since those need to be provided later) This throws an exception from CreateNewBlock otherwise, which is not safe without #1245! --- src/main.cpp | 43 +++++++++++++++++++++++++++++++------------ src/main.h | 4 ++-- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d795ca1df..48bf0a54e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1252,10 +1252,10 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } -bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) +bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) { // Check it again in case a previous version let a bad block in - if (!CheckBlock()) + if (!CheckBlock(!fJustCheck, !fJustCheck)) return false; // Do not allow blocks that contain transactions which 'overwrite' older transactions, @@ -1283,7 +1283,13 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) bool fStrictPayToScriptHash = (pindex->nTime >= nBIP16SwitchTime); //// issue here: it doesn't know the version - unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); + unsigned int nTxPos; + if (fJustCheck) + // FetchInputs treats CDiskTxPos(1,1,1) as a special "refer to memorypool" indicator + // Since we're just checking the block and not actually connecting it, it might not (and probably shouldn't) be on the disk to get the transaction from + nTxPos = 1; + else + nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); map mapQueuedChanges; int64 nFees = 0; @@ -1295,7 +1301,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return DoS(100, error("ConnectBlock() : too many sigops")); CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); - nTxPos += ::GetSerializeSize(tx, SER_DISK); + if (!fJustCheck) + nTxPos += ::GetSerializeSize(tx, SER_DISK); MapPrevTx mapInputs; if (!tx.IsCoinBase()) @@ -1323,6 +1330,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) mapQueuedChanges[tx.GetHash()] = CTxIndex(posThisTx, tx.vout.size()); } + if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) + return false; + + if (fJustCheck) + return true; + // Write queued txindex changes for (map::iterator mi = mapQueuedChanges.begin(); mi != mapQueuedChanges.end(); ++mi) { @@ -1330,9 +1343,6 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) return error("ConnectBlock() : UpdateTxIndex failed"); } - if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) - return false; - // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. if (pindex->pprev) @@ -1619,7 +1629,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) -bool CBlock::CheckBlock() const +bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot) const { // These are checks that are independent of context // that can be verified before saving an orphan block. @@ -1629,7 +1639,7 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : size limits failed")); // Check proof of work matches claimed amount - if (!CheckProofOfWork(GetHash(), nBits)) + if (fCheckPOW && !CheckProofOfWork(GetHash(), nBits)) return DoS(50, error("CheckBlock() : proof of work failed")); // Check timestamp @@ -1657,7 +1667,7 @@ bool CBlock::CheckBlock() const return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount")); // Check merkleroot - if (hashMerkleRoot != BuildMerkleTree()) + if (fCheckMerkleRoot && hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch")); return true; @@ -3066,6 +3076,9 @@ public: uint64 nLastBlockTx = 0; uint64 nLastBlockSize = 0; +const char* pszDummy = "\0\0"; +CScript scriptDummy(std::vector(pszDummy, pszDummy + sizeof(pszDummy))); + CBlock* CreateNewBlock(CReserveKey& reservekey) { CBlockIndex* pindexPrev = pindexBest; @@ -3224,16 +3237,22 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) nLastBlockSize = nBlockSize; printf("CreateNewBlock(): total size %lu\n", nBlockSize); - } pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); - pblock->hashMerkleRoot = pblock->BuildMerkleTree(); pblock->UpdateTime(pindexPrev); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock.get()); pblock->nNonce = 0; + pblock->vtx[0].vin[0].scriptSig = scriptDummy; + CBlockIndex indexDummy(1, 1, *pblock); + indexDummy.pprev = pindexPrev; + indexDummy.nHeight = pindexPrev->nHeight + 1; + if (!pblock->ConnectBlock(txdb, &indexDummy, true)) + throw std::runtime_error("CreateNewBlock() : ConnectBlock failed"); + } + return pblock.release(); } diff --git a/src/main.h b/src/main.h index 18d5dbdd7..996b727b6 100644 --- a/src/main.h +++ b/src/main.h @@ -1017,11 +1017,11 @@ public: bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); - bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex); + bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false); bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos); - bool CheckBlock() const; + bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true) const; bool AcceptBlock(); private: From 148e107da6f3e0f477e773cc3a3cb882ff53dab4 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 22 May 2012 19:51:13 +0000 Subject: [PATCH 2/3] Run BDB disk-less for test_bitcoin --- src/db.cpp | 53 ++++++++++++++++++++++++++++++++++++--- src/db.h | 3 +++ src/test/test_bitcoin.cpp | 8 +++++- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index e494d28e3..5671993d3 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -42,7 +42,8 @@ void CDBEnv::EnvShutdown() { printf("EnvShutdown exception: %s (%d)\n", e.what(), e.get_errno()); } - DbEnv(0).remove(GetDataDir().string().c_str(), 0); + if (!fMockDb) + DbEnv(0).remove(GetDataDir().string().c_str(), 0); } CDBEnv::CDBEnv() : dbenv(0) @@ -103,12 +104,48 @@ bool CDBEnv::Open(boost::filesystem::path pathEnv_) return error("CDB() : error %d opening database environment", ret); fDbEnvInit = true; + fMockDb = false; return true; } +void CDBEnv::MakeMock() +{ + if (fDbEnvInit) + throw runtime_error("CDBEnv::MakeMock(): already initialized"); + + if (fShutdown) + throw runtime_error("CDBEnv::MakeMock(): during shutdown"); + + printf("CDBEnv::MakeMock()\n"); + + dbenv.set_cachesize(1, 0, 1); + dbenv.set_lg_bsize(10485760*4); + dbenv.set_lg_max(10485760); + dbenv.set_lk_max_locks(10000); + dbenv.set_lk_max_objects(10000); + dbenv.set_flags(DB_AUTO_COMMIT, 1); + dbenv.log_set_config(DB_LOG_IN_MEMORY, 1); + int ret = dbenv.open(NULL, + DB_CREATE | + DB_INIT_LOCK | + DB_INIT_LOG | + DB_INIT_MPOOL | + DB_INIT_TXN | + DB_THREAD | + DB_PRIVATE, + S_IRUSR | S_IWUSR); + if (ret > 0) + throw runtime_error(strprintf("CDBEnv::MakeMock(): error %d opening database environment", ret)); + + fDbEnvInit = true; + fMockDb = true; +} + void CDBEnv::CheckpointLSN(std::string strFile) { dbenv.txn_checkpoint(0, 0, 0); + if (fMockDb) + return; dbenv.lsn_reset(strFile.c_str(), 0); } @@ -138,8 +175,17 @@ CDB::CDB(const char *pszFile, const char* pszMode) : { pdb = new Db(&bitdb.dbenv, 0); + bool fMockDb = bitdb.IsMock(); + if (fMockDb) + { + DbMpoolFile*mpf = pdb->get_mpf(); + ret = mpf->set_flags(DB_MPOOL_NOFILE, 1); + if (ret != 0) + throw runtime_error(strprintf("CDB() : failed to configure for no temp file backing for database %s", pszFile)); + } + ret = pdb->open(NULL, // Txn pointer - pszFile, // Filename + fMockDb ? NULL : pszFile, // Filename "main", // Logical db name DB_BTREE, // Database type nFlags, // Flags @@ -337,7 +383,8 @@ void CDBEnv::Flush(bool fShutdown) dbenv.txn_checkpoint(0, 0, 0); if (!IsChainFile(strFile) || fDetachDB) { printf("%s detach\n", strFile.c_str()); - dbenv.lsn_reset(strFile.c_str(), 0); + if (!fMockDb) + dbenv.lsn_reset(strFile.c_str(), 0); } printf("%s closed\n", strFile.c_str()); mapFileUseCount.erase(mi++); diff --git a/src/db.h b/src/db.h index 1030a40c4..4a08bf10a 100644 --- a/src/db.h +++ b/src/db.h @@ -35,6 +35,7 @@ class CDBEnv private: bool fDetachDB; bool fDbEnvInit; + bool fMockDb; boost::filesystem::path pathEnv; void EnvShutdown(); @@ -47,6 +48,8 @@ public: CDBEnv(); ~CDBEnv(); + void MakeMock(); + bool IsMock() { return fMockDb; }; bool Open(boost::filesystem::path pathEnv_); void Close(); void Flush(bool fShutdown); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 96d63bff9..cae0bb6ba 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -1,6 +1,7 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite #include +#include "db.h" #include "main.h" #include "wallet.h" @@ -14,13 +15,18 @@ struct TestingSetup { TestingSetup() { fPrintToConsole = true; // don't want to write to debug.log file noui_connect(); - pwalletMain = new CWallet(); + bitdb.MakeMock(); + LoadBlockIndex(true); + bool fFirstRun; + pwalletMain = new CWallet("wallet.dat"); + pwalletMain->LoadWallet(fFirstRun); RegisterWallet(pwalletMain); } ~TestingSetup() { delete pwalletMain; pwalletMain = NULL; + bitdb.Flush(true); } }; From 639b61d78e9038f217644c47c4e72306159c9822 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 22 May 2012 21:55:15 +0000 Subject: [PATCH 3/3] Tests for CreateNewBlock --- src/main.cpp | 9 ++ src/main.h | 1 + src/test/miner_tests.cpp | 190 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index adcfb1948..e02cbe5c2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,6 +653,15 @@ bool CTxMemPool::remove(CTransaction &tx) return true; } +void +CTxMemPool::clear() +{ + LOCK(cs); + mapTx.clear(); + mapNextTx.clear(); + ++nTransactionsUpdated; +} + void CTxMemPool::queryHashes(std::vector& vtxid) { vtxid.clear(); diff --git a/src/main.h b/src/main.h index fc8d45992..301badcca 100644 --- a/src/main.h +++ b/src/main.h @@ -1606,6 +1606,7 @@ public: bool fCheckInputs, bool* pfMissingInputs); bool addUnchecked(const uint256& hash, CTransaction &tx); bool remove(CTransaction &tx); + void clear(); void queryHashes(std::vector& vtxid); unsigned long size() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 5712b4a1b..3c6039541 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -1,12 +1,202 @@ #include +#include "init.h" +#include "main.h" #include "uint256.h" #include "util.h" +#include "wallet.h" extern void SHA256Transform(void* pstate, void* pinput, const void* pinit); BOOST_AUTO_TEST_SUITE(miner_tests) +static +struct { + unsigned char extranonce; + unsigned int nonce; +} blockinfo[] = { + {4, 0xa4a3e223}, {2, 0x15c32f9e}, {1, 0x0375b547}, {1, 0x7004a8a5}, + {2, 0xce440296}, {2, 0x52cfe198}, {1, 0x77a72cd0}, {2, 0xbb5d6f84}, + {2, 0x83f30c2c}, {1, 0x48a73d5b}, {1, 0xef7dcd01}, {2, 0x6809c6c4}, + {2, 0x0883ab3c}, {1, 0x087bbbe2}, {2, 0x2104a814}, {2, 0xdffb6daa}, + {1, 0xee8a0a08}, {2, 0xba4237c1}, {1, 0xa70349dc}, {1, 0x344722bb}, + {3, 0xd6294733}, {2, 0xec9f5c94}, {2, 0xca2fbc28}, {1, 0x6ba4f406}, + {2, 0x015d4532}, {1, 0x6e119b7c}, {2, 0x43e8f314}, {2, 0x27962f38}, + {2, 0xb571b51b}, {2, 0xb36bee23}, {2, 0xd17924a8}, {2, 0x6bc212d9}, + {1, 0x630d4948}, {2, 0x9a4c4ebb}, {2, 0x554be537}, {1, 0xd63ddfc7}, + {2, 0xa10acc11}, {1, 0x759a8363}, {2, 0xfb73090d}, {1, 0xe82c6a34}, + {1, 0xe33e92d7}, {3, 0x658ef5cb}, {2, 0xba32ff22}, {5, 0x0227a10c}, + {1, 0xa9a70155}, {5, 0xd096d809}, {1, 0x37176174}, {1, 0x830b8d0f}, + {1, 0xc6e3910e}, {2, 0x823f3ca8}, {1, 0x99850849}, {1, 0x7521fb81}, + {1, 0xaacaabab}, {1, 0xd645a2eb}, {5, 0x7aea1781}, {5, 0x9d6e4b78}, + {1, 0x4ce90fd8}, {1, 0xabdc832d}, {6, 0x4a34f32a}, {2, 0xf2524c1c}, + {2, 0x1bbeb08a}, {1, 0xad47f480}, {1, 0x9f026aeb}, {1, 0x15a95049}, + {2, 0xd1cb95b2}, {2, 0xf84bbda5}, {1, 0x0fa62cd1}, {1, 0xe05f9169}, + {1, 0x78d194a9}, {5, 0x3e38147b}, {5, 0x737ba0d4}, {1, 0x63378e10}, + {1, 0x6d5f91cf}, {2, 0x88612eb8}, {2, 0xe9639484}, {1, 0xb7fabc9d}, + {2, 0x19b01592}, {1, 0x5a90dd31}, {2, 0x5bd7e028}, {2, 0x94d00323}, + {1, 0xa9b9c01a}, {1, 0x3a40de61}, {1, 0x56e7eec7}, {5, 0x859f7ef6}, + {1, 0xfd8e5630}, {1, 0x2b0c9f7f}, {1, 0xba700e26}, {1, 0x7170a408}, + {1, 0x70de86a8}, {1, 0x74d64cd5}, {1, 0x49e738a1}, {2, 0x6910b602}, + {0, 0x643c565f}, {1, 0x54264b3f}, {2, 0x97ea6396}, {2, 0x55174459}, + {2, 0x03e8779a}, {1, 0x98f34d8f}, {1, 0xc07b2b07}, {1, 0xdfe29668}, + {1, 0x3141c7c1}, {1, 0xb3b595f4}, {1, 0x735abf08}, {5, 0x623bfbce}, + {2, 0xd351e722}, {1, 0xf4ca48c9}, {1, 0x5b19c670}, {1, 0xa164bf0e}, + {2, 0xbbbeb305}, {2, 0xfe1c810a}, +}; + +// NOTE: These tests rely on CreateNewBlock doing its own self-validation! +BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) +{ + CReserveKey reservekey(pwalletMain); + CBlock *pblock; + CTransaction tx; + CScript script; + uint256 hash; + + // Simple block creation, nothing special yet: + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + + // We can't make transactions until we have inputs + // Therefore, load 100 blocks :) + std::vectortxFirst; + for (unsigned int i = 0; i < sizeof(blockinfo)/sizeof(*blockinfo); ++i) + { + pblock->nTime = pindexBest->GetMedianTimePast()+1; + pblock->vtx[0].vin[0].scriptSig = CScript(); + pblock->vtx[0].vin[0].scriptSig.push_back(blockinfo[i].extranonce); + pblock->vtx[0].vin[0].scriptSig.push_back(pindexBest->nHeight); + pblock->vtx[0].vout[0].scriptPubKey = CScript(); + if (txFirst.size() < 2) + txFirst.push_back(new CTransaction(pblock->vtx[0])); + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + pblock->nNonce = blockinfo[i].nonce; + assert(ProcessBlock(NULL, pblock)); + pblock->hashPrevBlock = pblock->GetHash(); + } + delete pblock; + + // Just to make sure we can still make simple blocks + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + + // block sigops > limit: 1000 CHECKMULTISIG + 1 + tx.vin.resize(1); + // NOTE: OP_NOP is used to force 20 SigOps for the CHECKMULTISIG + tx.vin[0].scriptSig = CScript() << OP_0 << OP_0 << OP_0 << OP_NOP << OP_CHECKMULTISIG << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vout.resize(1); + tx.vout[0].nValue = 5000000000LL; + for (unsigned int i = 0; i < 1001; ++i) + { + tx.vout[0].nValue -= 1000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // block size > limit + tx.vin[0].scriptSig = CScript(); + // 18 * (520char + DROP) + OP_1 = 9433 bytes + std::vector vchData(520); + for (unsigned int i = 0; i < 18; ++i) + tx.vin[0].scriptSig << vchData << OP_DROP; + tx.vin[0].scriptSig << OP_1; + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vout[0].nValue = 5000000000LL; + for (unsigned int i = 0; i < 128; ++i) + { + tx.vout[0].nValue -= 10000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + } + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // orphan in mempool + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // child with higher priority than parent + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vin[0].prevout.hash = txFirst[1]->GetHash(); + tx.vout[0].nValue = 4900000000LL; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + tx.vin.resize(2); + tx.vin[1].scriptSig = CScript() << OP_1; + tx.vin[1].prevout.hash = txFirst[0]->GetHash(); + tx.vin[1].prevout.n = 0; + tx.vout[0].nValue = 5900000000LL; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // coinbase in mempool + tx.vin.resize(1); + tx.vin[0].prevout.SetNull(); + tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; + tx.vout[0].nValue = 0; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // invalid (pre-p2sh) txn in mempool + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].prevout.n = 0; + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = 4900000000LL; + script = CScript() << OP_0; + tx.vout[0].scriptPubKey.SetDestination(script.GetID()); + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vin[0].prevout.hash = hash; + tx.vin[0].scriptSig = CScript() << (std::vector)script; + tx.vout[0].nValue -= 1000000; + hash = tx.GetHash(); + mempool.addUnchecked(hash,tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // double spend txn pair in mempool + tx.vin[0].prevout.hash = txFirst[0]->GetHash(); + tx.vin[0].scriptSig = CScript() << OP_1; + tx.vout[0].nValue = 4900000000LL; + tx.vout[0].scriptPubKey = CScript() << OP_1; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + tx.vout[0].scriptPubKey = CScript() << OP_2; + hash = tx.GetHash(); + mempool.addUnchecked(hash, tx); + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + mempool.clear(); + + // subsidy changing + int nHeight = pindexBest->nHeight; + pindexBest->nHeight = 209999; + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + pindexBest->nHeight = 210000; + BOOST_CHECK(pblock = CreateNewBlock(reservekey)); + delete pblock; + pindexBest->nHeight = nHeight; +} + BOOST_AUTO_TEST_CASE(sha256transform_equality) { unsigned int pSHA256InitState[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};