MWEB: Pegout maturity

This commit is contained in:
David Burkett 2022-01-29 20:50:11 -05:00 committed by Loshan T
parent 3e70891275
commit 9a9dfec9ac
9 changed files with 42 additions and 15 deletions

View File

@ -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);
}
}

View File

@ -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<typename Stream>
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<TxOutCompression>(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<TxOutCompression>(out));
}

View File

@ -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;

View File

@ -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)) {

View File

@ -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<unsigned char> 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;

View File

@ -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&) {

View File

@ -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);

View File

@ -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;

View File

@ -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;
}