diff --git a/src/main.cpp b/src/main.cpp index 1c8c96241..8f5536c7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -642,7 +642,7 @@ CTxIndex::GetContainingBlock (const CDiskTxPos& pos) // Read block header CBlock block; - if (!block.ReadFromDisk(pos.nFile, pos.nBlockPos, false)) + if (!block.ReadFromDisk (pos.nFile, pos.nBlockPos, false)) return NULL; // Find the block in the index @@ -1164,6 +1164,15 @@ CTransaction::ConnectInputs (DatabaseSet& dbset, assert (vin[i].fHasPrevInfo); assert (txPrev); + /* Calculate the previous output's height. Note that the value + here should be ensured to be accurate, since IsUnspendable + below depends on it for block validation! Thus we do not + use the cache introduced for priority calculation. */ + int prevHeight = txindex.GetHeight (); + if (prevHeight == -1) + prevHeight = pindexBlock->nHeight; + assert (prevHeight >= 0); + if (prevout.n >= txPrev->vout.size ()) return error ("ConnectInputs: %s prevout.n out of range" " %d %d prev tx %s\n%s", @@ -1180,6 +1189,15 @@ CTransaction::ConnectInputs (DatabaseSet& dbset, return error ("ConnectInputs: %s VerifySignature failed", GetHash ().ToString ().substr (0, 10).c_str ()); + /* Check for unspendable outputs. This is redundant for some + of the checks done in IsUnspendable, but it also takes care + of blocking dust spam. If not validating blocks (i. e., miner + or mempool), we are strict and block dust even before + the hardfork point. */ + if (IsUnspendable (txPrev->vout[prevout.n], prevHeight, + pindexBlock->nHeight, !fBlock)) + return error ("ConnectInputs: previous txo is unspendable"); + // If prev is coinbase, check that it's matured if (txPrev->IsCoinBase ()) for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev) diff --git a/src/main.h b/src/main.h index 576daa043..f72c1547e 100644 --- a/src/main.h +++ b/src/main.h @@ -110,6 +110,7 @@ extern CHooks* hooks; class CReserveKey; class CTxDB; class CTxIndex; +class CTxOut; void RegisterWallet(CWallet* pwalletIn); void UnregisterWallet(CWallet* pwalletIn); @@ -141,6 +142,14 @@ std::string GetWarnings(std::string strFor); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock /*, bool fAllowSlow = false*/ ); +/* Check if a transaction is unspendable. Either because it is provably + prunable, or because it is explicitly blocked due to some rule. nHeight + is the height at which (or later) it will be spent, and nPrevHeight + is the one where it is included in the block chain. The strict parameter + can be turned on for mempool validation (as opposed to blocks) to + reject transactions from the mempool even before a hardfork. */ +bool IsUnspendable (const CTxOut& txo, int nPrevHeight, + int nHeight, bool strict); @@ -942,6 +951,10 @@ class CTxIndex static int GetHeight (const CDiskTxPos& pos); static int GetDepthInMainChain (const CDiskTxPos& pos); + inline int GetHeight () const + { + return GetHeight (pos); + } inline int GetDepthInMainChain () const { return GetDepthInMainChain (pos); diff --git a/src/namecoin.cpp b/src/namecoin.cpp index b71a9417e..d308b759a 100644 --- a/src/namecoin.cpp +++ b/src/namecoin.cpp @@ -28,6 +28,9 @@ template void ConvertTo(Value& value, bool fAllowNull=false); static const int BUG_WORKAROUND_BLOCK_START = 139750; // Bug was not exploited before block 139872, so skip checking earlier blocks static const int BUG_WORKAROUND_BLOCK = 150000; // Point of hard fork +/* Softfork point where the dustspam block was introduced. */ +static const int FORK_HEIGHT_DUSTSPAM = 300000; + map, uint256> mapMyNames; map, set > mapNamePending; @@ -1933,6 +1936,44 @@ int64 GetNameNetFee(const CTransaction& tx) return nFee; } +bool +IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight, bool strict) +{ + assert (nPrevHeight <= nHeight); + + /* An OP_RETURN script as per network fee payments is always + unspendable. Check this. */ + const CScript& script = txo.scriptPubKey; + if (script.size () == 1 && script[0] == OP_RETURN) + return true; + + /* Block dust spam outputs, so that they can be removed from the UTXO + set. We block all 1-Swartz outputs created between blocks 39k and 41k. + Blocking takes effect at the softfork height. */ + if (txo.nValue == 1 && nPrevHeight >= 39000 && nPrevHeight <= 41000 + && (strict || nHeight >= FORK_HEIGHT_DUSTSPAM)) + return true; + + /* A name_update or name_firstupdate is unspendable if expired. */ + if (nHeight >= nPrevHeight + GetExpirationDepth (nHeight)) + { + int op; + std::vector vvch; + if (DecodeNameScript (script, op, vvch)) + switch (op) + { + case OP_NAME_FIRSTUPDATE: + case OP_NAME_UPDATE: + return true; + + default: + break; + } + } + + return false; +} + bool GetValueOfNameTx(const CTransaction& tx, vector& value) { vector > vvch;