From 450cbb0944cd20a06ce806e6679a1f4c83c50db2 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 1 Jul 2012 18:54:00 +0200 Subject: [PATCH] Ultraprune This switches bitcoin's transaction/block verification logic to use a "coin database", which contains all unredeemed transaction output scripts, amounts and heights. The name ultraprune comes from the fact that instead of a full transaction index, we only (need to) keep an index with unspent outputs. For now, the blocks themselves are kept as usual, although they are only necessary for serving, rescanning and reorganizing. The basic datastructures are CCoins (representing the coins of a single transaction), and CCoinsView (representing a state of the coins database). There are several implementations for CCoinsView. A dummy, one backed by the coins database (coins.dat), one backed by the memory pool, and one that adds a cache on top of it. FetchInputs, ConnectInputs, ConnectBlock, DisconnectBlock, ... now operate on a generic CCoinsView. The block switching logic now builds a single cached CCoinsView with changes to be committed to the database before any changes are made. This means no uncommitted changes are ever read from the database, and should ease the transition to another database layer which does not support transactions (but does support atomic writes), like LevelDB. For the getrawtransaction() RPC call, access to a txid-to-disk index would be preferable. As this index is not necessary or even useful for any other part of the implementation, it is not provided. Instead, getrawtransaction() uses the coin database to find the block height, and then scans that block to find the requested transaction. This is slow, but should suffice for debug purposes. --- src/db.cpp | 245 ++----- src/db.h | 40 +- src/init.cpp | 8 - src/main.cpp | 1091 ++++++++++++++++---------------- src/main.h | 348 +++++----- src/qt/transactiondesc.cpp | 7 +- src/rpcmining.cpp | 41 +- src/rpcrawtransaction.cpp | 87 ++- src/script.cpp | 5 +- src/script.h | 3 +- src/test/DoS_tests.cpp | 10 +- src/test/script_P2SH_tests.cpp | 26 +- src/test/transaction_tests.cpp | 30 +- src/wallet.cpp | 63 +- src/wallet.h | 7 +- 15 files changed, 887 insertions(+), 1124 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index 53be48cb0..0a6ba3fb3 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -244,7 +244,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) : ret = pdb->open(NULL, // Txn pointer fMockDb ? NULL : pszFile, // Filename - "main", // Logical db name + fMockDb ? pszFile : "main", // Logical db name DB_BTREE, // Database type nFlags, // Flags 0); @@ -273,7 +273,7 @@ CDB::CDB(const char *pszFile, const char* pszMode) : static bool IsChainFile(std::string strFile) { - if (strFile == "blkindex.dat") + if (strFile == "coins.dat" || strFile == "chain.dat") return true; return false; @@ -475,111 +475,66 @@ void CDBEnv::Flush(bool fShutdown) // -// CTxDB +// CChainDB and CCoinsDB // -bool CTxDB::ReadTxIndex(uint256 hash, CTxIndex& txindex) -{ +bool CCoinsDB::HaveCoins(uint256 hash) { assert(!fClient); - txindex.SetNull(); - return Read(make_pair(string("tx"), hash), txindex); + return Exists(make_pair('c', hash)); } -bool CTxDB::UpdateTxIndex(uint256 hash, const CTxIndex& txindex) -{ +bool CCoinsDB::ReadCoins(uint256 hash, CCoins &coins) { assert(!fClient); - return Write(make_pair(string("tx"), hash), txindex); -} - -bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight) -{ - assert(!fClient); - - // Add to tx index - uint256 hash = tx.GetHash(); - CTxIndex txindex(pos, tx.vout.size()); - return Write(make_pair(string("tx"), hash), txindex); + return Read(make_pair('c', hash), coins); } -bool CTxDB::EraseTxIndex(const CTransaction& tx) -{ - assert(!fClient); - uint256 hash = tx.GetHash(); - - return Erase(make_pair(string("tx"), hash)); -} - -bool CTxDB::ContainsTx(uint256 hash) -{ +bool CCoinsDB::WriteCoins(uint256 hash, const CCoins &coins) { assert(!fClient); - return Exists(make_pair(string("tx"), hash)); + if (coins.IsPruned()) + return Erase(make_pair('c', hash)); + else + return Write(make_pair('c', hash), coins); } -bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex) +bool CChainDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) { - assert(!fClient); - tx.SetNull(); - if (!ReadTxIndex(hash, txindex)) - return false; - return (tx.ReadFromDisk(txindex.pos)); + return Write(make_pair('b', blockindex.GetBlockHash()), blockindex); } -bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx) +bool CCoinsDB::ReadHashBestChain(uint256& hashBestChain) { - CTxIndex txindex; - return ReadDiskTx(hash, tx, txindex); + return Read('B', hashBestChain); } -bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex) +bool CCoinsDB::WriteHashBestChain(uint256 hashBestChain) { - return ReadDiskTx(outpoint.hash, tx, txindex); + return Write('B', hashBestChain); } -bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx) +bool CChainDB::ReadBestInvalidWork(CBigNum& bnBestInvalidWork) { - CTxIndex txindex; - return ReadDiskTx(outpoint.hash, tx, txindex); + return Read('I', bnBestInvalidWork); } -bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) +bool CChainDB::WriteBestInvalidWork(CBigNum bnBestInvalidWork) { - return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex); + return Write('I', bnBestInvalidWork); } -bool CTxDB::WriteBlockFileInfo(int nFile, const CBlockFileInfo &info) { - return Write(make_pair(string("blockfile"), nFile), info); +bool CChainDB::WriteBlockFileInfo(int nFile, const CBlockFileInfo &info) { + return Write(make_pair('f', nFile), info); } -bool CTxDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { - return Read(make_pair(string("blockfile"), nFile), info); +bool CChainDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { + return Read(make_pair('f', nFile), info); } -bool CTxDB::WriteLastBlockFile(int nFile) { - return Write(string("lastblockfile"), nFile); +bool CChainDB::WriteLastBlockFile(int nFile) { + return Write('l', nFile); } -bool CTxDB::ReadLastBlockFile(int &nFile) { - return Read(string("lastblockfile"), nFile); -} - -bool CTxDB::ReadHashBestChain(uint256& hashBestChain) -{ - return Read(string("hashBestChain"), hashBestChain); -} - -bool CTxDB::WriteHashBestChain(uint256 hashBestChain) -{ - return Write(string("hashBestChain"), hashBestChain); -} - -bool CTxDB::ReadBestInvalidWork(CBigNum& bnBestInvalidWork) -{ - return Read(string("bnBestInvalidWork"), bnBestInvalidWork); -} - -bool CTxDB::WriteBestInvalidWork(CBigNum bnBestInvalidWork) -{ - return Write(string("bnBestInvalidWork"), bnBestInvalidWork); +bool CChainDB::ReadLastBlockFile(int &nFile) { + return Read('l', nFile); } CBlockIndex static * InsertBlockIndex(uint256 hash) @@ -602,9 +557,9 @@ CBlockIndex static * InsertBlockIndex(uint256 hash) return pindexNew; } -bool CTxDB::LoadBlockIndex() +bool LoadBlockIndex(CCoinsDB &coindb, CChainDB &chaindb) { - if (!LoadBlockIndexGuts()) + if (!chaindb.LoadBlockIndexGuts()) return false; if (fRequestShutdown) @@ -626,29 +581,39 @@ bool CTxDB::LoadBlockIndex() } // Load block file info - ReadLastBlockFile(nLastBlockFile); + chaindb.ReadLastBlockFile(nLastBlockFile); printf("LoadBlockIndex(): last block file = %i\n", nLastBlockFile); - if (ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile)) + if (chaindb.ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile)) printf("LoadBlockIndex(): last block file: %s\n", infoLastBlockFile.ToString().c_str()); // Load hashBestChain pointer to end of best chain - if (!ReadHashBestChain(hashBestChain)) + if (!coindb.ReadHashBestChain(hashBestChain)) { if (pindexGenesisBlock == NULL) return true; return error("CTxDB::LoadBlockIndex() : hashBestChain not loaded"); } - if (!mapBlockIndex.count(hashBestChain)) + std::map::iterator it = mapBlockIndex.find(hashBestChain); + if (it == mapBlockIndex.end()) { return error("CTxDB::LoadBlockIndex() : hashBestChain not found in the block index"); - pindexBest = mapBlockIndex[hashBestChain]; - nBestHeight = pindexBest->nHeight; - bnBestChainWork = pindexBest->bnChainWork; - printf("LoadBlockIndex(): hashBestChain=%s height=%d date=%s\n", - hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, - DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); + } else { + // set 'next' pointers in best chain + CBlockIndex *pindex = it->second; + while(pindex != NULL && pindex->pprev != NULL) { + CBlockIndex *pindexPrev = pindex->pprev; + pindexPrev->pnext = pindex; + pindex = pindexPrev; + } + pindexBest = it->second; + nBestHeight = pindexBest->nHeight; + bnBestChainWork = pindexBest->bnChainWork; + } + printf("LoadBlockIndex(): hashBestChain=%s height=%d date=%s\n", + hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, + DateTimeStrFormat("%x %H:%M:%S", pindexBest->GetBlockTime()).c_str()); // Load bnBestInvalidWork, OK if it doesn't exist - ReadBestInvalidWork(bnBestInvalidWork); + chaindb.ReadBestInvalidWork(bnBestInvalidWork); // Verify blocks in the best chain int nCheckLevel = GetArg("-checklevel", 1); @@ -664,7 +629,6 @@ bool CTxDB::LoadBlockIndex() if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth) break; CBlock block; - CDiskBlockPos blockPos = pindex->GetBlockPos(); if (!block.ReadFromDisk(pindex)) return error("LoadBlockIndex() : block.ReadFromDisk failed"); // check level 1: verify block validity @@ -673,98 +637,12 @@ bool CTxDB::LoadBlockIndex() printf("LoadBlockIndex() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); pindexFork = pindex->pprev; } - // check level 2: verify transaction index validity - if (nCheckLevel>1) - { - BOOST_FOREACH(const CTransaction &tx, block.vtx) - { - uint256 hashTx = tx.GetHash(); - CTxIndex txindex; - if (ReadTxIndex(hashTx, txindex)) - { - // check level 3: checker transaction hashes - if (nCheckLevel>2 || blockPos != txindex.pos.blockPos) - { - // either an error or a duplicate transaction - CTransaction txFound; - if (!txFound.ReadFromDisk(txindex.pos)) - { - printf("LoadBlockIndex() : *** cannot read mislocated transaction %s\n", hashTx.ToString().c_str()); - pindexFork = pindex->pprev; - } - else - if (txFound.GetHash() != hashTx) // not a duplicate tx - { - printf("LoadBlockIndex(): *** invalid tx position for %s\n", hashTx.ToString().c_str()); - pindexFork = pindex->pprev; - } - } - // check level 4: check whether spent txouts were spent within the main chain - unsigned int nOutput = 0; - if (nCheckLevel>3) - { - BOOST_FOREACH(const CDiskTxPos &txpos, txindex.vSpent) - { - if (!txpos.IsNull()) - { - // check level 6: check whether spent txouts were spent by a valid transaction that consume them - if (nCheckLevel>5) - { - CTransaction txSpend; - if (!txSpend.ReadFromDisk(txpos)) - { - printf("LoadBlockIndex(): *** cannot read spending transaction of %s:%i from disk\n", hashTx.ToString().c_str(), nOutput); - pindexFork = pindex->pprev; - } - else if (!txSpend.CheckTransaction()) - { - printf("LoadBlockIndex(): *** spending transaction of %s:%i is invalid\n", hashTx.ToString().c_str(), nOutput); - pindexFork = pindex->pprev; - } - else - { - bool fFound = false; - BOOST_FOREACH(const CTxIn &txin, txSpend.vin) - if (txin.prevout.hash == hashTx && txin.prevout.n == nOutput) - fFound = true; - if (!fFound) - { - printf("LoadBlockIndex(): *** spending transaction of %s:%i does not spend it\n", hashTx.ToString().c_str(), nOutput); - pindexFork = pindex->pprev; - } - } - } - } - nOutput++; - } - } - } - // check level 5: check whether all prevouts are marked spent - if (nCheckLevel>4) - { - BOOST_FOREACH(const CTxIn &txin, tx.vin) - { - CTxIndex txindex; - if (ReadTxIndex(txin.prevout.hash, txindex)) - if (txindex.vSpent.size()-1 < txin.prevout.n || txindex.vSpent[txin.prevout.n].IsNull()) - { - printf("LoadBlockIndex(): *** found unspent prevout %s:%i in %s\n", txin.prevout.hash.ToString().c_str(), txin.prevout.n, hashTx.ToString().c_str()); - pindexFork = pindex->pprev; - } - } - } - } - } + // TODO: stronger verifications } if (pindexFork && !fRequestShutdown) { - // Reorg back to the fork - printf("LoadBlockIndex() : *** moving best chain pointer back to block %d\n", pindexFork->nHeight); - CBlock block; - if (!block.ReadFromDisk(pindexFork)) - return error("LoadBlockIndex() : block.ReadFromDisk failed"); - CTxDB txdb; - block.SetBestChain(txdb, pindexFork); + // TODO: reorg back + return error("LoadBlockIndex(): chain database corrupted"); } return true; @@ -772,7 +650,7 @@ bool CTxDB::LoadBlockIndex() -bool CTxDB::LoadBlockIndexGuts() +bool CChainDB::LoadBlockIndexGuts() { // Get database cursor Dbc* pcursor = GetCursor(); @@ -786,7 +664,7 @@ bool CTxDB::LoadBlockIndexGuts() // Read next record CDataStream ssKey(SER_DISK, CLIENT_VERSION); if (fFlags == DB_SET_RANGE) - ssKey << make_pair(string("blockindex"), uint256(0)); + ssKey << make_pair('b', uint256(0)); CDataStream ssValue(SER_DISK, CLIENT_VERSION); int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); fFlags = DB_NEXT; @@ -798,9 +676,9 @@ bool CTxDB::LoadBlockIndexGuts() // Unserialize try { - string strType; - ssKey >> strType; - if (strType == "blockindex" && !fRequestShutdown) + char chType; + ssKey >> chType; + if (chType == 'b' && !fRequestShutdown) { CDiskBlockIndex diskindex; ssValue >> diskindex; @@ -808,7 +686,6 @@ bool CTxDB::LoadBlockIndexGuts() // Construct block index object CBlockIndex* pindexNew = InsertBlockIndex(diskindex.GetBlockHash()); pindexNew->pprev = InsertBlockIndex(diskindex.hashPrev); - pindexNew->pnext = InsertBlockIndex(diskindex.hashNext); pindexNew->nHeight = diskindex.nHeight; pindexNew->pos = diskindex.pos; pindexNew->nUndoPos = diskindex.nUndoPos; diff --git a/src/db.h b/src/db.h index 61b59060a..dd8993d56 100644 --- a/src/db.h +++ b/src/db.h @@ -17,10 +17,8 @@ class CAddress; class CAddrMan; class CBlockLocator; class CDiskBlockIndex; -class CDiskTxPos; class CMasterKey; class COutPoint; -class CTxIndex; class CWallet; class CWalletTx; @@ -316,39 +314,43 @@ public: -/** Access to the transaction database (blkindex.dat) */ -class CTxDB : public CDB +/** Access to the transaction database (coins.dat) */ +class CCoinsDB : public CDB { public: - CTxDB(const char* pszMode="r+") : CDB("blkindex.dat", pszMode) { } + CCoinsDB(const char* pszMode="r+") : CDB("coins.dat", pszMode) { } private: - CTxDB(const CTxDB&); - void operator=(const CTxDB&); + CCoinsDB(const CCoinsDB&); + void operator=(const CCoinsDB&); public: - bool ReadTxIndex(uint256 hash, CTxIndex& txindex); - bool UpdateTxIndex(uint256 hash, const CTxIndex& txindex); - bool AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight); - bool EraseTxIndex(const CTransaction& tx); - bool ContainsTx(uint256 hash); - bool ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex); - bool ReadDiskTx(uint256 hash, CTransaction& tx); - bool ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex); - bool ReadDiskTx(COutPoint outpoint, CTransaction& tx); - bool WriteBlockIndex(const CDiskBlockIndex& blockindex); + bool ReadCoins(uint256 hash, CCoins &coins); + bool WriteCoins(uint256 hash, const CCoins& coins); + bool HaveCoins(uint256 hash); bool ReadHashBestChain(uint256& hashBestChain); bool WriteHashBestChain(uint256 hashBestChain); +}; + +/** Access to the block database (chain.dat) */ +class CChainDB : public CDB +{ +public: + CChainDB(const char* pszMode="r+") : CDB("chain.dat", pszMode) { } +private: + CChainDB(const CChainDB&); + void operator=(const CChainDB&); +public: + bool WriteBlockIndex(const CDiskBlockIndex& blockindex); bool ReadBestInvalidWork(CBigNum& bnBestInvalidWork); bool WriteBestInvalidWork(CBigNum bnBestInvalidWork); bool ReadBlockFileInfo(int nFile, CBlockFileInfo &fileinfo); bool WriteBlockFileInfo(int nFile, const CBlockFileInfo &fileinfo); bool ReadLastBlockFile(int &nFile); bool WriteLastBlockFile(int nFile); - bool LoadBlockIndex(); -private: bool LoadBlockIndexGuts(); }; +bool LoadBlockIndex(CCoinsDB &coinsdb, CChainDB &chaindb); /** Access to the (IP) address database (peers.dat) */ diff --git a/src/init.cpp b/src/init.cpp index 92c752a8f..56108cece 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -638,14 +638,6 @@ bool AppInit2() return InitError(msg); } - if (GetBoolArg("-loadblockindextest")) - { - CTxDB txdb("r"); - txdb.LoadBlockIndex(); - PrintBlockTree(); - return false; - } - uiInterface.InitMessage(_("Loading block index...")); printf("Loading block index...\n"); nStart = GetTimeMillis(); diff --git a/src/main.cpp b/src/main.cpp index 616845e92..ef5f627d9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -158,6 +158,99 @@ void static ResendWalletTransactions() +////////////////////////////////////////////////////////////////////////////// +// +// CCoinsView implementations +// + +bool CCoinsView::GetCoins(uint256 txid, CCoins &coins) { return false; } +bool CCoinsView::SetCoins(uint256 txid, const CCoins &coins) { return false; } +bool CCoinsView::HaveCoins(uint256 txid) { return false; } +CBlockIndex *CCoinsView::GetBestBlock() { return NULL; } +bool CCoinsView::SetBestBlock(CBlockIndex *pindex) { return false; } + +CCoinsViewBacked::CCoinsViewBacked(CCoinsView &viewIn) : base(&viewIn) { } +bool CCoinsViewBacked::GetCoins(uint256 txid, CCoins &coins) { return base->GetCoins(txid, coins); } +bool CCoinsViewBacked::SetCoins(uint256 txid, const CCoins &coins) { return base->SetCoins(txid, coins); } +bool CCoinsViewBacked::HaveCoins(uint256 txid) { return base->HaveCoins(txid); } +CBlockIndex *CCoinsViewBacked::GetBestBlock() { return base->GetBestBlock(); } +bool CCoinsViewBacked::SetBestBlock(CBlockIndex *pindex) { return base->SetBestBlock(pindex); } +void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } + +CCoinsViewDB::CCoinsViewDB(CCoinsDB &dbIn) : db(dbIn) {} +bool CCoinsViewDB::GetCoins(uint256 txid, CCoins &coins) { return db.ReadCoins(txid, coins); } +bool CCoinsViewDB::SetCoins(uint256 txid, const CCoins &coins) { return db.WriteCoins(txid, coins); } +bool CCoinsViewDB::HaveCoins(uint256 txid) { return db.HaveCoins(txid); } +CBlockIndex *CCoinsViewDB::GetBestBlock() { return pindexBest; } +bool CCoinsViewDB::SetBestBlock(CBlockIndex *pindex) { return db.WriteHashBestChain(pindex->GetBlockHash()); } + +CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), pindexTip(NULL) { } + +bool CCoinsViewCache::GetCoins(uint256 txid, CCoins &coins) { + if (cacheCoins.count(txid)) { + coins = cacheCoins[txid]; + return true; + } + if (base->GetCoins(txid, coins)) { + cacheCoins[txid] = coins; + return true; + } + return false; +} + +bool CCoinsViewCache::SetCoins(uint256 txid, const CCoins &coins) { + cacheCoins[txid] = coins; + return true; +} + +bool CCoinsViewCache::HaveCoins(uint256 txid) { + return cacheCoins.count(txid) || base->HaveCoins(txid); +} + +CBlockIndex *CCoinsViewCache::GetBestBlock() { + if (pindexTip == NULL) + pindexTip = base->GetBestBlock(); + return pindexTip; +} + +bool CCoinsViewCache::SetBestBlock(CBlockIndex *pindex) { + pindexTip = pindex; + return true; +} + +bool CCoinsViewCache::Flush() { + for (std::map::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { + if (!base->SetCoins(it->first, it->second)) + return false; + } + if (!base->SetBestBlock(pindexTip)) + return false; + cacheCoins.clear(); + pindexTip = NULL; + return true; +} + +/** CCoinsView that brings transactions from a memorypool into view. + It does not check for spendings by memory pool transactions. */ +CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } + +bool CCoinsViewMemPool::GetCoins(uint256 txid, CCoins &coins) { + if (base->GetCoins(txid, coins)) + return true; + if (mempool.exists(txid)) { + const CTransaction &tx = mempool.lookup(txid); + coins = CCoins(tx, MEMPOOL_HEIGHT); + return true; + } + return false; +} + +bool CCoinsViewMemPool::HaveCoins(uint256 txid) { + return mempool.exists(txid) || base->HaveCoins(txid); +} + + + ////////////////////////////////////////////////////////////////////////////// // // mapOrphanTransactions @@ -237,37 +330,9 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) ////////////////////////////////////////////////////////////////////////////// // -// CTransaction and CTxIndex +// CTransaction // -bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet) -{ - SetNull(); - if (!txdb.ReadTxIndex(prevout.hash, txindexRet)) - return false; - if (!ReadFromDisk(txindexRet.pos)) - return false; - if (prevout.n >= vout.size()) - { - SetNull(); - return false; - } - return true; -} - -bool CTransaction::ReadFromDisk(CTxDB& txdb, COutPoint prevout) -{ - CTxIndex txindex; - return ReadFromDisk(txdb, prevout, txindex); -} - -bool CTransaction::ReadFromDisk(COutPoint prevout) -{ - CTxDB txdb("r"); - CTxIndex txindex; - return ReadFromDisk(txdb, prevout, txindex); -} - bool CTransaction::IsStandard() const { if (nVersion > CTransaction::CURRENT_VERSION) @@ -303,7 +368,7 @@ bool CTransaction::IsStandard() const // expensive-to-check-upon-redemption script like: // DUP CHECKSIG DROP ... repeated 100 times... OP_1 // -bool CTransaction::AreInputsStandard(const MapPrevTx& mapInputs) const +bool CTransaction::AreInputsStandard(CCoinsView& mapInputs) const { if (IsCoinBase()) return true; // Coinbases don't use vin normally @@ -383,17 +448,21 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock) else { CBlock blockTmp; - if (pblock == NULL) - { - // Load the block this tx is in - CTxIndex txindex; - if (!CTxDB("r").ReadTxIndex(GetHash(), txindex)) - return 0; - if (!blockTmp.ReadFromDisk(txindex.pos.blockPos)) - return 0; - pblock = &blockTmp; + + if (pblock == NULL) { + CCoinsDB coinsdb("r"); + CCoins coins; + if (coinsdb.ReadCoins(GetHash(), coins)) { + CBlockIndex *pindex = FindBlockByHeight(coins.nHeight); + if (pindex) { + if (!blockTmp.ReadFromDisk(pindex)) + return 0; + pblock = &blockTmp; + } + } } + if (pblock) { // Update the tx's hashBlock hashBlock = pblock->GetHash(); @@ -411,6 +480,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock) // Fill in merkle branch vMerkleBranch = pblock->GetMerkleBranch(nIndex); + } } // Is the tx in a block that's in the main chain @@ -526,8 +596,20 @@ int64 CTransaction::GetMinFee(unsigned int nBlockSize, bool fAllowFree, return nMinFee; } +void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) +{ + LOCK(cs); + + std::map::iterator it = mapNextTx.lower_bound(COutPoint(hashTx, 0)); -bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, + // iterate over all COutPoints in mapNextTx whose hash equals the provided hashTx + while (it != mapNextTx.end() && it->first.hash == hashTx) { + coins.Spend(it->first.n); // and remove those outputs from coins + it++; + } +} + +bool CTxMemPool::accept(CCoinsDB& coinsdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs) { if (pfMissingInputs) @@ -548,16 +630,13 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, if (!fTestNet && !tx.IsStandard()) return error("CTxMemPool::accept() : nonstandard transaction type"); - // Do we already have it? + // is it already in the memory pool? uint256 hash = tx.GetHash(); { LOCK(cs); if (mapTx.count(hash)) return false; } - if (fCheckInputs) - if (txdb.ContainsTx(hash)) - return false; // Check for conflicts with in-memory transactions CTransaction* ptxOld = NULL; @@ -589,27 +668,32 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, if (fCheckInputs) { - MapPrevTx mapInputs; - map mapUnused; - bool fInvalid = false; - if (!tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) - { - if (fInvalid) - return error("CTxMemPool::accept() : FetchInputs found invalid tx %s", hash.ToString().substr(0,10).c_str()); - if (pfMissingInputs) - *pfMissingInputs = true; + CCoinsViewDB viewDB(coinsdb); + CCoinsViewMemPool viewMemPool(viewDB, mempool); + CCoinsViewCache view(viewMemPool); + + // do we already have it? + if (view.HaveCoins(hash)) return false; + + // do all inputs exist? + BOOST_FOREACH(const CTxIn txin, tx.vin) { + if (!view.HaveCoins(txin.prevout.hash)) { + if (pfMissingInputs) + *pfMissingInputs = true; + return false; + } } // Check for non-standard pay-to-script-hash in inputs - if (!tx.AreInputsStandard(mapInputs) && !fTestNet) + if (!tx.AreInputsStandard(view) && !fTestNet) return error("CTxMemPool::accept() : nonstandard transaction input"); // Note: if you modify this code to accept non-standard transactions, then // you should add code here to check that the transaction does a // reasonable number of ECDSA signature verifications. - int64 nFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); + int64 nFees = tx.GetValueIn(view)-tx.GetValueOut(); unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block @@ -646,7 +730,7 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!tx.ConnectInputs(mapInputs, mapUnused, CDiskTxPos(true), pindexBest, false, false)) + if (!tx.CheckInputs(view, CS_ALWAYS, true, false)) { return error("CTxMemPool::accept() : ConnectInputs failed %s", hash.ToString().substr(0,10).c_str()); } @@ -674,9 +758,9 @@ bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, return true; } -bool CTransaction::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) +bool CTransaction::AcceptToMemoryPool(CCoinsDB& coinsdb, bool fCheckInputs, bool* pfMissingInputs) { - return mempool.accept(txdb, *this, fCheckInputs, pfMissingInputs); + return mempool.accept(coinsdb, *this, fCheckInputs, pfMissingInputs); } bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx) @@ -765,29 +849,29 @@ int CMerkleTx::GetBlocksToMaturity() const } -bool CMerkleTx::AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs) +bool CMerkleTx::AcceptToMemoryPool(CCoinsDB& coinsdb, bool fCheckInputs) { if (fClient) { - if (!IsInMainChain() && !ClientConnectInputs()) + if (!IsInMainChain() && !ClientCheckInputs()) return false; - return CTransaction::AcceptToMemoryPool(txdb, false); + return CTransaction::AcceptToMemoryPool(coinsdb, false); } else { - return CTransaction::AcceptToMemoryPool(txdb, fCheckInputs); + return CTransaction::AcceptToMemoryPool(coinsdb, fCheckInputs); } } bool CMerkleTx::AcceptToMemoryPool() { - CTxDB txdb("r"); - return AcceptToMemoryPool(txdb); + CCoinsDB coinsdb("r"); + return AcceptToMemoryPool(coinsdb); } -bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) +bool CWalletTx::AcceptWalletTransaction(CCoinsDB& coinsdb, bool fCheckInputs) { { @@ -798,64 +882,65 @@ bool CWalletTx::AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs) if (!tx.IsCoinBase()) { uint256 hash = tx.GetHash(); - if (!mempool.exists(hash) && !txdb.ContainsTx(hash)) - tx.AcceptToMemoryPool(txdb, fCheckInputs); + if (!mempool.exists(hash) && !coinsdb.HaveCoins(hash)) + tx.AcceptToMemoryPool(coinsdb, fCheckInputs); } } - return AcceptToMemoryPool(txdb, fCheckInputs); + return AcceptToMemoryPool(coinsdb, fCheckInputs); } return false; } bool CWalletTx::AcceptWalletTransaction() { - CTxDB txdb("r"); - return AcceptWalletTransaction(txdb); -} - -int CTxIndex::GetDepthInMainChain() const -{ - // Read block header - CBlock block; - if (!block.ReadFromDisk(pos.blockPos, false)) - return 0; - // Find the block in the index - map::iterator mi = mapBlockIndex.find(block.GetHash()); - if (mi == mapBlockIndex.end()) - return 0; - CBlockIndex* pindex = (*mi).second; - if (!pindex || !pindex->IsInMainChain()) - return 0; - return 1 + nBestHeight - pindex->nHeight; + CCoinsDB coinsdb("r"); + return AcceptWalletTransaction(coinsdb); } // Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock -bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock) +bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) { + CBlockIndex *pindexSlow = NULL; { LOCK(cs_main); { LOCK(mempool.cs); if (mempool.exists(hash)) { - tx = mempool.lookup(hash); + txOut = mempool.lookup(hash); return true; } } - CTxDB txdb("r"); - CTxIndex txindex; - if (tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex)) - { - CBlock block; - if (block.ReadFromDisk(txindex.pos.blockPos, false)) - hashBlock = block.GetHash(); - return true; + + if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it + int nHeight = -1; + { + CCoinsDB coindb("r"); + CCoinsViewDB view(coindb); + CCoins coins; + if (view.GetCoins(hash, coins)) + nHeight = coins.nHeight; + } + if (nHeight > 0) + pindexSlow = FindBlockByHeight(nHeight); } } - return false; -} + if (pindexSlow) { + CBlock block; + if (block.ReadFromDisk(pindexSlow)) { + BOOST_FOREACH(const CTransaction &tx, block.vtx) { + if (tx.GetHash() == hash) { + txOut = tx; + hashBlock = pindexSlow->GetBlockHash(); + return true; + } + } + } + } + return false; +} @@ -1051,7 +1136,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) if (pindexNew->bnChainWork > bnBestInvalidWork) { bnBestInvalidWork = pindexNew->bnChainWork; - CTxDB().WriteBestInvalidWork(bnBestInvalidWork); + CChainDB().WriteBestInvalidWork(bnBestInvalidWork); uiInterface.NotifyBlocksChanged(); } printf("InvalidChainFound: invalid block=%s height=%d work=%s date=%s\n", @@ -1084,145 +1169,35 @@ void CBlock::UpdateTime(const CBlockIndex* pindexPrev) -bool CTransaction::DisconnectInputs(CTxDB& txdb) -{ - // Relinquish previous transactions' spent pointers - if (!IsCoinBase()) - { - BOOST_FOREACH(const CTxIn& txin, vin) - { - COutPoint prevout = txin.prevout; - - // Get prev txindex from disk - CTxIndex txindex; - if (!txdb.ReadTxIndex(prevout.hash, txindex)) - return error("DisconnectInputs() : ReadTxIndex failed"); - - if (prevout.n >= txindex.vSpent.size()) - return error("DisconnectInputs() : prevout.n out of range"); - - // Mark outpoint as not spent - txindex.vSpent[prevout.n].SetNull(); - - // Write back - if (!txdb.UpdateTxIndex(prevout.hash, txindex)) - return error("DisconnectInputs() : UpdateTxIndex failed"); - } - } - - // Remove transaction from index - // This can fail if a duplicate of this transaction was in a chain that got - // reorganized away. This is only possible if this transaction was completely - // spent, so erasing it would be a no-op anyway. - txdb.EraseTxIndex(*this); - - return true; -} - - -bool CTransaction::FetchInputs(CTxDB& txdb, const map& mapTestPool, - bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid) +CTxOut CTransaction::GetOutputFor(const CTxIn& input, CCoinsView& view) { - // FetchInputs can return false either because we just haven't seen some inputs - // (in which case the transaction should be stored as an orphan) - // or because the transaction is malformed (in which case the transaction should - // be dropped). If tx is definitely invalid, fInvalid will be set to true. - fInvalid = false; - - if (IsCoinBase()) - return true; // Coinbase transactions have no inputs to fetch. - - for (unsigned int i = 0; i < vin.size(); i++) - { - COutPoint prevout = vin[i].prevout; - if (inputsRet.count(prevout.hash)) - continue; // Got it already - - // Read txindex - CTxIndex& txindex = inputsRet[prevout.hash].first; - bool fFound = true; - if ((fBlock || fMiner) && mapTestPool.count(prevout.hash)) - { - // Get txindex from current proposed changes - txindex = mapTestPool.find(prevout.hash)->second; - } - else - { - // Read txindex from txdb - fFound = txdb.ReadTxIndex(prevout.hash, txindex); - } - if (!fFound && (fBlock || fMiner)) - return fMiner ? false : error("FetchInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - - // Read txPrev - CTransaction& txPrev = inputsRet[prevout.hash].second; - if (!fFound || txindex.pos.IsMemPool()) - { - // Get prev tx from single transactions in memory - { - LOCK(mempool.cs); - if (!mempool.exists(prevout.hash)) - return error("FetchInputs() : %s mempool Tx prev not found %s", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - txPrev = mempool.lookup(prevout.hash); - } - if (!fFound) - txindex.vSpent.resize(txPrev.vout.size()); - } - else - { - // Get prev tx from disk - if (!txPrev.ReadFromDisk(txindex.pos)) - return error("FetchInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,10).c_str(), prevout.hash.ToString().substr(0,10).c_str()); - } - } - - // Make sure all prevout.n indexes are valid: - for (unsigned int i = 0; i < vin.size(); i++) - { - const COutPoint prevout = vin[i].prevout; - assert(inputsRet.count(prevout.hash) != 0); - const CTxIndex& txindex = inputsRet[prevout.hash].first; - const CTransaction& txPrev = inputsRet[prevout.hash].second; - if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) - { - // Revisit this if/when transaction replacement is implemented and allows - // adding inputs: - fInvalid = true; - return DoS(100, error("FetchInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); - } - } - - return true; -} - -const CTxOut& CTransaction::GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const -{ - MapPrevTx::const_iterator mi = inputs.find(input.prevout.hash); - if (mi == inputs.end()) + CCoins coins; + if (!view.GetCoins(input.prevout.hash, coins)) throw std::runtime_error("CTransaction::GetOutputFor() : prevout.hash not found"); - const CTransaction& txPrev = (mi->second).second; - if (input.prevout.n >= txPrev.vout.size()) - throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range"); + if (input.prevout.n >= coins.vout.size()) + throw std::runtime_error("CTransaction::GetOutputFor() : prevout.n out of range or already spent"); + + const CTxOut &out = coins.vout[input.prevout.n]; + if (out.IsNull()) + throw std::runtime_error("CTransaction::GetOutputFor() : already spent"); - return txPrev.vout[input.prevout.n]; + return out; } -int64 CTransaction::GetValueIn(const MapPrevTx& inputs) const +int64 CTransaction::GetValueIn(CCoinsView& inputs) const { if (IsCoinBase()) return 0; int64 nResult = 0; for (unsigned int i = 0; i < vin.size(); i++) - { nResult += GetOutputFor(vin[i], inputs).nValue; - } - return nResult; + return nResult; } -unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const +unsigned int CTransaction::GetP2SHSigOpCount(CCoinsView& inputs) const { if (IsCoinBase()) return 0; @@ -1230,107 +1205,137 @@ unsigned int CTransaction::GetP2SHSigOpCount(const MapPrevTx& inputs) const unsigned int nSigOps = 0; for (unsigned int i = 0; i < vin.size(); i++) { - const CTxOut& prevout = GetOutputFor(vin[i], inputs); + CTxOut prevout = GetOutputFor(vin[i], inputs); if (prevout.scriptPubKey.IsPayToScriptHash()) nSigOps += prevout.scriptPubKey.GetSigOpCount(vin[i].scriptSig); } return nSigOps; } -bool CTransaction::ConnectInputs(MapPrevTx inputs, - map& mapTestPool, const CDiskTxPos& posThisTx, - const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash) +bool CTransaction::UpdateCoins(CCoinsView &inputs, CTxUndo &txundo, int nHeight) const +{ + uint256 hash = GetHash(); + + // mark inputs spent + if (!IsCoinBase()) { + BOOST_FOREACH(const CTxIn &txin, vin) { + CCoins coins; + if (!inputs.GetCoins(txin.prevout.hash, coins)) + return error("UpdateCoins() : cannot find prevtx"); + CTxInUndo undo; + if (!coins.Spend(txin.prevout, undo)) + return error("UpdateCoins() : cannot spend input"); + txundo.vprevout.push_back(undo); + if (!inputs.SetCoins(txin.prevout.hash, coins)) + return error("UpdateCoins() : cannot update input"); + } + } + + // add outputs + if (!inputs.SetCoins(hash, CCoins(*this, nHeight))) + return error("UpdateCoins() : cannot update output"); + + return true; +} + +bool CTransaction::HaveInputs(CCoinsView &inputs) const +{ + if (!IsCoinBase()) { + // first check whether information about the prevout hash is available + for (unsigned int i = 0; i < vin.size(); i++) { + const COutPoint &prevout = vin[i].prevout; + if (!inputs.HaveCoins(prevout.hash)) + return false; + } + + // then check whether the actual outputs are available + for (unsigned int i = 0; i < vin.size(); i++) { + const COutPoint &prevout = vin[i].prevout; + CCoins coins; + inputs.GetCoins(prevout.hash, coins); + if (!coins.IsAvailable(prevout.n)) + return false; + } + } + return true; +} + +bool CTransaction::CheckInputs(CCoinsView &inputs, enum CheckSig_mode csmode, bool fStrictPayToScriptHash, bool fStrictEncodings) const { - // Take over previous transactions' spent pointers - // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain - // fMiner is true when called from the internal bitcoin miner - // ... both are false when called from CTransaction::AcceptToMemoryPool if (!IsCoinBase()) { int64 nValueIn = 0; int64 nFees = 0; for (unsigned int i = 0; i < vin.size(); i++) { - COutPoint prevout = vin[i].prevout; - assert(inputs.count(prevout.hash) > 0); - CTxIndex& txindex = inputs[prevout.hash].first; - CTransaction& txPrev = inputs[prevout.hash].second; + const COutPoint &prevout = vin[i].prevout; + CCoins coins; + if (!inputs.GetCoins(prevout.hash, coins)) + return error("CheckInputs() : cannot find prevout tx"); - if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) - return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %"PRIszu" %"PRIszu" prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str())); + // Check for conflicts (double-spend) + // This doesn't trigger the DoS code on purpose; if it did, it would make it easier + // for an attacker to attempt to split the network. + if (!coins.IsAvailable(prevout.n)) + return error("CheckInputs() : %s prev tx already used", GetHash().ToString().substr(0,10).c_str()); // If prev is coinbase, check that it's matured - if (txPrev.IsCoinBase()) - for (const CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) - if (pindex->GetBlockPos() == txindex.pos.blockPos) - return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight); + if (coins.IsCoinBase()) { + CBlockIndex *pindexBlock = inputs.GetBestBlock(); + if (pindexBlock->nHeight - coins.nHeight < COINBASE_MATURITY) + return error("CheckInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - coins.nHeight); + } // Check for negative or overflow input values - nValueIn += txPrev.vout[prevout.n].nValue; - if (!MoneyRange(txPrev.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) - return DoS(100, error("ConnectInputs() : txin values out of range")); + nValueIn += coins.vout[prevout.n].nValue; + if (!MoneyRange(coins.vout[prevout.n].nValue) || !MoneyRange(nValueIn)) + return DoS(100, error("CheckInputs() : txin values out of range")); } + + if (nValueIn < GetValueOut()) + return DoS(100, error("ChecktInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); + + // Tally transaction fees + int64 nTxFee = nValueIn - GetValueOut(); + if (nTxFee < 0) + return DoS(100, error("CheckInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); + nFees += nTxFee; + if (!MoneyRange(nFees)) + return DoS(100, error("CheckInputs() : nFees out of range")); + // The first loop above does all the inexpensive checks. // Only if ALL inputs pass do we perform expensive ECDSA signature checks. // Helps prevent CPU exhaustion attacks. - for (unsigned int i = 0; i < vin.size(); i++) - { - COutPoint prevout = vin[i].prevout; - assert(inputs.count(prevout.hash) > 0); - CTxIndex& txindex = inputs[prevout.hash].first; - CTransaction& txPrev = inputs[prevout.hash].second; - // Check for conflicts (double-spend) - // This doesn't trigger the DoS code on purpose; if it did, it would make it easier - // for an attacker to attempt to split the network. - if (!txindex.vSpent[prevout.n].IsNull()) - return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,10).c_str(), txindex.vSpent[prevout.n].ToString().c_str()); + // Skip ECDSA signature verification when connecting blocks + // before the last blockchain checkpoint. This is safe because block merkle hashes are + // still computed and checked, and any change will be caught at the next checkpoint. + if (csmode == CS_ALWAYS || + (csmode == CS_AFTER_CHECKPOINT && inputs.GetBestBlock()->nHeight >= Checkpoints::GetTotalBlocksEstimate())) { + for (unsigned int i = 0; i < vin.size(); i++) { + const COutPoint &prevout = vin[i].prevout; + CCoins coins; + inputs.GetCoins(prevout.hash, coins); - // Skip ECDSA signature verification when connecting blocks (fBlock=true) - // before the last blockchain checkpoint. This is safe because block merkle hashes are - // still computed and checked, and any change will be caught at the next checkpoint. - if (!(fBlock && (nBestHeight < Checkpoints::GetTotalBlocksEstimate()))) - { // Verify signature - if (!VerifySignature(txPrev, *this, i, fStrictPayToScriptHash, false, 0)) - { + if (!VerifySignature(coins, *this, i, fStrictPayToScriptHash, fStrictEncodings, 0)) { // only during transition phase for P2SH: do not invoke anti-DoS code for // potentially old clients relaying bad P2SH transactions - if (fStrictPayToScriptHash && VerifySignature(txPrev, *this, i, false, false, 0)) - return error("ConnectInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); + if (fStrictPayToScriptHash && VerifySignature(coins, *this, i, false, fStrictEncodings, 0)) + return error("CheckInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); - return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); + return DoS(100,error("CheckInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); } } - - // Mark outpoints as spent - txindex.vSpent[prevout.n] = posThisTx; - - // Write back - if (fBlock || fMiner) - { - mapTestPool[prevout.hash] = txindex; - } } - - if (nValueIn < GetValueOut()) - return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str())); - - // Tally transaction fees - int64 nTxFee = nValueIn - GetValueOut(); - if (nTxFee < 0) - return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); - nFees += nTxFee; - if (!MoneyRange(nFees)) - return DoS(100, error("ConnectInputs() : nFees out of range")); } return true; } -bool CTransaction::ClientConnectInputs() +bool CTransaction::ClientCheckInputs() const { if (IsCoinBase()) return false; @@ -1351,7 +1356,7 @@ bool CTransaction::ClientConnectInputs() return false; // Verify signature - if (!VerifySignature(txPrev, *this, i, true, false, 0)) + if (!VerifySignature(CCoins(txPrev, -1), *this, i, true, false, 0)) return error("ConnectInputs() : VerifySignature failed"); ///// this is redundant with the mempool.mapNextTx stuff, @@ -1379,34 +1384,89 @@ bool CTransaction::ClientConnectInputs() -bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) +bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsView &view) { - // Disconnect in reverse order - for (int i = vtx.size()-1; i >= 0; i--) - if (!vtx[i].DisconnectInputs(txdb)) - return false; + assert(pindex == view.GetBestBlock()); - // Update block index on disk without changing it in memory. - // The memory index structure will be changed after the db commits. - if (pindex->pprev) + CBlockUndo blockUndo; { - CDiskBlockIndex blockindexPrev(pindex->pprev); - blockindexPrev.hashNext = 0; - if (!txdb.WriteBlockIndex(blockindexPrev)) - return error("DisconnectBlock() : WriteBlockIndex failed"); + CDiskBlockPos pos = pindex->GetUndoPos(); + if (pos.IsNull()) + return error("DisconnectBlock() : no undo data available"); + FILE *file = OpenUndoFile(pos, true); + if (file == NULL) + return error("DisconnectBlock() : undo file not available"); + CAutoFile fileUndo(file, SER_DISK, CLIENT_VERSION); + fileUndo >> blockUndo; } + assert(blockUndo.vtxundo.size() + 1 == vtx.size()); + + // undo transactions in reverse order + for (int i = vtx.size() - 1; i >= 0; i--) { + const CTransaction &tx = vtx[i]; + uint256 hash = tx.GetHash(); + + // check that all outputs are available + CCoins outs; + if (!view.GetCoins(hash, outs)) + return error("DisconnectBlock() : outputs still spent? database corrupted"); + + CCoins outsBlock = CCoins(tx, pindex->nHeight); + if (outs != outsBlock) + return error("DisconnectBlock() : added transaction mismatch? database corrupted"); + + // remove outputs + if (!view.SetCoins(hash, CCoins())) + return error("DisconnectBlock() : cannot delete coin outputs"); + + // restore inputs + if (i > 0) { // not coinbases + const CTxUndo &txundo = blockUndo.vtxundo[i-1]; + assert(txundo.vprevout.size() == tx.vin.size()); + for (unsigned int j = tx.vin.size(); j-- > 0;) { + const COutPoint &out = tx.vin[j].prevout; + const CTxInUndo &undo = txundo.vprevout[j]; + CCoins coins; + view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent + if (coins.IsPruned()) { + if (undo.nHeight == 0) + return error("DisconnectBlock() : undo data doesn't contain tx metadata? database corrupted"); + coins.fCoinBase = undo.fCoinBase; + coins.nHeight = undo.nHeight; + coins.nVersion = undo.nVersion; + } else { + if (undo.nHeight != 0) + return error("DisconnectBlock() : undo data contains unneeded tx metadata? database corrupted"); + } + if (coins.IsAvailable(out.n)) + return error("DisconnectBlock() : prevout output not spent? database corrupted"); + if (coins.vout.size() < out.n+1) + coins.vout.resize(out.n+1); + coins.vout[out.n] = undo.txout; + if (!view.SetCoins(out.hash, coins)) + return error("DisconnectBlock() : cannot restore coin inputs"); + } + } + } + + // move best block pointer to prevout block + view.SetBestBlock(pindex->pprev); + return true; } -bool FindUndoPos(CTxDB &txdb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize); +bool FindUndoPos(CChainDB &chaindb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize); -bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) +bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsView &view, bool fJustCheck) { // Check it again in case a previous version let a bad block in if (!CheckBlock(!fJustCheck, !fJustCheck)) return false; + // verify that the view's current state corresponds to the previous block + assert(pindex->pprev == view.GetBestBlock()); + // Do not allow blocks that contain transactions which 'overwrite' older transactions, // unless those are already completely spent. // If such overwrites are allowed, coinbases and transactions depending upon those @@ -1421,115 +1481,81 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) // initial block download. bool fEnforceBIP30 = !((pindex->nHeight==91842 && pindex->GetBlockHash() == uint256("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) || (pindex->nHeight==91880 && pindex->GetBlockHash() == uint256("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721"))); + if (fEnforceBIP30) { + BOOST_FOREACH(CTransaction& tx, vtx) { + uint256 hash = tx.GetHash(); + CCoins coins; + if (view.GetCoins(hash, coins) && !coins.IsPruned()) + return error("ConnectBlock() : tried to overwrite transaction"); + } + } // BIP16 didn't become active until Apr 1 2012 int64 nBIP16SwitchTime = 1333238400; bool fStrictPayToScriptHash = (pindex->nTime >= nBIP16SwitchTime); - //// issue here: it doesn't know the version - unsigned int nTxPos; - if (fJustCheck) - // 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->pos.nPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - 1 + GetSizeOfCompactSize(vtx.size()); - CBlockUndo blockundo; - map mapQueuedChanges; int64 nFees = 0; unsigned int nSigOps = 0; BOOST_FOREACH(CTransaction& tx, vtx) { - uint256 hashTx = tx.GetHash(); - - if (fEnforceBIP30) { - CTxIndex txindexOld; - if (txdb.ReadTxIndex(hashTx, txindexOld)) { - BOOST_FOREACH(CDiskTxPos &pos, txindexOld.vSpent) - if (pos.IsNull()) - return false; - } - } - nSigOps += tx.GetLegacySigOpCount(); if (nSigOps > MAX_BLOCK_SIGOPS) return DoS(100, error("ConnectBlock() : too many sigops")); - CDiskTxPos posThisTx(pindex->GetBlockPos(), nTxPos); - if (!fJustCheck) - nTxPos += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); - else - posThisTx = CDiskTxPos(true); - - MapPrevTx mapInputs; if (!tx.IsCoinBase()) { - CTxUndo undo; - - bool fInvalid; - if (!tx.FetchInputs(txdb, mapQueuedChanges, true, false, mapInputs, fInvalid)) - return false; + if (!tx.HaveInputs(view)) + return DoS(100, error("ConnectBlock() : inputs missing/spent")); if (fStrictPayToScriptHash) { // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating // an incredibly-expensive-to-validate block. - nSigOps += tx.GetP2SHSigOpCount(mapInputs); + nSigOps += tx.GetP2SHSigOpCount(view); if (nSigOps > MAX_BLOCK_SIGOPS) - return DoS(100, error("ConnectBlock() : too many sigops")); + return DoS(100, error("ConnectBlock() : too many sigops")); } - nFees += tx.GetValueIn(mapInputs)-tx.GetValueOut(); + nFees += tx.GetValueIn(view)-tx.GetValueOut(); - BOOST_FOREACH(const CTxIn &in, tx.vin) { - undo.vprevout.push_back(CTxInUndo(mapInputs[in.prevout.hash].second.vout[in.prevout.n], pindex->nHeight)); - } - - if (!tx.ConnectInputs(mapInputs, mapQueuedChanges, posThisTx, pindex, true, false, fStrictPayToScriptHash)) + if (!tx.CheckInputs(view, CS_AFTER_CHECKPOINT, fStrictPayToScriptHash, false)) return false; - - blockundo.vtxundo.push_back(undo); } - mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); + CTxUndo txundo; + if (!tx.UpdateCoins(view, txundo, pindex->nHeight)) + return error("ConnectBlock() : UpdateInputs failed"); + if (!tx.IsCoinBase()) + blockundo.vtxundo.push_back(txundo); } - 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) - { - if (!txdb.UpdateTxIndex((*mi).first, (*mi).second)) - return error("ConnectBlock() : UpdateTxIndex failed"); - } - // Write undo information to disk - if (pindex->GetUndoPos().IsNull() && pindex->nHeight > Checkpoints::GetTotalBlocksEstimate()) + if (pindex->GetUndoPos().IsNull()) { + CChainDB chaindb; CDiskBlockPos pos; - if (!FindUndoPos(txdb, pindex->pos.nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 8)) + if (!FindUndoPos(chaindb, pindex->pos.nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 8)) return error("ConnectBlock() : FindUndoPos failed"); if (!blockundo.WriteToDisk(pos)) return error("ConnectBlock() : CBlockUndo::WriteToDisk failed"); - pindex->nUndoPos = pos.nPos; - } - // Update block index on disk without changing it in memory. - // The memory index structure will be changed after the db commits. - if (pindex->pprev) - { - CDiskBlockIndex blockindexPrev(pindex->pprev); - blockindexPrev.hashNext = pindex->GetBlockHash(); - if (!txdb.WriteBlockIndex(blockindexPrev)) + // update nUndoPos in block index + pindex->nUndoPos = pos.nPos + 1; + CDiskBlockIndex blockindex(pindex); + if (!chaindb.WriteBlockIndex(blockindex)) return error("ConnectBlock() : WriteBlockIndex failed"); } + // add this block to the view's blockchain + if (!view.SetBestBlock(pindex)) + return false; + // Watch for transactions paying to me BOOST_FOREACH(CTransaction& tx, vtx) SyncWithWallets(tx, this, true); @@ -1537,47 +1563,70 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) return true; } -bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) +bool CBlock::SetBestChain(CBlockIndex* pindexNew) { - printf("REORGANIZE\n"); + // if this functions exits prematurely, the transaction is aborted + CCoinsDB coinsdb; + if (!coinsdb.TxnBegin()) + return error("SetBestChain() : TxnBegin failed"); + + // special case for attaching the genesis block + // note that no ConnectBlock is called, so its coinbase output is non-spendable + if (pindexGenesisBlock == NULL && pindexNew->GetBlockHash() == hashGenesisBlock) + { + coinsdb.WriteHashBestChain(pindexNew->GetBlockHash()); + if (!coinsdb.TxnCommit()) + return error("SetBestChain() : TxnCommit failed"); + pindexGenesisBlock = pindexNew; + pindexBest = pindexNew; + hashBestChain = pindexNew->GetBlockHash(); + nBestHeight = pindexBest->nHeight; + bnBestChainWork = pindexNew->bnChainWork; + return true; + } - // Find the fork - CBlockIndex* pfork = pindexBest; + // create cached view to the coins database + CCoinsViewDB viewDB(coinsdb); + CCoinsViewCache view(viewDB); + + // Find the fork (typically, there is none) + CBlockIndex* pfork = view.GetBestBlock(); CBlockIndex* plonger = pindexNew; while (pfork != plonger) { while (plonger->nHeight > pfork->nHeight) if (!(plonger = plonger->pprev)) - return error("Reorganize() : plonger->pprev is null"); + return error("SetBestChain() : plonger->pprev is null"); if (pfork == plonger) break; if (!(pfork = pfork->pprev)) - return error("Reorganize() : pfork->pprev is null"); + return error("SetBestChain() : pfork->pprev is null"); } - // List of what to disconnect + // List of what to disconnect (typically nothing) vector vDisconnect; - for (CBlockIndex* pindex = pindexBest; pindex != pfork; pindex = pindex->pprev) + for (CBlockIndex* pindex = view.GetBestBlock(); pindex != pfork; pindex = pindex->pprev) vDisconnect.push_back(pindex); - // List of what to connect + // List of what to connect (typically only pindexNew) vector vConnect; for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev) vConnect.push_back(pindex); reverse(vConnect.begin(), vConnect.end()); - printf("REORGANIZE: Disconnect %"PRIszu" blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str()); - printf("REORGANIZE: Connect %"PRIszu" blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str()); + if (vDisconnect.size() > 0) { + printf("REORGANIZE: Disconnect %"PRIszu" blocks; %s..%s\n", vDisconnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexBest->GetBlockHash().ToString().substr(0,20).c_str()); + printf("REORGANIZE: Connect %"PRIszu" blocks; %s..%s\n", vConnect.size(), pfork->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->GetBlockHash().ToString().substr(0,20).c_str()); + } // Disconnect shorter branch vector vResurrect; - BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) - { + BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) { CBlock block; if (!block.ReadFromDisk(pindex)) - return error("Reorganize() : ReadFromDisk for disconnect failed"); - if (!block.DisconnectBlock(txdb, pindex)) - return error("Reorganize() : DisconnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); + return error("SetBestBlock() : ReadFromDisk for disconnect failed"); + if (!block.DisconnectBlock(pindex, view)) + return error("SetBestBlock() : DisconnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); // Queue memory transactions to resurrect BOOST_FOREACH(const CTransaction& tx, block.vtx) @@ -1587,28 +1636,35 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Connect longer branch vector vDelete; - for (unsigned int i = 0; i < vConnect.size(); i++) - { - CBlockIndex* pindex = vConnect[i]; + BOOST_FOREACH(CBlockIndex *pindex, vConnect) { CBlock block; - if (!block.ReadFromDisk(pindex)) - return error("Reorganize() : ReadFromDisk for connect failed"); - if (!block.ConnectBlock(txdb, pindex)) - { - // Invalid block - return error("Reorganize() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); + CBlock *pblock; + if (pindex == pindexNew) // connecting *this block + pblock = this; + else { // other block; read it from disk + if (!block.ReadFromDisk(pindex)) + return error("SetBestBlock() : ReadFromDisk for connect failed"); + pblock = █ + } + if (!pblock->ConnectBlock(pindex, view)) { + InvalidChainFound(pindexNew); + return error("SetBestBlock() : ConnectBlock %s failed", pindex->GetBlockHash().ToString().substr(0,20).c_str()); } // Queue memory transactions to delete - BOOST_FOREACH(const CTransaction& tx, block.vtx) + BOOST_FOREACH(const CTransaction& tx, pblock->vtx) vDelete.push_back(tx); } - if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash())) - return error("Reorganize() : WriteHashBestChain failed"); // Make sure it's successfully written to disk before changing memory structure - if (!txdb.TxnCommit()) - return error("Reorganize() : TxnCommit failed"); + if (!view.Flush()) + return error("SetBestBlock() : failed to write coin changes"); + if (!coinsdb.TxnCommit()) + return error("SetBestBlock() : TxnCommit failed"); + coinsdb.Close(); + + // At this point, all changes have been done to the database. + // Proceed by updating the memory structures. // Disconnect shorter branch BOOST_FOREACH(CBlockIndex* pindex, vDisconnect) @@ -1622,108 +1678,12 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) // Resurrect memory transactions that were in the disconnected branch BOOST_FOREACH(CTransaction& tx, vResurrect) - tx.AcceptToMemoryPool(txdb, false); + tx.AcceptToMemoryPool(coinsdb, false); // Delete redundant memory transactions that are in the connected branch BOOST_FOREACH(CTransaction& tx, vDelete) mempool.remove(tx); - printf("REORGANIZE: done\n"); - - return true; -} - - -// Called from inside SetBestChain: attaches a block to the new best chain being built -bool CBlock::SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew) -{ - uint256 hash = GetHash(); - - // Adding to current best branch - if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash)) - { - txdb.TxnAbort(); - InvalidChainFound(pindexNew); - return false; - } - if (!txdb.TxnCommit()) - return error("SetBestChain() : TxnCommit failed"); - - // Add to current best branch - pindexNew->pprev->pnext = pindexNew; - - // Delete redundant memory transactions - BOOST_FOREACH(CTransaction& tx, vtx) - mempool.remove(tx); - - return true; -} - -bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) -{ - uint256 hash = GetHash(); - - if (!txdb.TxnBegin()) - return error("SetBestChain() : TxnBegin failed"); - - if (pindexGenesisBlock == NULL && hash == hashGenesisBlock) - { - txdb.WriteHashBestChain(hash); - if (!txdb.TxnCommit()) - return error("SetBestChain() : TxnCommit failed"); - pindexGenesisBlock = pindexNew; - } - else if (hashPrevBlock == hashBestChain) - { - if (!SetBestChainInner(txdb, pindexNew)) - return error("SetBestChain() : SetBestChainInner failed"); - } - else - { - // the first block in the new chain that will cause it to become the new best chain - CBlockIndex *pindexIntermediate = pindexNew; - - // list of blocks that need to be connected afterwards - std::vector vpindexSecondary; - - // Reorganize is costly in terms of db load, as it works in a single db transaction. - // Try to limit how much needs to be done inside - while (pindexIntermediate->pprev && pindexIntermediate->pprev->bnChainWork > pindexBest->bnChainWork) - { - vpindexSecondary.push_back(pindexIntermediate); - pindexIntermediate = pindexIntermediate->pprev; - } - - if (!vpindexSecondary.empty()) - printf("Postponing %"PRIszu" reconnects\n", vpindexSecondary.size()); - - // Switch to new best branch - if (!Reorganize(txdb, pindexIntermediate)) - { - txdb.TxnAbort(); - InvalidChainFound(pindexNew); - return error("SetBestChain() : Reorganize failed"); - } - - // Connect further blocks - BOOST_REVERSE_FOREACH(CBlockIndex *pindex, vpindexSecondary) - { - CBlock block; - if (!block.ReadFromDisk(pindex)) - { - printf("SetBestChain() : ReadFromDisk failed\n"); - break; - } - if (!txdb.TxnBegin()) { - printf("SetBestChain() : TxnBegin 2 failed\n"); - break; - } - // errors now are not fatal, we still did a reorganisation to a new chain in a valid way - if (!block.SetBestChainInner(txdb, pindex)) - break; - } - } - // Update best block in wallet (so we can detect restored wallets) bool fIsInitialDownload = IsInitialBlockDownload(); if (!fIsInitialDownload) @@ -1733,7 +1693,7 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) } // New best block - hashBestChain = hash; + hashBestChain = pindexNew->GetBlockHash(); pindexBest = pindexNew; pblockindexFBBHLast = NULL; nBestHeight = pindexBest->nHeight; @@ -1797,19 +1757,19 @@ bool CBlock::AddToBlockIndex(const CDiskBlockPos &pos) pindexNew->pos = pos; pindexNew->nUndoPos = 0; - CTxDB txdb; - if (!txdb.TxnBegin()) + CChainDB chaindb; + if (!chaindb.TxnBegin()) return false; - txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); - if (!txdb.TxnCommit()) + chaindb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); + if (!chaindb.TxnCommit()) return false; // New best - if (pindexNew->bnChainWork > bnBestChainWork) - if (!SetBestChain(txdb, pindexNew)) - return false; - - txdb.Close(); + if (pindexNew->bnChainWork > bnBestChainWork) { + if (!IsInitialBlockDownload() || (pindexNew->nHeight % 1) == 0) + if (!SetBestChain(pindexNew)) + return false; + } if (pindexNew == pindexBest) { @@ -1825,7 +1785,7 @@ bool CBlock::AddToBlockIndex(const CDiskBlockPos &pos) -bool FindBlockPos(CTxDB &txdb, CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64 nTime) +bool FindBlockPos(CChainDB &chaindb, CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64 nTime) { bool fUpdatedLast = false; @@ -1833,9 +1793,15 @@ bool FindBlockPos(CTxDB &txdb, CDiskBlockPos &pos, unsigned int nAddSize, unsign while (infoLastBlockFile.nSize + nAddSize >= MAX_BLOCKFILE_SIZE) { printf("Leaving block file %i: %s\n", nLastBlockFile, infoLastBlockFile.ToString().c_str()); + FILE *file = OpenBlockFile(pos); + FileCommit(file); + fclose(file); + file = OpenUndoFile(pos); + FileCommit(file); + fclose(file); nLastBlockFile++; infoLastBlockFile.SetNull(); - txdb.ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile); // check whether data for the new file somehow already exist; can fail just fine + chaindb.ReadBlockFileInfo(nLastBlockFile, infoLastBlockFile); // check whether data for the new file somehow already exist; can fail just fine fUpdatedLast = true; } @@ -1855,15 +1821,15 @@ bool FindBlockPos(CTxDB &txdb, CDiskBlockPos &pos, unsigned int nAddSize, unsign fclose(file); } - if (!txdb.WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile)) + if (!chaindb.WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile)) return error("FindBlockPos() : cannot write updated block info"); if (fUpdatedLast) - txdb.WriteLastBlockFile(nLastBlockFile); + chaindb.WriteLastBlockFile(nLastBlockFile); return true; } -bool FindUndoPos(CTxDB &txdb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize) +bool FindUndoPos(CChainDB &chaindb, int nFile, CDiskBlockPos &pos, unsigned int nAddSize) { pos.nFile = nFile; @@ -1873,15 +1839,15 @@ bool FindUndoPos(CTxDB &txdb, int nFile, CDiskBlockPos &pos, unsigned int nAddSi if (nFile == nLastBlockFile) { pos.nPos = infoLastBlockFile.nUndoSize; nNewSize = (infoLastBlockFile.nUndoSize += nAddSize); - if (!txdb.WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile)) + if (!chaindb.WriteBlockFileInfo(nLastBlockFile, infoLastBlockFile)) return error("FindUndoPos() : cannot write updated block info"); } else { CBlockFileInfo info; - if (!txdb.ReadBlockFileInfo(nFile, info)) + if (!chaindb.ReadBlockFileInfo(nFile, info)) return error("FindUndoPos() : cannot read block info"); pos.nPos = info.nUndoSize; nNewSize = (info.nUndoSize += nAddSize); - if (!txdb.WriteBlockFileInfo(nFile, info)) + if (!chaindb.WriteBlockFileInfo(nFile, info)) return error("FindUndoPos() : cannot write updated block info"); } @@ -2013,8 +1979,8 @@ bool CBlock::AcceptBlock() return error("AcceptBlock() : out of disk space"); CDiskBlockPos blockPos; { - CTxDB txdb; - if (!FindBlockPos(txdb, blockPos, nBlockSize+8, nHeight, nTime)) + CChainDB chaindb; + if (!FindBlockPos(chaindb, blockPos, nBlockSize+8, nHeight, nTime)) return error("AcceptBlock() : FindBlockPos failed"); } if (!WriteToDisk(blockPos)) @@ -2159,15 +2125,17 @@ int nLastBlockFile = 0; FILE* OpenDiskFile(const CDiskBlockPos &pos, const char *prefix, bool fReadOnly) { - if (pos.IsNull() || pos.IsMemPool()) + if (pos.IsNull()) return NULL; boost::filesystem::path path = GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, pos.nFile); boost::filesystem::create_directories(path.parent_path()); FILE* file = fopen(path.string().c_str(), "rb+"); if (!file && !fReadOnly) file = fopen(path.string().c_str(), "wb+"); - if (!file) + if (!file) { + printf("Unable to open file %s\n", path.string().c_str()); return NULL; + } if (pos.nPos) { if (fseek(file, pos.nPos, SEEK_SET)) { printf("Unable to seek to position %u of %s\n", pos.nPos, path.string().c_str()); @@ -2200,10 +2168,12 @@ bool LoadBlockIndex(bool fAllowNew) // // Load block index // - CTxDB txdb("cr"); - if (!txdb.LoadBlockIndex()) + CChainDB chaindb("cr"); + CCoinsDB coinsdb("cr"); + if (!LoadBlockIndex(coinsdb, chaindb)) return false; - txdb.Close(); + chaindb.Close(); + coinsdb.Close(); // // Init with genesis block @@ -2256,8 +2226,8 @@ bool LoadBlockIndex(bool fAllowNew) unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION); CDiskBlockPos blockPos; { - CTxDB txdb; - if (!FindBlockPos(txdb, blockPos, nBlockSize+8, 0, block.nTime)) + CChainDB chaindb; + if (!FindBlockPos(chaindb, blockPos, nBlockSize+8, 0, block.nTime)) return error("AcceptBlock() : FindBlockPos failed"); } if (!block.WriteToDisk(blockPos)) @@ -2316,7 +2286,7 @@ void PrintBlockTree() // print item CBlock block; block.ReadFromDisk(pindex); - printf("%d (blk%05u.dat:0x%lx) %s tx %"PRIszu"", + printf("%d (blk%05u.dat:0x%x) %s tx %"PRIszu"", pindex->nHeight, pindex->GetBlockPos().nFile, pindex->GetBlockPos().nPos, DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(), @@ -2522,22 +2492,20 @@ string GetWarnings(string strFor) // -bool static AlreadyHave(CTxDB& txdb, const CInv& inv) +bool static AlreadyHave(CCoinsDB &coinsdb, const CInv& inv) { switch (inv.type) { case MSG_TX: { - bool txInMap = false; + bool txInMap = false; { - LOCK(mempool.cs); - txInMap = (mempool.exists(inv.hash)); + LOCK(mempool.cs); + txInMap = mempool.exists(inv.hash); } - return txInMap || - mapOrphanTransactions.count(inv.hash) || - txdb.ContainsTx(inv.hash); + return txInMap || mapOrphanTransactions.count(inv.hash) || + coinsdb.HaveCoins(inv.hash); } - case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); @@ -2780,7 +2748,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) break; } } - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) { const CInv &inv = vInv[nInv]; @@ -2789,7 +2757,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return true; pfrom->AddInventoryKnown(inv); - bool fAlreadyHave = AlreadyHave(txdb, inv); + bool fAlreadyHave = AlreadyHave(coinsdb, inv); if (fDebug) printf(" got inventory: %s %s\n", inv.ToString().c_str(), fAlreadyHave ? "have" : "new"); @@ -2961,7 +2929,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vector vWorkQueue; vector vEraseQueue; CDataStream vMsg(vRecv); - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); CTransaction tx; vRecv >> tx; @@ -2969,7 +2937,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->AddInventoryKnown(inv); bool fMissingInputs = false; - if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs)) + if (tx.AcceptToMemoryPool(coinsdb, true, &fMissingInputs)) { SyncWithWallets(tx, NULL, true); RelayMessage(inv, vMsg); @@ -2991,7 +2959,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) CInv inv(MSG_TX, tx.GetHash()); bool fMissingInputs2 = false; - if (tx.AcceptToMemoryPool(txdb, true, &fMissingInputs2)) + if (tx.AcceptToMemoryPool(coinsdb, true, &fMissingInputs2)) { printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str()); SyncWithWallets(tx, NULL, true); @@ -3439,11 +3407,11 @@ bool SendMessages(CNode* pto, bool fSendTrickle) // vector vGetData; int64 nNow = GetTime() * 1000000; - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) { const CInv& inv = (*pto->mapAskFor.begin()).second; - if (!AlreadyHave(txdb, inv)) + if (!AlreadyHave(coinsdb, inv)) { if (fDebugNet) printf("sending getdata: %s\n", inv.ToString().c_str()); @@ -3653,7 +3621,9 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) int64 nFees = 0; { LOCK2(cs_main, mempool.cs); - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); + CCoinsViewDB viewdb(coinsdb); + CCoinsViewCache view(viewdb); // Priority order to process transactions list vOrphan; // list memory doesn't move @@ -3675,9 +3645,8 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) BOOST_FOREACH(const CTxIn& txin, tx.vin) { // Read prev transaction - CTransaction txPrev; - CTxIndex txindex; - if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex)) + CCoins coins; + if (!view.GetCoins(txin.prevout.hash, coins)) { // This should never happen; all transactions in the memory // pool should connect to either transactions in the chain @@ -3704,10 +3673,12 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue; continue; } - int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; + + int64 nValueIn = coins.vout[txin.prevout.n].nValue; nTotalIn += nValueIn; - int nConf = txindex.GetDepthInMainChain(); + int nConf = pindexPrev->nHeight - coins.nHeight; + dPriority += (double)nValueIn * nConf; } if (fMissingInputs) continue; @@ -3731,7 +3702,6 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) } // Collect transactions into block - map mapTestPool; uint64 nBlockSize = 1000; uint64 nBlockTx = 0; int nBlockSigOps = 100; @@ -3750,6 +3720,9 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); vecPriority.pop_back(); + // second layer cached modifications just for this transaction + CCoinsViewCache viewTemp(view, true); + // Size limits unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); if (nBlockSize + nTxSize >= nBlockMaxSize) @@ -3774,24 +3747,21 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) std::make_heap(vecPriority.begin(), vecPriority.end(), comparer); } - // Connecting shouldn't fail due to dependency on other memory pool transactions - // because we're already processing them in order of dependency - map mapTestPoolTmp(mapTestPool); - MapPrevTx mapInputs; - bool fInvalid; - if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid)) + if (!tx.CheckInputs(viewTemp, CS_ALWAYS, true, false)) continue; - int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); + int64 nTxFees = tx.GetValueIn(viewTemp)-tx.GetValueOut(); - nTxSigOps += tx.GetP2SHSigOpCount(mapInputs); + nTxSigOps += tx.GetP2SHSigOpCount(viewTemp); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; - if (!tx.ConnectInputs(mapInputs, mapTestPoolTmp, CDiskTxPos(true), pindexPrev, false, true)) + CTxUndo txundo; + if (!tx.UpdateCoins(viewTemp, txundo, pindexPrev->nHeight+1)) continue; - mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(true), tx.vout.size()); - swap(mapTestPool, mapTestPoolTmp); + + // push changes from the second layer cache to the first one + viewTemp.Flush(); // Added pblock->vtx.push_back(tx); @@ -3829,19 +3799,20 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) nLastBlockSize = nBlockSize; printf("CreateNewBlock(): total size %"PRI64u"\n", nBlockSize); - pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); - - // Fill in header - pblock->hashPrevBlock = pindexPrev->GetBlockHash(); - pblock->UpdateTime(pindexPrev); - pblock->nBits = GetNextWorkRequired(pindexPrev, pblock.get()); - pblock->nNonce = 0; + pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees); + // Fill in header + pblock->hashPrevBlock = pindexPrev->GetBlockHash(); + pblock->UpdateTime(pindexPrev); + pblock->nBits = GetNextWorkRequired(pindexPrev, pblock.get()); + pblock->nNonce = 0; pblock->vtx[0].vin[0].scriptSig = scriptDummy; + CBlockIndex indexDummy(*pblock); indexDummy.pprev = pindexPrev; indexDummy.nHeight = pindexPrev->nHeight + 1; - if (!pblock->ConnectBlock(txdb, &indexDummy, true)) + CCoinsViewCache viewNew(viewdb); + if (!pblock->ConnectBlock(&indexDummy, viewNew, true)) throw std::runtime_error("CreateNewBlock() : ConnectBlock failed"); } diff --git a/src/main.h b/src/main.h index 936a4e184..a57fadac7 100644 --- a/src/main.h +++ b/src/main.h @@ -31,6 +31,7 @@ static const unsigned int MAX_INV_SZ = 50000; static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB +static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF; static const int64 MIN_TX_FEE = 50000; static const int64 MIN_RELAY_TX_FEE = 10000; static const int64 MAX_MONEY = 21000000 * COIN; @@ -81,9 +82,12 @@ static const uint64 nMinDiskSpace = 52428800; class CReserveKey; -class CTxDB; -class CTxIndex; +class CCoinsDB; +class CChainDB; class CDiskBlockPos; +class CCoins; +class CTxUndo; +class CCoinsView; void RegisterWallet(CWallet* pwalletIn); void UnregisterWallet(CWallet* pwalletIn); @@ -108,8 +112,7 @@ unsigned int ComputeMinWork(unsigned int nBase, int64 nTime); int GetNumBlocksOfPeers(); bool IsInitialBlockDownload(); std::string GetWarnings(std::string strFor); -bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock); - +bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false); @@ -143,62 +146,8 @@ public: void SetNull() { nFile = -1; nPos = 0; } bool IsNull() const { return (nFile == -1); } - - void SetMemPool() { nFile = -2; nPos = 0; } - bool IsMemPool() const { return (nFile == -2); } }; -/** Position on disk for a particular transaction. */ -class CDiskTxPos -{ -public: - CDiskBlockPos blockPos; - unsigned int nTxPos; - - CDiskTxPos(bool fInMemPool = false) - { - SetNull(); - if (fInMemPool) - blockPos.SetMemPool(); - } - - CDiskTxPos(const CDiskBlockPos &block, unsigned int nTxPosIn) : blockPos(block), nTxPos(nTxPosIn) { } - - IMPLEMENT_SERIALIZE( - READWRITE(blockPos); - READWRITE(VARINT(nTxPos)); - ) - - void SetNull() { blockPos.SetNull(); nTxPos = 0; } - bool IsNull() const { return (nTxPos == 0); } - bool IsMemPool() const { return blockPos.IsMemPool(); } - - friend bool operator==(const CDiskTxPos& a, const CDiskTxPos& b) - { - return (a.blockPos == b.blockPos && - a.nTxPos == b.nTxPos); - } - - friend bool operator!=(const CDiskTxPos& a, const CDiskTxPos& b) - { - return !(a == b); - } - - std::string ToString() const - { - if (IsNull()) - return "null"; - else if (blockPos.IsMemPool()) - return "mempool"; - else - return strprintf("\"blk%05i.dat:0x%x\"", blockPos.nFile, nTxPos); - } - - void print() const - { - printf("%s", ToString().c_str()); - } -}; @@ -413,7 +362,13 @@ enum GetMinFee_mode GMF_SEND, }; -typedef std::map > MapPrevTx; +// Modes for script/signature checking +enum CheckSig_mode +{ + CS_NEVER, // never validate scripts + CS_AFTER_CHECKPOINT, // validate scripts after the last checkpoint + CS_ALWAYS // always validate scripts +}; /** The basic transaction that is broadcasted on the network and contained in * blocks. A transaction can contain multiple inputs and outputs. @@ -525,7 +480,7 @@ public: @return True if all inputs (scriptSigs) use only standard transaction forms @see CTransaction::FetchInputs */ - bool AreInputsStandard(const MapPrevTx& mapInputs) const; + bool AreInputsStandard(CCoinsView& mapInputs) const; /** Count ECDSA signature operations the old-fashioned (pre-0.6) way @return number of sigops this transaction's outputs will produce when spent @@ -539,7 +494,7 @@ public: @return maximum number of sigops required to validate this transaction's inputs @see CTransaction::FetchInputs */ - unsigned int GetP2SHSigOpCount(const MapPrevTx& mapInputs) const; + unsigned int GetP2SHSigOpCount(CCoinsView& mapInputs) const; /** Amount of bitcoins spent by this transaction. @return sum of all outputs (note: does not include fees) @@ -564,7 +519,7 @@ public: @return Sum of value of all inputs (scriptSigs) @see CTransaction::FetchInputs */ - int64 GetValueIn(const MapPrevTx& mapInputs) const; + int64 GetValueIn(CCoinsView& mapInputs) const; static bool AllowFree(double dPriority) { @@ -575,33 +530,6 @@ public: int64 GetMinFee(unsigned int nBlockSize=1, bool fAllowFree=true, enum GetMinFee_mode mode=GMF_BLOCK) const; - bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL) - { - CAutoFile filein = CAutoFile(OpenBlockFile(pos.blockPos, pfileRet==NULL), SER_DISK, CLIENT_VERSION); - if (!filein) - return error("CTransaction::ReadFromDisk() : OpenBlockFile failed"); - - // Read transaction - if (fseek(filein, pos.nTxPos, SEEK_SET) != 0) - return error("CTransaction::ReadFromDisk() : fseek failed"); - - try { - filein >> *this; - } - catch (std::exception &e) { - return error("%s() : deserialize or I/O error", __PRETTY_FUNCTION__); - } - - // Return file pointer - if (pfileRet) - { - if (fseek(filein, pos.nTxPos, SEEK_SET) != 0) - return error("CTransaction::ReadFromDisk() : second fseek failed"); - *pfileRet = filein.release(); - } - return true; - } - friend bool operator==(const CTransaction& a, const CTransaction& b) { return (a.nVersion == b.nVersion && @@ -638,45 +566,27 @@ public: } - bool ReadFromDisk(CTxDB& txdb, COutPoint prevout, CTxIndex& txindexRet); - bool ReadFromDisk(CTxDB& txdb, COutPoint prevout); - bool ReadFromDisk(COutPoint prevout); - bool DisconnectInputs(CTxDB& txdb); + // Do all possible client-mode checks + bool ClientCheckInputs() const; - /** Fetch from memory and/or disk. inputsRet keys are transaction hashes. + // Check whether all prevouts of this transaction are present in the UTXO set represented by view + bool HaveInputs(CCoinsView &view) const; - @param[in] txdb Transaction database - @param[in] mapTestPool List of pending changes to the transaction index database - @param[in] fBlock True if being called to add a new best-block to the chain - @param[in] fMiner True if being called by CreateNewBlock - @param[out] inputsRet Pointers to this transaction's inputs - @param[out] fInvalid returns true if transaction is invalid - @return Returns true if all inputs are in txdb or mapTestPool - */ - bool FetchInputs(CTxDB& txdb, const std::map& mapTestPool, - bool fBlock, bool fMiner, MapPrevTx& inputsRet, bool& fInvalid); - - /** Sanity check previous transactions, then, if all checks succeed, - mark them as spent by this transaction. - - @param[in] inputs Previous transactions (from FetchInputs) - @param[out] mapTestPool Keeps track of inputs that need to be updated on disk - @param[in] posThisTx Position of this transaction on disk - @param[in] pindexBlock - @param[in] fBlock true if called from ConnectBlock - @param[in] fMiner true if called from CreateNewBlock - @param[in] fStrictPayToScriptHash true if fully validating p2sh transactions - @return Returns true if all checks succeed - */ - bool ConnectInputs(MapPrevTx inputs, - std::map& mapTestPool, const CDiskTxPos& posThisTx, - const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash=true); - bool ClientConnectInputs(); + // Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) + // This does not modify the UTXO set + bool CheckInputs(CCoinsView &view, enum CheckSig_mode csmode, bool fStrictPayToScriptHash=true, bool fStrictEncodings=true) const; + + // Apply the effects of this transaction on the UTXO set represented by view + bool UpdateCoins(CCoinsView &view, CTxUndo &txundo, int nHeight) const; + + // Context-independent validity checks bool CheckTransaction() const; - bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); + + // Try to accept this transaction into the memory pool + bool AcceptToMemoryPool(CCoinsDB& coinsdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); protected: - const CTxOut& GetOutputFor(const CTxIn& input, const MapPrevTx& inputs) const; + static CTxOut GetOutputFor(const CTxIn& input, CCoinsView& mapInputs); }; /** wrapper for CTxOut that provides a more compact serialization */ @@ -752,6 +662,7 @@ public: class CTxUndo { public: + // undo information for all txins std::vector vprevout; IMPLEMENT_SERIALIZE( @@ -763,7 +674,7 @@ public: class CBlockUndo { public: - std::vector vtxundo; + std::vector vtxundo; // for all but the coinbase IMPLEMENT_SERIALIZE( READWRITE(vtxundo); @@ -789,7 +700,7 @@ public: // Flush stdio buffers and commit to disk before returning fflush(fileout); - if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0) + if (!IsInitialBlockDownload()) FileCommit(fileout); return true; @@ -1084,66 +995,16 @@ public: int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } bool IsInMainChain() const { return GetDepthInMainChain() > 0; } int GetBlocksToMaturity() const; - bool AcceptToMemoryPool(CTxDB& txdb, bool fCheckInputs=true); + bool AcceptToMemoryPool(CCoinsDB& coinsdb, bool fCheckInputs=true); bool AcceptToMemoryPool(); }; -/** A txdb record that contains the disk location of a transaction and the - * locations of transactions that spend its outputs. vSpent is really only - * used as a flag, but having the location is very helpful for debugging. - */ -class CTxIndex -{ -public: - CDiskTxPos pos; - std::vector vSpent; - CTxIndex() - { - SetNull(); - } - CTxIndex(const CDiskTxPos& posIn, unsigned int nOutputs) - { - pos = posIn; - vSpent.resize(nOutputs); - } - IMPLEMENT_SERIALIZE - ( - if (!(nType & SER_GETHASH)) - READWRITE(nVersion); - READWRITE(pos); - READWRITE(vSpent); - ) - - void SetNull() - { - pos.SetNull(); - vSpent.clear(); - } - - bool IsNull() - { - return pos.IsNull(); - } - - friend bool operator==(const CTxIndex& a, const CTxIndex& b) - { - return (a.pos == b.pos && - a.vSpent == b.vSpent); - } - - friend bool operator!=(const CTxIndex& a, const CTxIndex& b) - { - return !(a == b); - } - int GetDepthInMainChain() const; - -}; @@ -1155,9 +1016,6 @@ public: * to everyone and the block is added to the block chain. The first transaction * in the block is a special one that creates a new coin owned by the creator * of the block. - * - * Blocks are appended to blk0001.dat files on disk. Their location on disk - * is indexed by CBlockIndex objects in memory. */ class CBlock { @@ -1305,7 +1163,7 @@ public: // Flush stdio buffers and commit to disk before returning fflush(fileout); - if (!IsInitialBlockDownload() || (nBestHeight+1) % 500 == 0) + if (!IsInitialBlockDownload()) FileCommit(fileout); return true; @@ -1360,16 +1218,26 @@ public: } - bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); - bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck=false); + // Undo the effects of this block (with given index) on the UTXO set represented by coins + bool DisconnectBlock(CBlockIndex *pindex, CCoinsView &coins); + + // Apply the effects of this block (with given index) on the UTXO set represented by coins + bool ConnectBlock(CBlockIndex *pindex, CCoinsView &coins, bool fJustCheck=false); + + // Read a block from disk bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); - bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); + + // Make this block (with given index) the new tip of the active block chain + bool SetBestChain(CBlockIndex* pindexNew); + + // Add this block to the block index, and if necessary, switch the active block chain to this bool AddToBlockIndex(const CDiskBlockPos &pos); + + // Context-independent validity checks bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true) const; - bool AcceptBlock(); -private: - bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew); + // Store block on disk + bool AcceptBlock(); }; @@ -1412,7 +1280,7 @@ public: } std::string ToString() const { - return strprintf("CBlockFileInfo(blocks=%u, size=%lu, heights=%u..%u, time=%s..%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst).c_str(), DateTimeStrFormat("%Y-%m-%d", nTimeLast).c_str()); + return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u..%u, time=%s..%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst).c_str(), DateTimeStrFormat("%Y-%m-%d", nTimeLast).c_str()); } // update statistics (does not update nSize) @@ -1466,7 +1334,7 @@ public: pnext = NULL; nHeight = 0; pos.SetNull(); - nUndoPos = (unsigned int)(-1); + nUndoPos = 0; bnChainWork = 0; nVersion = 0; @@ -1499,10 +1367,10 @@ public: CDiskBlockPos GetUndoPos() const { CDiskBlockPos ret = pos; - if (nUndoPos == (unsigned int)(-1)) + if (nUndoPos == 0) ret.SetNull(); else - ret.nPos = nUndoPos; + ret.nPos = nUndoPos - 1; return ret; } @@ -1604,18 +1472,13 @@ class CDiskBlockIndex : public CBlockIndex { public: uint256 hashPrev; - uint256 hashNext; - CDiskBlockIndex() - { + CDiskBlockIndex() { hashPrev = 0; - hashNext = 0; } - explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex) - { + explicit CDiskBlockIndex(CBlockIndex* pindex) : CBlockIndex(*pindex) { hashPrev = (pprev ? pprev->GetBlockHash() : 0); - hashNext = (pnext ? pnext->GetBlockHash() : 0); } IMPLEMENT_SERIALIZE @@ -1623,7 +1486,6 @@ public: if (!(nType & SER_GETHASH)) READWRITE(nVersion); - READWRITE(hashNext); READWRITE(nHeight); READWRITE(pos); READWRITE(nUndoPos); @@ -1654,10 +1516,9 @@ public: { std::string str = "CDiskBlockIndex("; str += CBlockIndex::ToString(); - str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)", + str += strprintf("\n hashBlock=%s, hashPrev=%s)", GetBlockHash().ToString().c_str(), - hashPrev.ToString().substr(0,20).c_str(), - hashNext.ToString().substr(0,20).c_str()); + hashPrev.ToString().substr(0,20).c_str()); return str; } @@ -1815,12 +1676,13 @@ public: std::map mapTx; std::map mapNextTx; - bool accept(CTxDB& txdb, CTransaction &tx, + bool accept(CCoinsDB& coinsdb, CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs); bool addUnchecked(const uint256& hash, CTransaction &tx); bool remove(CTransaction &tx); void clear(); void queryHashes(std::vector& vtxid); + void pruneSpent(const uint256& hash, CCoins &coins); unsigned long size() { @@ -1841,4 +1703,86 @@ public: extern CTxMemPool mempool; +/** Abstract view on the open txout dataset. */ +class CCoinsView +{ +public: + // Retrieve the CCoins (unspent transaction outputs) for a given txid + virtual bool GetCoins(uint256 txid, CCoins &coins); + + // Modify the CCoins for a given txid + virtual bool SetCoins(uint256 txid, const CCoins &coins); + + // Just check whether we have data for a given txid. + // This may (but cannot always) return true for fully spent transactions + virtual bool HaveCoins(uint256 txid); + + // Retrieve the block index whose state this CCoinsView currently represents + virtual CBlockIndex *GetBestBlock(); + + // Modify the currently active block index + virtual bool SetBestBlock(CBlockIndex *pindex); +}; + +/** CCoinsView backed by another CCoinsView */ +class CCoinsViewBacked : public CCoinsView +{ +protected: + CCoinsView *base; + +public: + CCoinsViewBacked(CCoinsView &viewIn); + bool GetCoins(uint256 txid, CCoins &coins); + bool SetCoins(uint256 txid, const CCoins &coins); + bool HaveCoins(uint256 txid); + CBlockIndex *GetBestBlock(); + bool SetBestBlock(CBlockIndex *pindex); + void SetBackend(CCoinsView &viewIn); +}; + + +/** CCoinsView backed by a CCoinsDB */ +class CCoinsViewDB : public CCoinsView +{ +protected: + CCoinsDB &db; +public: + CCoinsViewDB(CCoinsDB &dbIn); + bool GetCoins(uint256 txid, CCoins &coins); + bool SetCoins(uint256 txid, const CCoins &coins); + bool HaveCoins(uint256 txid); + CBlockIndex *GetBestBlock(); + bool SetBestBlock(CBlockIndex *pindex); +}; + +/** CCoinsView that adds a memory cache for transactions to another CCoinsView */ +class CCoinsViewCache : public CCoinsViewBacked +{ +protected: + CBlockIndex *pindexTip; + std::map cacheCoins; + +public: + CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false); + bool GetCoins(uint256 txid, CCoins &coins); + bool SetCoins(uint256 txid, const CCoins &coins); + bool HaveCoins(uint256 txid); + CBlockIndex *GetBestBlock(); + bool SetBestBlock(CBlockIndex *pindex); + bool Flush(); +}; + +/** CCoinsView that brings transactions from a memorypool into view. + It does not check for spendings by memory pool transactions. */ +class CCoinsViewMemPool : public CCoinsViewBacked +{ +protected: + CTxMemPool &mempool; + +public: + CCoinsViewMemPool(CCoinsView &baseIn, CTxMemPool &mempoolIn); + bool GetCoins(uint256 txid, CCoins &coins); + bool HaveCoins(uint256 txid); +}; + #endif diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index efc77e190..dc840b9f8 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -234,7 +234,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) strHTML += "
" + tr("Transaction") + ":
"; strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true); - CTxDB txdb("r"); // To fetch source txouts + CCoinsDB coindb("r"); // To fetch source txouts + CCoinsViewDB coins(coindb); strHTML += "
" + tr("Inputs") + ":"; strHTML += "
    "; @@ -245,8 +246,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) { COutPoint prevout = txin.prevout; - CTransaction prev; - if(txdb.ReadDiskTx(prevout.hash, prev)) + CCoins prev; + if(coins.GetCoins(prevout.hash, prev)) { if (prevout.n < prev.vout.size()) { diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 96712482d..96518c6d8 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -198,7 +198,7 @@ Value getwork(const Array& params, bool fHelp) Value getblocktemplate(const Array& params, bool fHelp) { - if (fHelp || params.size() > 1) + if (fHelp || params.size() != 1) throw runtime_error( "getblocktemplate [params]\n" "Returns data needed to construct a block to work on:\n" @@ -281,7 +281,9 @@ Value getblocktemplate(const Array& params, bool fHelp) Array transactions; map setTxIndex; int i = 0; - CTxDB txdb("r"); + CCoinsDB coindb("r"); + CCoinsViewDB viewdb(coindb); + CCoinsViewCache view(viewdb); BOOST_FOREACH (CTransaction& tx, pblock->vtx) { uint256 txHash = tx.GetHash(); @@ -298,25 +300,21 @@ Value getblocktemplate(const Array& params, bool fHelp) entry.push_back(Pair("hash", txHash.GetHex())); - MapPrevTx mapInputs; - map mapUnused; - bool fInvalid = false; - if (tx.FetchInputs(txdb, mapUnused, false, false, mapInputs, fInvalid)) + Array deps; + BOOST_FOREACH (const CTxIn &in, tx.vin) { - entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(mapInputs) - tx.GetValueOut()))); - - Array deps; - BOOST_FOREACH (MapPrevTx::value_type& inp, mapInputs) - { - if (setTxIndex.count(inp.first)) - deps.push_back(setTxIndex[inp.first]); - } - entry.push_back(Pair("depends", deps)); + if (setTxIndex.count(in.prevout.hash)) + deps.push_back(setTxIndex[in.prevout.hash]); + } + entry.push_back(Pair("depends", deps)); - int64_t nSigOps = tx.GetLegacySigOpCount(); - nSigOps += tx.GetP2SHSigOpCount(mapInputs); - entry.push_back(Pair("sigops", nSigOps)); + int64_t nSigOps = tx.GetLegacySigOpCount(); + if (tx.HaveInputs(view)) + { + entry.push_back(Pair("fee", (int64_t)(tx.GetValueIn(view) - tx.GetValueOut()))); + nSigOps += tx.GetP2SHSigOpCount(view); } + entry.push_back(Pair("sigops", nSigOps)); transactions.push_back(entry); } @@ -364,18 +362,17 @@ Value submitblock(const Array& params, bool fHelp) vector blockData(ParseHex(params[0].get_str())); CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION); - CBlock block; + CBlock pblock; try { - ssBlock >> block; + ssBlock >> pblock; } catch (std::exception &e) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed"); } - bool fAccepted = ProcessBlock(NULL, &block); + bool fAccepted = ProcessBlock(NULL, &pblock); if (!fAccepted) return "rejected"; return Value::null; } - diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index cb5bae62d..c62898316 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -118,7 +118,7 @@ Value getrawtransaction(const Array& params, bool fHelp) CTransaction tx; uint256 hashBlock = 0; - if (!GetTransaction(hash, tx, hashBlock)) + if (!GetTransaction(hash, tx, hashBlock, true)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -335,26 +335,22 @@ Value signrawtransaction(const Array& params, bool fHelp) bool fComplete = true; // Fetch previous transactions (inputs): - map mapPrevOut; - for (unsigned int i = 0; i < mergedTx.vin.size(); i++) + CCoinsView viewDummy; + CCoinsViewCache view(viewDummy); { - CTransaction tempTx; - MapPrevTx mapPrevTx; - CTxDB txdb("r"); - map unused; - bool fInvalid; - - // FetchInputs aborts on failure, so we go one at a time. - tempTx.vin.push_back(mergedTx.vin[i]); - tempTx.FetchInputs(txdb, unused, false, false, mapPrevTx, fInvalid); - - // Copy results into mapPrevOut: - BOOST_FOREACH(const CTxIn& txin, tempTx.vin) - { + LOCK(mempool.cs); + CCoinsDB coinsdb("r"); + CCoinsViewDB viewDB(coinsdb); + CCoinsViewMemPool viewMempool(viewDB, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view + + BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { const uint256& prevHash = txin.prevout.hash; - if (mapPrevTx.count(prevHash) && mapPrevTx[prevHash].second.vout.size()>txin.prevout.n) - mapPrevOut[txin.prevout] = mapPrevTx[prevHash].second.vout[txin.prevout.n].scriptPubKey; + CCoins coins; + view.GetCoins(prevHash, coins); // this is certainly allowed to fail } + + view.SetBackend(viewDummy); // switch back to avoid locking db/mempool too long } // Add previous txouts given in the RPC call: @@ -386,20 +382,19 @@ Value signrawtransaction(const Array& params, bool fHelp) vector pkData(ParseHex(pkHex)); CScript scriptPubKey(pkData.begin(), pkData.end()); - COutPoint outpoint(txid, nOut); - if (mapPrevOut.count(outpoint)) - { - // Complain if scriptPubKey doesn't match - if (mapPrevOut[outpoint] != scriptPubKey) - { + CCoins coins; + if (view.GetCoins(txid, coins)) { + if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { string err("Previous output scriptPubKey mismatch:\n"); - err = err + mapPrevOut[outpoint].ToString() + "\nvs:\n"+ + err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ scriptPubKey.ToString(); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); } + // what todo if txid is known, but the actual output isn't? } - else - mapPrevOut[outpoint] = scriptPubKey; + coins.vout[nOut].scriptPubKey = scriptPubKey; + coins.vout[nOut].nValue = 0; // we don't know the actual output value + view.SetCoins(txid, coins); } } @@ -452,12 +447,13 @@ Value signrawtransaction(const Array& params, bool fHelp) for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { CTxIn& txin = mergedTx.vin[i]; - if (mapPrevOut.count(txin.prevout) == 0) + CCoins coins; + if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { fComplete = false; continue; } - const CScript& prevPubKey = mapPrevOut[txin.prevout]; + const CScript& prevPubKey = coins.vout[txin.prevout.n].scriptPubKey; txin.scriptSig.clear(); // Only sign SIGHASH_SINGLE if there's a corresponding output: @@ -505,24 +501,27 @@ Value sendrawtransaction(const Array& params, bool fHelp) } uint256 hashTx = tx.GetHash(); - // See if the transaction is already in a block - // or in the memory pool: - CTransaction existingTx; - uint256 hashBlock = 0; - if (GetTransaction(hashTx, existingTx, hashBlock)) + bool fHave = false; + CCoins existingCoins; { - if (hashBlock != 0) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("transaction already in block ")+hashBlock.GetHex()); + CCoinsDB coinsdb("r"); + { + CCoinsViewDB coinsviewDB(coinsdb); + CCoinsViewMemPool coinsview(coinsviewDB, mempool); + fHave = coinsview.GetCoins(hashTx, existingCoins); + } + if (!fHave) { + // push to local node + if (!tx.AcceptToMemoryPool(coinsdb)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected"); + } + } + if (fHave) { + if (existingCoins.nHeight < 1000000000) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "transaction already in block chain"); // Not in block, but already in the memory pool; will drop // through to re-relay it. - } - else - { - // push to local node - CTxDB txdb("r"); - if (!tx.AcceptToMemoryPool(txdb)) - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected"); - + } else { SyncWithWallets(tx, NULL, true); } RelayMessage(CInv(MSG_TX, hashTx), tx); diff --git a/src/script.cpp b/src/script.cpp index a840bb1c0..61112b17c 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1719,7 +1719,7 @@ bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTrans return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, nHashType); } -bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType) +bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType) { assert(nIn < txTo.vin.size()); const CTxIn& txin = txTo.vin[nIn]; @@ -1727,9 +1727,6 @@ bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsig return false; const CTxOut& txout = txFrom.vout[txin.prevout.n]; - if (txin.prevout.hash != txFrom.GetHash()) - return false; - return VerifyScript(txin.scriptSig, txout.scriptPubKey, txTo, nIn, fValidatePayToScriptHash, fStrictEncodings, nHashType); } diff --git a/src/script.h b/src/script.h index e7b52d95e..f9df587ca 100644 --- a/src/script.h +++ b/src/script.h @@ -14,6 +14,7 @@ #include "keystore.h" #include "bignum.h" +class CCoins; class CTransaction; /** Signature hash types/flags */ @@ -667,7 +668,7 @@ bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransa bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType); -bool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType); +bool VerifySignature(const CCoins& txFrom, const CTransaction& txTo, unsigned int nIn, bool fValidatePayToScriptHash, bool fStrictEncodings, int nHashType); // Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders, // combine them intelligently and return the result. diff --git a/src/test/DoS_tests.cpp b/src/test/DoS_tests.cpp index d46959312..8235b0bda 100644 --- a/src/test/DoS_tests.cpp +++ b/src/test/DoS_tests.cpp @@ -278,7 +278,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) mst1 = boost::posix_time::microsec_clock::local_time(); for (unsigned int i = 0; i < 5; i++) for (unsigned int j = 0; j < tx.vin.size(); j++) - BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, true, SIGHASH_ALL)); + BOOST_CHECK(VerifySignature(CCoins(orphans[j], MEMPOOL_HEIGHT), tx, j, true, true, SIGHASH_ALL)); mst2 = boost::posix_time::microsec_clock::local_time(); msdiff = mst2 - mst1; long nManyValidate = msdiff.total_milliseconds(); @@ -289,13 +289,13 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) // Empty a signature, validation should fail: CScript save = tx.vin[0].scriptSig; tx.vin[0].scriptSig = CScript(); - BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, true, SIGHASH_ALL)); + BOOST_CHECK(!VerifySignature(CCoins(orphans[0], MEMPOOL_HEIGHT), tx, 0, true, true, SIGHASH_ALL)); tx.vin[0].scriptSig = save; // Swap signatures, validation should fail: std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig); - BOOST_CHECK(!VerifySignature(orphans[0], tx, 0, true, true, SIGHASH_ALL)); - BOOST_CHECK(!VerifySignature(orphans[1], tx, 1, true, true, SIGHASH_ALL)); + BOOST_CHECK(!VerifySignature(CCoins(orphans[0], MEMPOOL_HEIGHT), tx, 0, true, true, SIGHASH_ALL)); + BOOST_CHECK(!VerifySignature(CCoins(orphans[1], MEMPOOL_HEIGHT), tx, 1, true, true, SIGHASH_ALL)); std::swap(tx.vin[0].scriptSig, tx.vin[1].scriptSig); // Exercise -maxsigcachesize code: @@ -305,7 +305,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig) BOOST_CHECK(SignSignature(keystore, orphans[0], tx, 0)); BOOST_CHECK(tx.vin[0].scriptSig != oldSig); for (unsigned int j = 0; j < tx.vin.size(); j++) - BOOST_CHECK(VerifySignature(orphans[j], tx, j, true, true, SIGHASH_ALL)); + BOOST_CHECK(VerifySignature(CCoins(orphans[j], MEMPOOL_HEIGHT), tx, j, true, true, SIGHASH_ALL)); mapArgs.erase("-maxsigcachesize"); LimitOrphanTxSize(0); diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index 9db584c14..35069a3fd 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -105,7 +105,7 @@ BOOST_AUTO_TEST_CASE(sign) { CScript sigSave = txTo[i].vin[0].scriptSig; txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig; - bool sigOK = VerifySignature(txFrom, txTo[i], 0, true, true, 0); + bool sigOK = VerifySignature(CCoins(txFrom, 0), txTo[i], 0, true, true, 0); if (i == j) BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j)); else @@ -243,7 +243,8 @@ BOOST_AUTO_TEST_CASE(switchover) BOOST_AUTO_TEST_CASE(AreInputsStandard) { - std::map > mapInputs; + CCoinsView coinsDummy; + CCoinsViewCache coins(coinsDummy); CBasicKeyStore keystore; CKey key[3]; vector keys; @@ -264,23 +265,29 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) CScript pay1of3; pay1of3.SetMultisig(1, keys); txFrom.vout[0].scriptPubKey = payScriptHash1; + txFrom.vout[0].nValue = 1000; txFrom.vout[1].scriptPubKey = pay1; + txFrom.vout[1].nValue = 2000; txFrom.vout[2].scriptPubKey = pay1of3; + txFrom.vout[2].nValue = 3000; // Last three non-standard: CScript empty; keystore.AddCScript(empty); txFrom.vout[3].scriptPubKey = empty; + txFrom.vout[3].nValue = 4000; // Can't use SetPayToScriptHash, it checks for the empty Script. So: txFrom.vout[4].scriptPubKey << OP_HASH160 << Hash160(empty) << OP_EQUAL; + txFrom.vout[4].nValue = 5000; CScript oneOfEleven; oneOfEleven << OP_1; for (int i = 0; i < 11; i++) oneOfEleven << key[0].GetPubKey(); oneOfEleven << OP_11 << OP_CHECKMULTISIG; txFrom.vout[5].scriptPubKey.SetDestination(oneOfEleven.GetID()); + txFrom.vout[5].nValue = 6000; - mapInputs[txFrom.GetHash()] = make_pair(CTxIndex(), txFrom); + coins.SetCoins(txFrom.GetHash(), CCoins(txFrom, 0)); CTransaction txTo; txTo.vout.resize(1); @@ -297,21 +304,22 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txTo.vin[2].prevout.hash = txFrom.GetHash(); BOOST_CHECK(SignSignature(keystore, txFrom, txTo, 2)); - BOOST_CHECK(txTo.AreInputsStandard(mapInputs)); - BOOST_CHECK_EQUAL(txTo.GetP2SHSigOpCount(mapInputs), 1); + BOOST_CHECK(txTo.AreInputsStandard(coins)); + BOOST_CHECK_EQUAL(txTo.GetP2SHSigOpCount(coins), 1); // Make sure adding crap to the scriptSigs makes them non-standard: for (int i = 0; i < 3; i++) { CScript t = txTo.vin[i].scriptSig; txTo.vin[i].scriptSig = (CScript() << 11) + t; - BOOST_CHECK(!txTo.AreInputsStandard(mapInputs)); + BOOST_CHECK(!txTo.AreInputsStandard(coins)); txTo.vin[i].scriptSig = t; } CTransaction txToNonStd; txToNonStd.vout.resize(1); txToNonStd.vout[0].scriptPubKey.SetDestination(key[1].GetPubKey().GetID()); + txToNonStd.vout[0].nValue = 1000; txToNonStd.vin.resize(2); txToNonStd.vin[0].prevout.n = 4; txToNonStd.vin[0].prevout.hash = txFrom.GetHash(); @@ -320,11 +328,11 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard) txToNonStd.vin[1].prevout.hash = txFrom.GetHash(); txToNonStd.vin[1].scriptSig << OP_0 << Serialize(oneOfEleven); - BOOST_CHECK(!txToNonStd.AreInputsStandard(mapInputs)); - BOOST_CHECK_EQUAL(txToNonStd.GetP2SHSigOpCount(mapInputs), 11); + BOOST_CHECK(!txToNonStd.AreInputsStandard(coins)); + BOOST_CHECK_EQUAL(txToNonStd.GetP2SHSigOpCount(coins), 11); txToNonStd.vin[0].scriptSig.clear(); - BOOST_CHECK(!txToNonStd.AreInputsStandard(mapInputs)); + BOOST_CHECK(!txToNonStd.AreInputsStandard(coins)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 1dacdeaa2..17b925c34 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -173,7 +173,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) // paid to a TX_PUBKEYHASH. // static std::vector -SetupDummyInputs(CBasicKeyStore& keystoreRet, MapPrevTx& inputsRet) +SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet) { std::vector dummyTransactions; dummyTransactions.resize(2); @@ -192,14 +192,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, MapPrevTx& inputsRet) dummyTransactions[0].vout[0].scriptPubKey << key[0].GetPubKey() << OP_CHECKSIG; dummyTransactions[0].vout[1].nValue = 50*CENT; dummyTransactions[0].vout[1].scriptPubKey << key[1].GetPubKey() << OP_CHECKSIG; - inputsRet[dummyTransactions[0].GetHash()] = make_pair(CTxIndex(), dummyTransactions[0]); + coinsRet.SetCoins(dummyTransactions[0].GetHash(), CCoins(dummyTransactions[0], 0)); dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21*CENT; dummyTransactions[1].vout[0].scriptPubKey.SetDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[1].nValue = 22*CENT; dummyTransactions[1].vout[1].scriptPubKey.SetDestination(key[3].GetPubKey().GetID()); - inputsRet[dummyTransactions[1].GetHash()] = make_pair(CTxIndex(), dummyTransactions[1]); + coinsRet.SetCoins(dummyTransactions[1].GetHash(), CCoins(dummyTransactions[1], 0)); return dummyTransactions; } @@ -207,8 +207,9 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, MapPrevTx& inputsRet) BOOST_AUTO_TEST_CASE(test_Get) { CBasicKeyStore keystore; - MapPrevTx dummyInputs; - std::vector dummyTransactions = SetupDummyInputs(keystore, dummyInputs); + CCoinsView coinsDummy; + CCoinsViewCache coins(coinsDummy); + std::vector dummyTransactions = SetupDummyInputs(keystore, coins); CTransaction t1; t1.vin.resize(3); @@ -225,25 +226,24 @@ BOOST_AUTO_TEST_CASE(test_Get) t1.vout[0].nValue = 90*CENT; t1.vout[0].scriptPubKey << OP_1; - BOOST_CHECK(t1.AreInputsStandard(dummyInputs)); - BOOST_CHECK_EQUAL(t1.GetValueIn(dummyInputs), (50+21+22)*CENT); + BOOST_CHECK(t1.AreInputsStandard(coins)); + BOOST_CHECK_EQUAL(t1.GetValueIn(coins), (50+21+22)*CENT); // Adding extra junk to the scriptSig should make it non-standard: t1.vin[0].scriptSig << OP_11; - BOOST_CHECK(!t1.AreInputsStandard(dummyInputs)); + BOOST_CHECK(!t1.AreInputsStandard(coins)); // ... as should not having enough: t1.vin[0].scriptSig = CScript(); - BOOST_CHECK(!t1.AreInputsStandard(dummyInputs)); + BOOST_CHECK(!t1.AreInputsStandard(coins)); } BOOST_AUTO_TEST_CASE(test_GetThrow) { CBasicKeyStore keystore; - MapPrevTx dummyInputs; - std::vector dummyTransactions = SetupDummyInputs(keystore, dummyInputs); - - MapPrevTx missingInputs; + CCoinsView coinsDummy; + CCoinsViewCache coins(coinsDummy); + std::vector dummyTransactions = SetupDummyInputs(keystore, coins); CTransaction t1; t1.vin.resize(3); @@ -257,8 +257,8 @@ BOOST_AUTO_TEST_CASE(test_GetThrow) t1.vout[0].nValue = 90*CENT; t1.vout[0].scriptPubKey << OP_1; - BOOST_CHECK_THROW(t1.AreInputsStandard(missingInputs), runtime_error); - BOOST_CHECK_THROW(t1.GetValueIn(missingInputs), runtime_error); + BOOST_CHECK_THROW(t1.AreInputsStandard(coinsDummy), runtime_error); + BOOST_CHECK_THROW(t1.GetValueIn(coinsDummy), runtime_error); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet.cpp b/src/wallet.cpp index 1a6a1082b..9b2960f64 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -685,7 +685,7 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nReceived, } } -void CWalletTx::AddSupportingTransactions(CTxDB& txdb) +void CWalletTx::AddSupportingTransactions() { vtxPrev.clear(); @@ -696,7 +696,6 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) BOOST_FOREACH(const CTxIn& txin, vin) vWorkQueue.push_back(txin.prevout.hash); - // This critsect is OK because txdb is already open { LOCK(pwallet->cs_wallet); map mapWalletPrev; @@ -720,15 +719,6 @@ void CWalletTx::AddSupportingTransactions(CTxDB& txdb) { tx = *mapWalletPrev[hash]; } - else if (!fClient && txdb.ReadDiskTx(hash, tx)) - { - ; - } - else - { - printf("ERROR: AddSupportingTransactions() : unsupported transaction\n"); - continue; - } int nDepth = tx.SetMerkleBranch(); vtxPrev.push_back(tx); @@ -775,49 +765,36 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) return ret; } -int CWallet::ScanForWalletTransaction(const uint256& hashTx) -{ - CTransaction tx; - tx.ReadFromDisk(COutPoint(hashTx, 0)); - if (AddToWalletIfInvolvingMe(tx, NULL, true, true)) - return 1; - return 0; -} - void CWallet::ReacceptWalletTransactions() { - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); bool fRepeat = true; while (fRepeat) { LOCK(cs_wallet); fRepeat = false; - vector vMissingTx; + bool fMissing = false; BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet) { CWalletTx& wtx = item.second; if (wtx.IsCoinBase() && wtx.IsSpent(0)) continue; - CTxIndex txindex; + CCoins coins; bool fUpdated = false; - if (txdb.ReadTxIndex(wtx.GetHash(), txindex)) + bool fNotFound = coinsdb.ReadCoins(wtx.GetHash(), coins); + if (!fNotFound || wtx.GetDepthInMainChain() > 0) { // Update fSpent if a tx got spent somewhere else by a copy of wallet.dat - if (txindex.vSpent.size() != wtx.vout.size()) - { - printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %"PRIszu" != wtx.vout.size() %"PRIszu"\n", txindex.vSpent.size(), wtx.vout.size()); - continue; - } - for (unsigned int i = 0; i < txindex.vSpent.size(); i++) + for (unsigned int i = 0; i < wtx.vout.size(); i++) { if (wtx.IsSpent(i)) continue; - if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i])) + if ((i >= coins.vout.size() || coins.vout[i].IsNull()) && IsMine(wtx.vout[i])) { wtx.MarkSpent(i); fUpdated = true; - vMissingTx.push_back(txindex.vSpent[i]); + fMissing = true; } } if (fUpdated) @@ -831,10 +808,10 @@ void CWallet::ReacceptWalletTransactions() { // Re-accept any txes of ours that aren't already in a block if (!wtx.IsCoinBase()) - wtx.AcceptWalletTransaction(txdb, false); + wtx.AcceptWalletTransaction(coinsdb, false); } } - if (!vMissingTx.empty()) + if (fMissing) { // TODO: optimize this to scan just part of the block chain? if (ScanForWalletTransactions(pindexGenesisBlock)) @@ -843,21 +820,21 @@ void CWallet::ReacceptWalletTransactions() } } -void CWalletTx::RelayWalletTransaction(CTxDB& txdb) +void CWalletTx::RelayWalletTransaction(CCoinsDB& coinsdb) { BOOST_FOREACH(const CMerkleTx& tx, vtxPrev) { if (!tx.IsCoinBase()) { uint256 hash = tx.GetHash(); - if (!txdb.ContainsTx(hash)) + if (!coinsdb.HaveCoins(hash)) RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); } } if (!IsCoinBase()) { uint256 hash = GetHash(); - if (!txdb.ContainsTx(hash)) + if (!coinsdb.HaveCoins(hash)) { printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str()); RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); @@ -867,8 +844,8 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb) void CWalletTx::RelayWalletTransaction() { - CTxDB txdb("r"); - RelayWalletTransaction(txdb); + CCoinsDB coinsdb("r"); + RelayWalletTransaction(coinsdb); } void CWallet::ResendWalletTransactions() @@ -891,7 +868,7 @@ void CWallet::ResendWalletTransactions() // Rebroadcast any of our txes that aren't in a block yet printf("ResendWalletTransactions()\n"); - CTxDB txdb("r"); + CCoinsDB coinsdb("r"); { LOCK(cs_wallet); // Sort them in chronological order @@ -907,7 +884,7 @@ void CWallet::ResendWalletTransactions() BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted) { CWalletTx& wtx = *item.second; - wtx.RelayWalletTransaction(txdb); + wtx.RelayWalletTransaction(coinsdb); } } } @@ -1162,8 +1139,6 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW { LOCK2(cs_main, cs_wallet); - // txdb must be opened before the mapWallet lock - CTxDB txdb("r"); { nFeeRet = nTransactionFee; loop @@ -1253,7 +1228,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW } // Fill vtxPrev by copying from previous transactions vtxPrev - wtxNew.AddSupportingTransactions(txdb); + wtxNew.AddSupportingTransactions(); wtxNew.fTimeReceivedIsTxTime = true; break; diff --git a/src/wallet.h b/src/wallet.h index c5f124390..e0aa07797 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -166,7 +166,6 @@ public: bool EraseFromWallet(uint256 hash); void WalletUpdateSpent(const CTransaction& prevout); int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false); - int ScanForWalletTransaction(const uint256& hashTx); void ReacceptWalletTransactions(); void ResendWalletTransactions(); int64 GetBalance() const; @@ -659,12 +658,12 @@ public: int64 GetTxTime() const; int GetRequestCount() const; - void AddSupportingTransactions(CTxDB& txdb); + void AddSupportingTransactions(); - bool AcceptWalletTransaction(CTxDB& txdb, bool fCheckInputs=true); + bool AcceptWalletTransaction(CCoinsDB& coinsdb, bool fCheckInputs=true); bool AcceptWalletTransaction(); - void RelayWalletTransaction(CTxDB& txdb); + void RelayWalletTransaction(CCoinsDB& coinsdb); void RelayWalletTransaction(); };