diff --git a/src/coins.h b/src/coins.h --- a/src/coins.h +++ b/src/coins.h @@ -70,235 +70,6 @@ } }; -/** - * Pruned version of CTransaction: only retains metadata and unspent transaction outputs - * - * Serialized format: - * - VARINT(nVersion) - DEPRECATED, always zero on new reccords. - * - VARINT(nCode) - * - unspentness bitvector, for vout[2] and further; least significant byte first - * - the non-spent CTxOuts (via CTxOutCompressor) - * - VARINT(nHeight) - * - * The nCode value consists of: - * - bit 0: IsCoinBase() - * - bit 1: vout[0] is not spent - * - bit 2: vout[1] is not spent - * - The higher bits encode N, the number of non-zero bytes in the following bitvector. - * - In case both bit 1 and bit 2 are unset, they encode N-1, as there must be at - * least one non-spent output). - * - * Example: 0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e - * <><><--------------------------------------------><----> - * | \ | / - * version code vout[1] height - * - * - version = 1 - * - code = 4 (vout[1] is not spent, and 0 non-zero bytes of bitvector follow) - * - unspentness bitvector: as 0 non-zero bytes follow, it has length 0 - * - vout[1]: 835800816115944e077fe7c803cfa57f29b36bf87c1d35 - * * 8358: compact amount representation for 60000000000 (600 BCC) - * * 00: special txout type pay-to-pubkey-hash - * * 816115944e077fe7c803cfa57f29b36bf87c1d35: address uint160 - * - height = 203998 - * - * - * Example: 0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b - * <><><--><--------------------------------------------------><----------------------------------------------><----> - * / \ \ | | / - * version code unspentness vout[4] vout[16] height - * - * - version = 1 - * - code = 9 (coinbase, neither vout[0] or vout[1] are unspent, - * 2 (1, +1 because both bit 1 and bit 2 are unset) non-zero bitvector bytes follow) - * - unspentness bitvector: bits 2 (0x04) and 14 (0x4000) are set, so vout[2+2] and vout[14+2] are unspent - * - vout[4]: 86ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4ee - * * 86ef97d579: compact amount representation for 234925952 (2.35 BCC) - * * 00: special txout type pay-to-pubkey-hash - * * 61b01caab50f1b8e9c50a5057eb43c2d9563a4ee: address uint160 - * - vout[16]: bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4 - * * bbd123: compact amount representation for 110397 (0.001 BCC) - * * 00: special txout type pay-to-pubkey-hash - * * 8c988f1a4a4de2161e0f50aac7f17e7f9555caa4: address uint160 - * - height = 120891 - * - * @DISABLE FORMATING FOR THIS COMMENT@ - */ -class CCoins { -public: - //! whether transaction is a coinbase - bool fCoinBase; - - //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs - //! at the end of the array are dropped - std::vector vout; - - //! at which height this transaction was included in the active block chain - int nHeight; - - void FromTx(const CTransaction &tx, int nHeightIn) { - fCoinBase = tx.IsCoinBase(); - vout = tx.vout; - nHeight = nHeightIn; - ClearUnspendable(); - } - - //! construct a CCoins from a CTransaction, at a given height - CCoins(const CTransaction &tx, int nHeightIn) { FromTx(tx, nHeightIn); } - - void Clear() { - fCoinBase = false; - std::vector().swap(vout); - nHeight = 0; - } - - //! empty constructor - CCoins() : fCoinBase(false), vout(0), nHeight(0) {} - - //! remove spent outputs at the end of vout - void Cleanup() { - while (vout.size() > 0 && vout.back().IsNull()) { - vout.pop_back(); - } - - if (vout.empty()) { - std::vector().swap(vout); - } - } - - void ClearUnspendable() { - for (CTxOut &txout : vout) { - if (txout.scriptPubKey.IsUnspendable()) { - txout.SetNull(); - } - } - - Cleanup(); - } - - void swap(CCoins &to) { - std::swap(to.fCoinBase, fCoinBase); - to.vout.swap(vout); - std::swap(to.nHeight, nHeight); - } - - //! equality test - friend bool operator==(const CCoins &a, const CCoins &b) { - // Empty CCoins objects are always equal. - if (a.IsPruned() && b.IsPruned()) { - return true; - } - - return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && - a.vout == b.vout; - } - - friend bool operator!=(const CCoins &a, const CCoins &b) { - return !(a == b); - } - - void CalcMaskSize(unsigned int &nBytes, unsigned int &nNonzeroBytes) const; - - bool IsCoinBase() const { return fCoinBase; } - - template void Serialize(Stream &s) const { - unsigned int nMaskSize = 0, nMaskCode = 0; - CalcMaskSize(nMaskSize, nMaskCode); - bool fFirst = vout.size() > 0 && !vout[0].IsNull(); - bool fSecond = vout.size() > 1 && !vout[1].IsNull(); - assert(fFirst || fSecond || nMaskCode); - unsigned int nCode = 8 * (nMaskCode - (fFirst || fSecond ? 0 : 1)) + - (fCoinBase ? 1 : 0) + (fFirst ? 2 : 0) + - (fSecond ? 4 : 0); - // version - int nVersionDummy = 0; - ::Serialize(s, VARINT(nVersionDummy)); - // header code - ::Serialize(s, VARINT(nCode)); - // spentness bitmask - for (unsigned int b = 0; b < nMaskSize; b++) { - uint8_t chAvail = 0; - for (size_t i = 0; i < 8 && 2 + b * 8 + i < vout.size(); i++) { - if (!vout[2 + b * 8 + i].IsNull()) { - chAvail |= (1 << i); - } - } - ::Serialize(s, chAvail); - } - // txouts themself - for (unsigned int i = 0; i < vout.size(); i++) { - if (!vout[i].IsNull()) { - ::Serialize(s, CTxOutCompressor(REF(vout[i]))); - } - } - // coinbase height - ::Serialize(s, VARINT(nHeight)); - } - - template void Unserialize(Stream &s) { - unsigned int nCode = 0; - // version - int nVersionDummy; - ::Unserialize(s, VARINT(nVersionDummy)); - // header code - ::Unserialize(s, VARINT(nCode)); - fCoinBase = nCode & 1; - std::vector vAvail(2, false); - vAvail[0] = (nCode & 2) != 0; - vAvail[1] = (nCode & 4) != 0; - unsigned int nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1); - // spentness bitmask - while (nMaskCode > 0) { - uint8_t chAvail = 0; - ::Unserialize(s, chAvail); - for (unsigned int p = 0; p < 8; p++) { - bool f = (chAvail & (1 << p)) != 0; - vAvail.push_back(f); - } - if (chAvail != 0) { - nMaskCode--; - } - } - // txouts themself - vout.assign(vAvail.size(), CTxOut()); - for (unsigned int i = 0; i < vAvail.size(); i++) { - if (vAvail[i]) { - ::Unserialize(s, REF(CTxOutCompressor(vout[i]))); - } - } - // coinbase height - ::Unserialize(s, VARINT(nHeight)); - Cleanup(); - } - - //! mark a vout spent - bool Spend(uint32_t nPos); - - //! check whether a particular output is still available - bool IsAvailable(unsigned int nPos) const { - return (nPos < vout.size() && !vout[nPos].IsNull()); - } - - //! check whether the entire CCoins is spent - //! note that only !IsPruned() CCoins can be serialized - bool IsPruned() const { - for (const CTxOut &out : vout) { - if (!out.IsNull()) { - return false; - } - } - return true; - } - - size_t DynamicMemoryUsage() const { - size_t ret = memusage::DynamicUsage(vout); - for (const CTxOut &out : vout) { - ret += RecursiveDynamicUsage(out.scriptPubKey); - } - return ret; - } -}; - class SaltedOutpointHasher { private: /** Salt */ diff --git a/src/coins.cpp b/src/coins.cpp --- a/src/coins.cpp +++ b/src/coins.cpp @@ -10,37 +10,6 @@ #include -/** - * calculate number of bytes for the bitmask, and its number of non-zero bytes - * each bit in the bitmask represents the availability of one output, but the - * availabilities of the first two outputs are encoded separately - */ -void CCoins::CalcMaskSize(unsigned int &nBytes, - unsigned int &nNonzeroBytes) const { - unsigned int nLastUsedByte = 0; - for (unsigned int b = 0; 2 + b * 8 < vout.size(); b++) { - bool fZero = true; - for (unsigned int i = 0; i < 8 && 2 + b * 8 + i < vout.size(); i++) { - if (!vout[2 + b * 8 + i].IsNull()) { - fZero = false; - continue; - } - } - if (!fZero) { - nLastUsedByte = b + 1; - nNonzeroBytes++; - } - } - nBytes += nLastUsedByte; -} - -bool CCoins::Spend(uint32_t nPos) { - if (nPos >= vout.size() || vout[nPos].IsNull()) return false; - vout[nPos].SetNull(); - Cleanup(); - return true; -} - bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; } diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -548,82 +548,6 @@ } } -BOOST_AUTO_TEST_CASE(ccoins_serialization) { - // Good example - CDataStream ss1( - ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), - SER_DISK, CLIENT_VERSION); - CCoins cc1; - ss1 >> cc1; - BOOST_CHECK_EQUAL(cc1.fCoinBase, false); - BOOST_CHECK_EQUAL(cc1.nHeight, 203998); - BOOST_CHECK_EQUAL(cc1.vout.size(), 2); - BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false); - BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true); - BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL); - BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), - HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex( - "816115944e077fe7c803cfa57f29b36bf87c1d35")))))); - - // Good example - CDataStream ss2( - ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eeb" - "bd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), - SER_DISK, CLIENT_VERSION); - CCoins cc2; - ss2 >> cc2; - BOOST_CHECK_EQUAL(cc2.fCoinBase, true); - BOOST_CHECK_EQUAL(cc2.nHeight, 120891); - BOOST_CHECK_EQUAL(cc2.vout.size(), 17); - for (int i = 0; i < 17; i++) { - BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16); - } - BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952); - BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), - HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex( - "61b01caab50f1b8e9c50a5057eb43c2d9563a4ee")))))); - BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397); - BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), - HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex( - "8c988f1a4a4de2161e0f50aac7f17e7f9555caa4")))))); - - // Smallest possible example - CDataStream ssx(SER_DISK, CLIENT_VERSION); - BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), ""); - - CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION); - CCoins cc3; - ss3 >> cc3; - BOOST_CHECK_EQUAL(cc3.fCoinBase, false); - BOOST_CHECK_EQUAL(cc3.nHeight, 0); - BOOST_CHECK_EQUAL(cc3.vout.size(), 1); - BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true); - BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0); - BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0); - - // scriptPubKey that ends beyond the end of the stream - CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION); - try { - CCoins cc4; - ss4 >> cc4; - BOOST_CHECK_MESSAGE(false, "We should have thrown"); - } catch (const std::ios_base::failure &e) { - } - - // Very large scriptPubKey (3*10^9 bytes) past the end of the stream - CDataStream tmp(SER_DISK, CLIENT_VERSION); - uint64_t x = 3000000000ULL; - tmp << VARINT(x); - BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00"); - CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION); - try { - CCoins cc5; - ss5 >> cc5; - BOOST_CHECK_MESSAGE(false, "We should have thrown"); - } catch (const std::ios_base::failure &e) { - } -} - static const COutPoint OUTPOINT; static const CAmount PRUNED = -1; static const CAmount ABSENT = -2; @@ -639,7 +563,7 @@ static const auto CLEAN_FLAGS = {char(0), FRESH}; static const auto ABSENT_FLAGS = {NO_ENTRY}; -void SetCoinsValue(CAmount value, Coin &coin) { +static void SetCoinValue(CAmount value, Coin &coin) { assert(value != ABSENT); coin.Clear(); assert(coin.IsSpent()); @@ -651,7 +575,7 @@ } } -size_t InsertCoinsMapEntry(CCoinsMap &map, CAmount value, char flags) { +size_t InsertCoinMapEntry(CCoinsMap &map, CAmount value, char flags) { if (value == ABSENT) { assert(flags == NO_ENTRY); return 0; @@ -659,13 +583,13 @@ assert(flags != NO_ENTRY); CCoinsCacheEntry entry; entry.flags = flags; - SetCoinsValue(value, entry.coin); + SetCoinValue(value, entry.coin); auto inserted = map.emplace(OUTPOINT, std::move(entry)); assert(inserted.second); return inserted.first->second.coin.DynamicMemoryUsage(); } -void GetCoinsMapEntry(const CCoinsMap &map, CAmount &value, char &flags) { +void GetCoinMapEntry(const CCoinsMap &map, CAmount &value, char &flags) { auto it = map.find(OUTPOINT); if (it == map.end()) { value = ABSENT; @@ -681,9 +605,9 @@ } } -void WriteCoinsViewEntry(CCoinsView &view, CAmount value, char flags) { +void WriteCoinViewEntry(CCoinsView &view, CAmount value, char flags) { CCoinsMap map; - InsertCoinsMapEntry(map, value, flags); + InsertCoinMapEntry(map, value, flags); view.BatchWrite(map, {}); } @@ -691,10 +615,10 @@ public: SingleEntryCacheTest(CAmount base_value, CAmount cache_value, char cache_flags) { - WriteCoinsViewEntry(base, base_value, - base_value == ABSENT ? NO_ENTRY : DIRTY); + WriteCoinViewEntry(base, base_value, + base_value == ABSENT ? NO_ENTRY : DIRTY); cache.usage() += - InsertCoinsMapEntry(cache.map(), cache_value, cache_flags); + InsertCoinMapEntry(cache.map(), cache_value, cache_flags); } CCoinsView root; @@ -711,7 +635,7 @@ CAmount result_value; char result_flags; - GetCoinsMapEntry(test.cache.map(), result_value, result_flags); + GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); } @@ -753,16 +677,16 @@ CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH); } -void CheckSpendCoins(CAmount base_value, CAmount cache_value, - CAmount expected_value, char cache_flags, - char expected_flags) { +void CheckSpendCoin(CAmount base_value, CAmount cache_value, + CAmount expected_value, char cache_flags, + char expected_flags) { SingleEntryCacheTest test(base_value, cache_value, cache_flags); test.cache.SpendCoin(OUTPOINT); test.cache.SelfTest(); CAmount result_value; char result_flags; - GetCoinsMapEntry(test.cache.map(), result_value, result_flags); + GetCoinMapEntry(test.cache.map(), result_value, result_flags); BOOST_CHECK_EQUAL(result_value, expected_value); BOOST_CHECK_EQUAL(result_flags, expected_flags); }; @@ -776,33 +700,33 @@ * Base Cache Result Cache Result * Value Value Value Flags Flags */ - CheckSpendCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); - CheckSpendCoins(ABSENT, PRUNED, PRUNED, 0, DIRTY); - CheckSpendCoins(ABSENT, PRUNED, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(ABSENT, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); - CheckSpendCoins(ABSENT, VALUE2, PRUNED, 0, DIRTY); - CheckSpendCoins(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(ABSENT, VALUE2, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); - CheckSpendCoins(PRUNED, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); - CheckSpendCoins(PRUNED, PRUNED, PRUNED, 0, DIRTY); - CheckSpendCoins(PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); - CheckSpendCoins(PRUNED, VALUE2, PRUNED, 0, DIRTY); - CheckSpendCoins(PRUNED, VALUE2, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(PRUNED, VALUE2, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(PRUNED, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); - CheckSpendCoins(VALUE1, ABSENT, PRUNED, NO_ENTRY, DIRTY); - CheckSpendCoins(VALUE1, PRUNED, PRUNED, 0, DIRTY); - CheckSpendCoins(VALUE1, PRUNED, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); - CheckSpendCoins(VALUE1, VALUE2, PRUNED, 0, DIRTY); - CheckSpendCoins(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY); - CheckSpendCoins(VALUE1, VALUE2, PRUNED, DIRTY, DIRTY); - CheckSpendCoins(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); + CheckSpendCoin(ABSENT, PRUNED, PRUNED, 0, DIRTY); + CheckSpendCoin(ABSENT, PRUNED, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(ABSENT, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(ABSENT, VALUE2, PRUNED, 0, DIRTY); + CheckSpendCoin(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(ABSENT, VALUE2, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(PRUNED, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY); + CheckSpendCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY); + CheckSpendCoin(PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(PRUNED, VALUE2, PRUNED, 0, DIRTY); + CheckSpendCoin(PRUNED, VALUE2, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(PRUNED, VALUE2, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(PRUNED, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(VALUE1, ABSENT, PRUNED, NO_ENTRY, DIRTY); + CheckSpendCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY); + CheckSpendCoin(VALUE1, PRUNED, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY); + CheckSpendCoin(VALUE1, VALUE2, PRUNED, 0, DIRTY); + CheckSpendCoin(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY); + CheckSpendCoin(VALUE1, VALUE2, PRUNED, DIRTY, DIRTY); + CheckSpendCoin(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY); } void CheckAddCoinBase(CAmount base_value, CAmount cache_value, @@ -818,7 +742,7 @@ test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); test.cache.SelfTest(); - GetCoinsMapEntry(test.cache.map(), result_value, result_flags); + GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &e) { result_value = FAIL; result_flags = NO_ENTRY; @@ -869,17 +793,17 @@ CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true); } -void CheckWriteCoins(CAmount parent_value, CAmount child_value, - CAmount expected_value, char parent_flags, - char child_flags, char expected_flags) { +void CheckWriteCoin(CAmount parent_value, CAmount child_value, + CAmount expected_value, char parent_flags, char child_flags, + char expected_flags) { SingleEntryCacheTest test(ABSENT, parent_value, parent_flags); CAmount result_value; char result_flags; try { - WriteCoinsViewEntry(test.cache, child_value, child_flags); + WriteCoinViewEntry(test.cache, child_value, child_flags); test.cache.SelfTest(); - GetCoinsMapEntry(test.cache.map(), result_value, result_flags); + GetCoinMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error &e) { result_value = FAIL; result_flags = NO_ENTRY; @@ -889,7 +813,7 @@ BOOST_CHECK_EQUAL(result_flags, expected_flags); } -BOOST_AUTO_TEST_CASE(ccoins_write) { +BOOST_AUTO_TEST_CASE(coin_write) { /* Check BatchWrite behavior, flushing one entry from a child cache to a * parent cache, and checking the resulting entry in the parent cache * after the write. @@ -897,74 +821,75 @@ * Parent Child Result Parent Child Result * Value Value Value Flags Flags Flags */ - CheckWriteCoins(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY); - CheckWriteCoins(ABSENT, PRUNED, PRUNED, NO_ENTRY, DIRTY, DIRTY); - CheckWriteCoins(ABSENT, PRUNED, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY); - CheckWriteCoins(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH, - DIRTY | FRESH); - CheckWriteCoins(PRUNED, ABSENT, PRUNED, 0, NO_ENTRY, 0); - CheckWriteCoins(PRUNED, ABSENT, PRUNED, FRESH, NO_ENTRY, FRESH); - CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY, NO_ENTRY, DIRTY); - CheckWriteCoins(PRUNED, ABSENT, PRUNED, DIRTY | FRESH, NO_ENTRY, - DIRTY | FRESH); - CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0, DIRTY, DIRTY); - CheckWriteCoins(PRUNED, PRUNED, PRUNED, 0, DIRTY | FRESH, DIRTY); - CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); - CheckWriteCoins(PRUNED, PRUNED, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); - CheckWriteCoins(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY | FRESH, DIRTY); - CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); - CheckWriteCoins(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY | FRESH, - NO_ENTRY); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0, DIRTY, DIRTY); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, FRESH, DIRTY | FRESH, - DIRTY | FRESH); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, - DIRTY | FRESH); - CheckWriteCoins(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH, - DIRTY | FRESH); - CheckWriteCoins(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0); - CheckWriteCoins(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH); - CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY); - CheckWriteCoins(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY, - DIRTY | FRESH); - CheckWriteCoins(VALUE1, PRUNED, PRUNED, 0, DIRTY, DIRTY); - CheckWriteCoins(VALUE1, PRUNED, FAIL, 0, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); - CheckWriteCoins(VALUE1, PRUNED, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); - CheckWriteCoins(VALUE1, PRUNED, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); - CheckWriteCoins(VALUE1, PRUNED, FAIL, DIRTY | FRESH, DIRTY | FRESH, - NO_ENTRY); - CheckWriteCoins(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY); - CheckWriteCoins(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); - CheckWriteCoins(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); - CheckWriteCoins(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); - CheckWriteCoins(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, - DIRTY | FRESH); - CheckWriteCoins(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH, - NO_ENTRY); + CheckWriteCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY); + CheckWriteCoin(ABSENT, PRUNED, PRUNED, NO_ENTRY, DIRTY, DIRTY); + CheckWriteCoin(ABSENT, PRUNED, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY); + CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH, + DIRTY | FRESH); + CheckWriteCoin(PRUNED, ABSENT, PRUNED, 0, NO_ENTRY, 0); + CheckWriteCoin(PRUNED, ABSENT, PRUNED, FRESH, NO_ENTRY, FRESH); + CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY, NO_ENTRY, DIRTY); + CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY | FRESH, NO_ENTRY, + DIRTY | FRESH); + CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY, DIRTY); + CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY | FRESH, DIRTY); + CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); + CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); + CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY | FRESH, DIRTY); + CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); + CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY | FRESH, + NO_ENTRY); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY, DIRTY); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY | FRESH, DIRTY | FRESH); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); + CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH, + DIRTY | FRESH); + CheckWriteCoin(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0); + CheckWriteCoin(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH); + CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY); + CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY, + DIRTY | FRESH); + CheckWriteCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY, DIRTY); + CheckWriteCoin(VALUE1, PRUNED, FAIL, 0, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY); + CheckWriteCoin(VALUE1, PRUNED, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY); + CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY); + CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY | FRESH, DIRTY | FRESH, + NO_ENTRY); + CheckWriteCoin(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY); + CheckWriteCoin(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH); + CheckWriteCoin(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY); + CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY); + CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH); + CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH, + NO_ENTRY); // The checks above omit cases where the child flags are not DIRTY, since // they would be too repetitive (the parent cache is never updated in these // cases). The loop below covers these cases and makes sure the parent cache // is always left unchanged. - for (CAmount parent_value : {ABSENT, PRUNED, VALUE1}) - for (CAmount child_value : {ABSENT, PRUNED, VALUE2}) + for (CAmount parent_value : {ABSENT, PRUNED, VALUE1}) { + for (CAmount child_value : {ABSENT, PRUNED, VALUE2}) { for (char parent_flags : - parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) + parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) { for (char child_flags : - child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) - CheckWriteCoins(parent_value, child_value, parent_value, - parent_flags, child_flags, parent_flags); + child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) { + CheckWriteCoin(parent_value, child_value, parent_value, + parent_flags, child_flags, parent_flags); + } + } + } + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/test_bitcoin_fuzzy.cpp b/src/test/test_bitcoin_fuzzy.cpp --- a/src/test/test_bitcoin_fuzzy.cpp +++ b/src/test/test_bitcoin_fuzzy.cpp @@ -36,7 +36,7 @@ CBANENTRY_DESERIALIZE, CTXUNDO_DESERIALIZE, CBLOCKUNDO_DESERIALIZE, - CCOINS_DESERIALIZE, + COIN_DESERIALIZE, CNETADDR_DESERIALIZE, CSERVICE_DESERIALIZE, CMESSAGEHEADER_DESERIALIZE, @@ -164,10 +164,10 @@ } break; } - case CCOINS_DESERIALIZE: { + case COIN_DESERIALIZE: { try { - CCoins block; - ds >> block; + Coin coin; + ds >> coin; } catch (const std::ios_base::failure &e) { return 0; } diff --git a/src/txdb.cpp b/src/txdb.cpp --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -264,6 +264,60 @@ return true; } +namespace { +//! Legacy class to deserialize pre-pertxout database entries without reindex. +class CCoins { +public: + //! whether transaction is a coinbase + bool fCoinBase; + + //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs + //! at the end of the array are dropped + std::vector vout; + + //! at which height this transaction was included in the active block chain + int nHeight; + + //! empty constructor + CCoins() : fCoinBase(false), vout(0), nHeight(0) {} + + template void Unserialize(Stream &s) { + uint32_t nCode = 0; + // version + int nVersionDummy; + ::Unserialize(s, VARINT(nVersionDummy)); + // header code + ::Unserialize(s, VARINT(nCode)); + fCoinBase = nCode & 1; + std::vector vAvail(2, false); + vAvail[0] = (nCode & 2) != 0; + vAvail[1] = (nCode & 4) != 0; + uint32_t nMaskCode = (nCode / 8) + ((nCode & 6) != 0 ? 0 : 1); + // spentness bitmask + while (nMaskCode > 0) { + uint8_t chAvail = 0; + ::Unserialize(s, chAvail); + for (unsigned int p = 0; p < 8; p++) { + bool f = (chAvail & (1 << p)) != 0; + vAvail.push_back(f); + } + if (chAvail != 0) { + nMaskCode--; + } + } + // txouts themself + vout.assign(vAvail.size(), CTxOut()); + for (size_t i = 0; i < vAvail.size(); i++) { + if (vAvail[i]) { + ::Unserialize(s, REF(CTxOutCompressor(vout[i]))); + } + } + // coinbase height + ::Unserialize(s, VARINT(nHeight)); + } +}; +} + /** * Upgrade the database from older formats. *