From 2897ea6fa8bba80915363ddc23ebcf66ccaf0399 Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Fri, 18 Jul 2014 12:16:43 +0200 Subject: [PATCH 1/3] Add CTxIndex::GetHeight routine. Split the old code of CTxIndex::GetDepthInMainChain to allow also GetHeight() and GetContainingBlock() to be used. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 1c8c96241..094f37df1 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 From 92e22724ae23efb57d9572b4d478a55c2d2242b5 Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Fri, 18 Jul 2014 12:40:50 +0200 Subject: [PATCH 2/3] Add IsUnspendable method, blocking dust spam outputs. Implement IsUnspendable() as additional check when verifying transactions. This method checks for provably unspendable outputs (will be useful later on for the UTXO set), and it also blocks dust spam outputs created between blocks 39k and 41k from being spent in the future. This enables them to be removed from the UTXO set. --- src/main.cpp | 16 ++++++++++++++++ src/main.h | 10 ++++++++++ src/namecoin.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 094f37df1..4fe98399c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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,13 @@ 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 (IsUnspendable (txPrev->vout[prevout.n], prevHeight, + pindexBlock->nHeight)) + 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..4fc400037 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,11 @@ 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. */ +bool IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight); @@ -942,6 +948,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..74ec38818 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) +{ + 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 + && 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; From 9459649896a8de94d6382aef4a3a6085627bf3cd Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Mon, 22 Sep 2014 18:21:44 +0200 Subject: [PATCH 3/3] Block dust spam tx from mempool before fork point. Don't accept dust spam tx to the mempool even before the fork point. Blocks with them are still considered valid, though. --- src/main.cpp | 6 ++++-- src/main.h | 7 +++++-- src/namecoin.cpp | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4fe98399c..8f5536c7e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1191,9 +1191,11 @@ CTransaction::ConnectInputs (DatabaseSet& dbset, /* Check for unspendable outputs. This is redundant for some of the checks done in IsUnspendable, but it also takes care - of blocking dust spam. */ + 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)) + pindexBlock->nHeight, !fBlock)) return error ("ConnectInputs: previous txo is unspendable"); // If prev is coinbase, check that it's matured diff --git a/src/main.h b/src/main.h index 4fc400037..f72c1547e 100644 --- a/src/main.h +++ b/src/main.h @@ -145,8 +145,11 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock /* 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. */ -bool IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight); + 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); diff --git a/src/namecoin.cpp b/src/namecoin.cpp index 74ec38818..d308b759a 100644 --- a/src/namecoin.cpp +++ b/src/namecoin.cpp @@ -1937,7 +1937,7 @@ int64 GetNameNetFee(const CTransaction& tx) } bool -IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight) +IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight, bool strict) { assert (nPrevHeight <= nHeight); @@ -1951,7 +1951,7 @@ IsUnspendable (const CTxOut& txo, int nPrevHeight, int nHeight) 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 - && nHeight >= FORK_HEIGHT_DUSTSPAM) + && (strict || nHeight >= FORK_HEIGHT_DUSTSPAM)) return true; /* A name_update or name_firstupdate is unspendable if expired. */