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); + return Read(make_pair('c', hash), coins); } -bool CTxDB::AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight) -{ +bool CCoinsDB::WriteCoins(uint256 hash, const CCoins &coins) { assert(!fClient); - - // Add to tx index - uint256 hash = tx.GetHash(); - CTxIndex txindex(pos, tx.vout.size()); - return Write(make_pair(string("tx"), hash), txindex); + if (coins.IsPruned()) + return Erase(make_pair('c', hash)); + else + return Write(make_pair('c', hash), coins); } -bool CTxDB::EraseTxIndex(const CTransaction& tx) +bool CChainDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) { - assert(!fClient); - uint256 hash = tx.GetHash(); - - return Erase(make_pair(string("tx"), hash)); + return Write(make_pair('b', blockindex.GetBlockHash()), blockindex); } -bool CTxDB::ContainsTx(uint256 hash) +bool CCoinsDB::ReadHashBestChain(uint256& hashBestChain) { - assert(!fClient); - return Exists(make_pair(string("tx"), hash)); + return Read('B', hashBestChain); } -bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex) +bool CCoinsDB::WriteHashBestChain(uint256 hashBestChain) { - assert(!fClient); - tx.SetNull(); - if (!ReadTxIndex(hash, txindex)) - return false; - return (tx.ReadFromDisk(txindex.pos)); + return Write('B', hashBestChain); } -bool CTxDB::ReadDiskTx(uint256 hash, CTransaction& tx) +bool CChainDB::ReadBestInvalidWork(CBigNum& bnBestInvalidWork) { - CTxIndex txindex; - return ReadDiskTx(hash, tx, txindex); + return Read('I', bnBestInvalidWork); } -bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex) +bool CChainDB::WriteBestInvalidWork(CBigNum bnBestInvalidWork) { - return ReadDiskTx(outpoint.hash, tx, txindex); + return Write('I', bnBestInvalidWork); } -bool CTxDB::ReadDiskTx(COutPoint outpoint, CTransaction& tx) -{ - CTxIndex txindex; - return ReadDiskTx(outpoint.hash, tx, txindex); +bool CChainDB::WriteBlockFileInfo(int nFile, const CBlockFileInfo &info) { + return Write(make_pair('f', nFile), info); } -bool CTxDB::WriteBlockIndex(const CDiskBlockIndex& blockindex) -{ - return Write(make_pair(string("blockindex"), blockindex.GetBlockHash()), blockindex); +bool CChainDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { + return Read(make_pair('f', nFile), info); } -bool CTxDB::WriteBlockFileInfo(int nFile, const CBlockFileInfo &info) { - return Write(make_pair(string("blockfile"), nFile), info); +bool CChainDB::WriteLastBlockFile(int nFile) { + return Write('l', nFile); } -bool CTxDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { - return Read(make_pair(string("blockfile"), nFile), info); -} - -bool CTxDB::WriteLastBlockFile(int nFile) { - return Write(string("lastblockfile"), 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); -bool CTxMemPool::accept(CTxDB& txdb, CTransaction &tx, bool fCheckInputs, + std::map::iterator it = mapNextTx.lower_bound(COutPoint(hashTx, 0)); + + // 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,60 +882,63 @@ 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); } } + + 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; } @@ -860,8 +947,6 @@ bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock) - - ////////////////////////////////////////////////////////////////////////////// // // CBlock and CBlockIndex @@ -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) +CTxOut CTransaction::GetOutputFor(const CTxIn& input, CCoinsView& view) { - // 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) -{ - // 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"); - return txPrev.vout[input.prevout.n]; + const CTxOut &out = coins.vout[input.prevout.n]; + if (out.IsNull()) + throw std::runtime_error("CTransaction::GetOutputFor() : already spent"); + + 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; - - 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())); - - // 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); - - // 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")); - - } - // 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; + const COutPoint &prevout = vin[i].prevout; + CCoins coins; + if (!inputs.GetCoins(prevout.hash, coins)) + return error("CheckInputs() : cannot find prevout tx"); // 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()); + if (!coins.IsAvailable(prevout.n)) + return error("CheckInputs() : %s prev tx already used", GetHash().ToString().substr(0,10).c_str()); - // 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)) - { - // 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()); - - return DoS(100,error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); - } + // If prev is coinbase, check that it's matured + 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); } - // Mark outpoints as spent - txindex.vSpent[prevout.n] = posThisTx; + // Check for negative or overflow input values + 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")); - // 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())); + 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("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str())); + return DoS(100, error("CheckInputs() : %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 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. + + // 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); + + // Verify signature + 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(coins, *this, i, false, fStrictEncodings, 0)) + return error("CheckInputs() : %s P2SH VerifySignature failed", GetHash().ToString().substr(0,10).c_str()); + + return DoS(100,error("CheckInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,10).c_str())); + } + } + } } 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"); - // Find the fork - CBlockIndex* pfork = pindexBest; + // 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; + } + + // 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); + // 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; - /** Sanity check previous transactions, then, if all checks succeed, - mark them as spent by this transaction. + // Apply the effects of this transaction on the UTXO set represented by view + bool UpdateCoins(CCoinsView &view, CTxUndo &txundo, int nHeight) const; - @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(); + // 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); - bool ReadFromDisk(const CBlockIndex* pindex, bool fReadTransactions=true); - bool SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew); - bool AddToBlockIndex(const CDiskBlockPos &pos); - bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true) const; - bool AcceptBlock(); + // Undo the effects of this block (with given index) on the UTXO set represented by coins + bool DisconnectBlock(CBlockIndex *pindex, CCoinsView &coins); -private: - bool SetBestChainInner(CTxDB& txdb, CBlockIndex *pindexNew); + // 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); + + // 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; + + // 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)); - - int64_t nSigOps = tx.GetLegacySigOpCount(); - nSigOps += tx.GetP2SHSigOpCount(mapInputs); - entry.push_back(Pair("sigops", nSigOps)); + if (setTxIndex.count(in.prevout.hash)) + deps.push_back(setTxIndex[in.prevout.hash]); } + entry.push_back(Pair("depends", deps)); + + 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; + LOCK(mempool.cs); + CCoinsDB coinsdb("r"); + CCoinsViewDB viewDB(coinsdb); + CCoinsViewMemPool viewMempool(viewDB, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view - // 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) - { + 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(); };