From 9a9dfec9ac04cb71faa29c746f6e0f3952fa4437 Mon Sep 17 00:00:00 2001 From: David Burkett Date: Sat, 29 Jan 2022 20:50:11 -0500 Subject: [PATCH] MWEB: Pegout maturity --- src/coins.cpp | 5 ++++- src/coins.h | 19 ++++++++++++++----- src/consensus/consensus.h | 2 ++ src/consensus/tx_verify.cpp | 6 ++++++ src/test/blockfilter_tests.cpp | 8 ++++---- src/test/coins_tests.cpp | 5 +++-- src/txdb.cpp | 2 +- src/txmempool.cpp | 8 +++++++- src/validation.cpp | 2 +- 9 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 4fdf9397e..01eefddde 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -109,10 +109,13 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool bool fCoinbase = tx.IsCoinBase(); const uint256& txid = tx.GetHash(); for (size_t i = 0; i < tx.vout.size(); ++i) { + // MWEB: The first output in the HogEx transaction is the HogAddr. + // The HogAddr is always spent in the next HogEx, so should not be subjected to pegout maturity rules. + bool fPegout = tx.IsHogEx() && i > 0; bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase; // Coinbase transactions can always be overwritten, in order to correctly // deal with the pre-BIP30 occurrences of duplicate coinbase transactions. - cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite); + cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase, fPegout), overwrite); } } diff --git a/src/coins.h b/src/coins.h index ad30fbf5d..b67d92e8f 100644 --- a/src/coins.h +++ b/src/coins.h @@ -40,27 +40,35 @@ public: //! at which height this containing transaction was included in the active block chain uint32_t nHeight : 31; + //! whether output was a pegout from a hogex transaction + bool fPegout; + //! construct a Coin from a CTxOut and height/coinbase information. - Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {} - Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {} + Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn, bool fPegoutIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), fPegout(fPegoutIn) {} + Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn, bool fPegoutIn) : out(outIn), fCoinBase(fCoinBaseIn), nHeight(nHeightIn), fPegout(fPegoutIn) {} void Clear() { out.SetNull(); fCoinBase = false; + fPegout = false; nHeight = 0; } //! empty constructor - Coin() : fCoinBase(false), nHeight(0) { } + Coin() : fCoinBase(false), nHeight(0), fPegout(false) {} bool IsCoinBase() const { return fCoinBase; } + bool IsPegout() const { + return fPegout; + } + template void Serialize(Stream &s) const { assert(!IsSpent()); - uint32_t code = nHeight * uint32_t{2} + fCoinBase; + uint32_t code = nHeight * uint32_t{2} + fCoinBase + (fPegout ? (uint32_t{1} << 31) : uint32_t{0}); ::Serialize(s, VARINT(code)); ::Serialize(s, Using(out)); } @@ -69,7 +77,8 @@ public: void Unserialize(Stream &s) { uint32_t code = 0; ::Unserialize(s, VARINT(code)); - nHeight = code >> 1; + fPegout = code >> 31; + nHeight = (code & ~(uint32_t{1} << 31)) >> 1; fCoinBase = code & 1; ::Unserialize(s, Using(out)); } diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index b64e0cde3..24e3022ce 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -19,6 +19,8 @@ static const unsigned int MAX_BLOCK_WEIGHT = 4000000; static const int64_t MAX_BLOCK_SIGOPS_COST = 80000; /** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */ static const int COINBASE_MATURITY = 100; +/** MWEB: Pegout transaction outputs can only be spent after this number of new blocks (network rule) */ +static const int PEGOUT_MATURITY = 6; static const int WITNESS_SCALE_FACTOR = 4; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 681a89ab4..45afa6e51 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -186,6 +186,12 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } + // If coin is a pegout, check that it's matured + if (coin.IsPegout() && nSpendHeight - coin.nHeight < PEGOUT_MATURITY) { + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-premature-spend-of-pegout", + strprintf("tried to spend pegout output at depth %d", nSpendHeight - coin.nHeight)); + } + // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index 178c26136..5175f0886 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -93,9 +93,9 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test) CBlockUndo block_undo; block_undo.vtxundo.emplace_back(); - block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(500, included_scripts[3]), 1000, true); - block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(600, included_scripts[4]), 10000, false); - block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(700, excluded_scripts[3]), 100000, false); + block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(500, included_scripts[3]), 1000, true, false); + block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(600, included_scripts[4]), 10000, false, false); + block_undo.vtxundo.back().vprevout.emplace_back(CTxOut(700, excluded_scripts[3]), 100000, false, false); BlockFilter block_filter(BlockFilterType::BASIC, block, block_undo); const GCSFilter& filter = block_filter.GetFilter(); @@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test) for (unsigned int ii = 0; ii < prev_scripts.size(); ii++) { std::vector raw_script = ParseHex(prev_scripts[ii].get_str()); CTxOut txout(0, CScript(raw_script.begin(), raw_script.end())); - tx_undo.vprevout.emplace_back(txout, 0, false); + tx_undo.vprevout.emplace_back(txout, 0, false, false); } uint256 prev_filter_header_basic; diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index f40420381..3e5557935 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -413,7 +413,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) // Update the expected result to know about the new output coins assert(tx.vout.size() == 1); const COutPoint outpoint(tx.GetHash(), 0); - result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase()); + result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase(), false); // Call UpdateCoins on the top cache CTxUndo undo; @@ -768,12 +768,13 @@ static void CheckAddCoinBase(CAmount base_value, CAmount cache_value, CAmount mo { SingleEntryCacheTest test(base_value, cache_value, cache_flags); + bool fPegout = false; CAmount result_value; char result_flags; try { CTxOut output; output.nValue = modify_value; - test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase), coinbase); + test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase, fPegout), coinbase); test.cache.SelfTest(); GetCoinsMapEntry(test.cache.map(), result_value, result_flags); } catch (std::logic_error&) { diff --git a/src/txdb.cpp b/src/txdb.cpp index fec3edfca..ace536515 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -401,7 +401,7 @@ bool CCoinsViewDB::Upgrade() { COutPoint outpoint(key.second, 0); for (size_t i = 0; i < old_coins.vout.size(); ++i) { if (!old_coins.vout[i].IsNull() && !old_coins.vout[i].scriptPubKey.IsUnspendable()) { - Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase); + Coin newcoin(std::move(old_coins.vout[i]), old_coins.nHeight, old_coins.fCoinBase, false); outpoint.n = i; CoinEntry entry(&outpoint); batch.Write(entry, newcoin); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 0c2b73196..55f0234da 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -528,6 +528,12 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem txToRemove.insert(it); break; } + + // MWEB: Remove pegout if immature + if (coin.IsPegout() && ((signed long)nMemPoolHeight) - coin.nHeight < PEGOUT_MATURITY) { + txToRemove.insert(it); + break; + } } } if (!validLP) { @@ -917,7 +923,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { CTransactionRef ptx = mempool.get(outpoint.hash); if (ptx) { if (outpoint.n < ptx->vout.size()) { - coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false); + coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false, ptx->mweb_tx.HasPegOut()); return true; } else { return false; diff --git a/src/validation.cpp b/src/validation.cpp index 2210c4f64..59c86260e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -720,7 +720,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) bool fSpendsCoinbase = false; for (const CTxIn &txin : tx.vin) { const Coin &coin = m_view.AccessCoin(txin.prevout); - if (coin.IsCoinBase()) { + if (coin.IsCoinBase() || coin.IsPegout()) { fSpendsCoinbase = true; break; }