From d70d01ff77310d3755891cb43cb2700b553019f4 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 15 Oct 2013 21:50:41 +0400 Subject: [PATCH 1/4] Name bug fix. Hard-fork is set to happen at block 150000 (~76 days from now). Before that bad name_* transactions are allowed, but won't be processed (i.e. won't be saved to name DB). After that they will be rejected, as well as blocks containing them. At first start nameindexfull.dat will be rescanned (slow - be patient). If something goes wrong, delete it to force another rescan. nameindexfull.dat is checked by version (rescanned if below 0.3.72). --- namecoin-qt.pro | 2 +- src/db.cpp | 2 +- src/db.h | 1 + src/init.cpp | 16 +++- src/namecoin.cpp | 191 ++++++++++++++++++++++++++++++++++++++--------- src/serialize.h | 2 +- 6 files changed, 175 insertions(+), 39 deletions(-) diff --git a/namecoin-qt.pro b/namecoin-qt.pro index d8b70cca1..082f81050 100644 --- a/namecoin-qt.pro +++ b/namecoin-qt.pro @@ -1,7 +1,7 @@ TEMPLATE = app TARGET = namecoin-qt macx:TARGET = "Namecoin-Qt" -VERSION = 0.3.71 +VERSION = 0.3.72 INCLUDEPATH += src src/json src/qt QT += network DEFINES += GUI QT_GUI BOOST_THREAD_USE_LIB BOOST_SPIRIT_THREADSAFE diff --git a/src/db.cpp b/src/db.cpp index 7a07b99f4..42c310612 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -157,7 +157,7 @@ void CDB::Close() --mapFileUseCount[strFile]; } -void static CloseDb(const string& strFile) +void CDB::CloseDb(const string& strFile) { CRITICAL_BLOCK(cs_db) { diff --git a/src/db.h b/src/db.h index 4b06d4423..a32e1b623 100644 --- a/src/db.h +++ b/src/db.h @@ -47,6 +47,7 @@ class CDB ~CDB() { Close(); } public: void Close(); + static void CloseDb(const std::string& strFile); private: CDB(const CDB&); void operator=(const CDB&); diff --git a/src/init.cpp b/src/init.cpp index 68a9ded8f..9b23fdd1c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -529,13 +529,27 @@ bool AppInit2(int argc, char* argv[]) RandAddSeedPerfmon(); - filesystem::path nameindexfile = filesystem::path(GetDataDir()) / "nameindexfull.dat"; + const char *name_db_file = "nameindexfull.dat"; + filesystem::path nameindexfile = filesystem::path(GetDataDir()) / name_db_file; if (!filesystem::exists(nameindexfile)) { //PrintConsole("Scanning blockchain for names to create fast index..."); rescanfornames(); //PrintConsole("\n"); } + else // Name bug workaround + { + CDB dbName(name_db_file, "r"); + int nVersion; + if (!dbName.ReadVersion(nVersion) || nVersion < 37200) + { + dbName.Close(); + CDB::CloseDb(name_db_file); + filesystem::remove(nameindexfile); + printf("Name DB is of old version containing a bug. Forcing rescan.\n"); + rescanfornames(); + } + } if (!CreateThread(StartNode, NULL)) wxMessageBox("Error: CreateThread(StartNode) failed", "Namecoin"); diff --git a/src/namecoin.cpp b/src/namecoin.cpp index f526cc894..0ff200844 100644 --- a/src/namecoin.cpp +++ b/src/namecoin.cpp @@ -27,6 +27,9 @@ extern int64 AmountFromValue(const Value& value); extern Object JSONRPCError(int code, const string& message); 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 + map, uint256> mapMyNames; map, set > mapNamePending; @@ -1493,6 +1496,71 @@ bool CNameDB::ScanNames( return true; } +// true - accept, false - reject +bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb) +{ + // Find previous name tx + bool found = false; + int prevOp; + vector > vvchPrevArgs; + + for (int i = 0; i < tx.vin.size(); i++) + { + COutPoint prevout = tx.vin[i].prevout; + CTxIndex txindex; + if (!txdb.ReadTxIndex(prevout.hash, txindex)) + { + printf("NameBugWorkaround WARNING: cannot read tx index of previous tx (hash: %s)\n", prevout.hash.ToString().c_str()); + continue; + } + CTransaction txPrev; + if (!txPrev.ReadFromDisk(txindex.pos)) + { + printf("NameBugWorkaround WARNING: cannot read previous tx from disk (hash: %s)\n", prevout.hash.ToString().c_str()); + continue; + } + + CTxOut& out = txPrev.vout[tx.vin[i].prevout.n]; + if (DecodeNameScript(out.scriptPubKey, prevOp, vvchPrevArgs)) + { + if (found) + return error("NameBugWorkaround WARNING: multiple previous name transactions"); + found = true; + } + } + + if (!found) + return error("NameBugWorkaround WARNING: prev tx not found"); + + vector > vvchArgs; + int op; + int nOut; + + if (!DecodeNameTx(tx, op, nOut, vvchArgs)) + return error("NameBugWorkaround WARNING: cannot decode name tx\n"); + + if (op == OP_NAME_FIRSTUPDATE) + { + // Check hash + const vector &vchHash = vvchPrevArgs[0]; + const vector &vchName = vvchArgs[0]; + const vector &vchRand = vvchArgs[1]; + vector vchToHash(vchRand); + vchToHash.insert(vchToHash.end(), vchName.begin(), vchName.end()); + uint160 hash = Hash160(vchToHash); + if (uint160(vchHash) != hash) + return false; + } + else if (op == OP_NAME_UPDATE) + { + // Check name + if (vvchPrevArgs[0] != vvchArgs[0]) + return false; + } + + return true; +} + bool CNameDB::ReconstructNameIndex() { CTxDB txdb("r"); @@ -1525,13 +1593,21 @@ bool CNameDB::ReconstructNameIndex() continue; nHeight = GetTxPosHeight(txindex.pos); + // Bug workaround + if (nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK) + if (!NameBugWorkaround(tx, txdb)) + { + printf("NameBugWorkaround rejected tx %s at height %d (name %s)\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str()); + continue; + } - vector vtxPos; + vector vtxPos; if (ExistsName(vchName)) { if (!ReadName(vchName, vtxPos)) return error("Rescanfornames() : failed to read from name DB"); } + CNameIndex txPos2; txPos2.nHeight = nHeight; txPos2.vValue = vchValue; @@ -1887,6 +1963,8 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, int nDepth; int64 nNetFee; + bool fBugWorkaround = false; + switch (op) { case OP_NAME_NEW: @@ -1899,6 +1977,28 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, return error("ConnectInputsHook() : got tx %s with fee too low %d", tx.GetHash().GetHex().c_str(), nNetFee); if (!found || prevOp != OP_NAME_NEW) return error("ConnectInputsHook() : name_firstupdate tx without previous name_new tx"); + + { + // Check hash + const vector &vchHash = vvchPrevArgs[0]; + const vector &vchName = vvchArgs[0]; + const vector &vchRand = vvchArgs[1]; + vector vchToHash(vchRand); + vchToHash.insert(vchToHash.end(), vchName.begin(), vchName.end()); + uint160 hash = Hash160(vchToHash); + if (uint160(vchHash) != hash) + { + if (pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) + return error("ConnectInputsHook() : name_firstupdate hash mismatch"); + else + { + // Accept bad transactions before the hard-fork point, but do not write them to name DB + printf("ConnectInputsHook() : name_firstupdate mismatch bug workaround"); + fBugWorkaround = true; + } + } + } + nPrevHeight = GetNameHeight(txdb, vvchArgs[0]); if (nPrevHeight >= 0 && pindexBlock->nHeight - nPrevHeight < GetExpirationDepth(pindexBlock->nHeight)) return error("ConnectInputsHook() : name_firstupdate on an unexpired name"); @@ -1932,6 +2032,20 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, case OP_NAME_UPDATE: if (!found || (prevOp != OP_NAME_FIRSTUPDATE && prevOp != OP_NAME_UPDATE)) return error("name_update tx without previous update tx"); + + // Check name + if (vvchPrevArgs[0] != vvchArgs[0]) + { + if (pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) + return error("ConnectInputsHook() : name_update name mismatch"); + else + { + // Accept bad transactions before the hard-fork point, but do not write them to name DB + printf("ConnectInputsHook() : name_update mismatch bug workaround"); + fBugWorkaround = true; + } + } + // TODO CPU intensive nDepth = CheckTransactionAtRelativeDepth(pindexBlock, vTxindex[nInput], GetExpirationDepth(pindexBlock->nHeight)); if ((fBlock || fMiner) && nDepth < 0) @@ -1941,48 +2055,55 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, return error("ConnectInputsHook() : name transaction has unknown op"); } - if (fBlock) - { - CNameDB dbName("cr+", txdb); + // If fBugWorkaround is in action, do not update name DB (i.e. just silently accept bad tx to avoid early hard-fork) + // Also disallow mining bad txes - dbName.TxnBegin(); + if (fMiner && fBugWorkaround) + return error("ConnectInputsHook(): mismatch bug workaround - should not mine this tx"); - if (op == OP_NAME_FIRSTUPDATE || op == OP_NAME_UPDATE) + if (!fBugWorkaround) + { + if (fBlock) { - //vector vtxPos; - vector vtxPos; - if (dbName.ExistsName(vvchArgs[0])) - { - if (!dbName.ReadName(vvchArgs[0], vtxPos)) - return error("ConnectInputsHook() : failed to read from name DB"); - } - vector vchValue; // add - int nHeight; - uint256 hash; - GetValueOfTxPos(txPos, vchValue, hash, nHeight); - CNameIndex txPos2; - txPos2.nHeight = pindexBlock->nHeight; - txPos2.vValue = vchValue; - txPos2.txPos = txPos; - vtxPos.push_back(txPos2); // fin add - //vtxPos.push_back(txPos); - if (!dbName.WriteName(vvchArgs[0], vtxPos)) + CNameDB dbName("cr+", txdb); + + dbName.TxnBegin(); + + if (op == OP_NAME_FIRSTUPDATE || op == OP_NAME_UPDATE) { - return error("ConnectInputsHook() : failed to write to name DB"); + //vector vtxPos; + vector vtxPos; + if (dbName.ExistsName(vvchArgs[0])) + { + if (!dbName.ReadName(vvchArgs[0], vtxPos)) + return error("ConnectInputsHook() : failed to read from name DB"); + } + vector vchValue; // add + int nHeight; + uint256 hash; + GetValueOfTxPos(txPos, vchValue, hash, nHeight); + CNameIndex txPos2; + txPos2.nHeight = pindexBlock->nHeight; + txPos2.vValue = vchValue; + txPos2.txPos = txPos; + vtxPos.push_back(txPos2); // fin add + //vtxPos.push_back(txPos); + if (!dbName.WriteName(vvchArgs[0], vtxPos)) + { + return error("ConnectInputsHook() : failed to write to name DB"); + } } - } - dbName.TxnCommit(); - } + dbName.TxnCommit(); + } - CRITICAL_BLOCK(cs_main) - { if (fBlock && op != OP_NAME_NEW) - { - std::map, std::set >::iterator mi = mapNamePending.find(vvchArgs[0]); - if (mi != mapNamePending.end()) - mi->second.erase(tx.GetHash()); - } + CRITICAL_BLOCK(cs_main) + { + std::map, std::set >::iterator mi = mapNamePending.find(vvchArgs[0]); + if (mi != mapNamePending.end()) + mi->second.erase(tx.GetHash()); + } } return true; diff --git a/src/serialize.h b/src/serialize.h index 71f095508..2a8e0ff80 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -35,7 +35,7 @@ class CDataStream; class CAutoFile; static const unsigned int MAX_SIZE = 0x02000000; -static const int VERSION = 37100; +static const int VERSION = 37200; static const char* pszSubVer = ""; static const bool VERSION_IS_BETA = false; From 2b90295680979cd0be3ecaf3577edf966f943553 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 15 Oct 2013 22:24:27 +0400 Subject: [PATCH 2/4] Small amendment to the code, otherwise won't compile. Making constructor/destructor of CDB public, so init.cpp can check name DB version without dependency on namecoin.h. --- src/db.cpp | 6 +++--- src/db.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index 42c310612..acc0cc5a5 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -291,7 +291,7 @@ void DBFlush(bool fShutdown) if (nRefCount == 0) { // Move log data to the dat file - CloseDb(strFile); + CDB::CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); printf("%s flush\n", strFile.c_str()); dbenv.lsn_reset(strFile.c_str(), 0); @@ -753,7 +753,7 @@ void ThreadFlushWalletDB(void* parg) int64 nStart = GetTimeMillis(); // Flush wallet.dat so it's self contained - CloseDb(strFile); + CDB::CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(strFile.c_str(), 0); @@ -777,7 +777,7 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) if (!mapFileUseCount.count(wallet.strWalletFile) || mapFileUseCount[wallet.strWalletFile] == 0) { // Flush log data to the dat file - CloseDb(wallet.strWalletFile); + CDB::CloseDb(wallet.strWalletFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(wallet.strWalletFile.c_str(), 0); mapFileUseCount.erase(wallet.strWalletFile); diff --git a/src/db.h b/src/db.h index a32e1b623..24bdd84d1 100644 --- a/src/db.h +++ b/src/db.h @@ -43,6 +43,7 @@ class CDB std::vector vTxn; bool fReadOnly; +public: // FIXME: making consturctor/destructor public, so namedb can be loaded from init.cpp without dependency on namecoin.h explicit CDB(const char* pszFile, const char* pszMode="r+"); ~CDB() { Close(); } public: From 753336a4eb7b6c0121836a6a521c441d3f180432 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 16 Oct 2013 12:51:46 +0400 Subject: [PATCH 3/4] An improved patch. 1. Added check that previous tx was good. Otherwise, while bad tx is rejected, bad on top of bad would be accepted, since the name matches. This however degrades the performance slightly (additional access to name db). 2. Renamed nameindexfull.dat to nameindex.dat. Hopefully this would fix the rescan crash issue of the previous commit. nameindex.dat was used in some older version though. When we agree the patch is final, we should rename to e.g. namedb.dat and make another commit. --- src/db.cpp | 8 +-- src/db.h | 2 - src/init.cpp | 29 ++++------ src/namecoin.cpp | 134 ++++++++++++++++++++++++++++++++++------------- src/namecoin.h | 4 +- 5 files changed, 115 insertions(+), 62 deletions(-) diff --git a/src/db.cpp b/src/db.cpp index acc0cc5a5..1b21a467a 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -157,7 +157,7 @@ void CDB::Close() --mapFileUseCount[strFile]; } -void CDB::CloseDb(const string& strFile) +static void CloseDb(const string& strFile) { CRITICAL_BLOCK(cs_db) { @@ -291,7 +291,7 @@ void DBFlush(bool fShutdown) if (nRefCount == 0) { // Move log data to the dat file - CDB::CloseDb(strFile); + CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); printf("%s flush\n", strFile.c_str()); dbenv.lsn_reset(strFile.c_str(), 0); @@ -753,7 +753,7 @@ void ThreadFlushWalletDB(void* parg) int64 nStart = GetTimeMillis(); // Flush wallet.dat so it's self contained - CDB::CloseDb(strFile); + CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(strFile.c_str(), 0); @@ -777,7 +777,7 @@ bool BackupWallet(const CWallet& wallet, const string& strDest) if (!mapFileUseCount.count(wallet.strWalletFile) || mapFileUseCount[wallet.strWalletFile] == 0) { // Flush log data to the dat file - CDB::CloseDb(wallet.strWalletFile); + CloseDb(wallet.strWalletFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(wallet.strWalletFile.c_str(), 0); mapFileUseCount.erase(wallet.strWalletFile); diff --git a/src/db.h b/src/db.h index 24bdd84d1..4b06d4423 100644 --- a/src/db.h +++ b/src/db.h @@ -43,12 +43,10 @@ class CDB std::vector vTxn; bool fReadOnly; -public: // FIXME: making consturctor/destructor public, so namedb can be loaded from init.cpp without dependency on namecoin.h explicit CDB(const char* pszFile, const char* pszMode="r+"); ~CDB() { Close(); } public: void Close(); - static void CloseDb(const std::string& strFile); private: CDB(const CDB&); void operator=(const CDB&); diff --git a/src/init.cpp b/src/init.cpp index 9b23fdd1c..92b8f1bc5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -529,27 +529,20 @@ bool AppInit2(int argc, char* argv[]) RandAddSeedPerfmon(); - const char *name_db_file = "nameindexfull.dat"; - filesystem::path nameindexfile = filesystem::path(GetDataDir()) / name_db_file; - if (!filesystem::exists(nameindexfile)) - { - //PrintConsole("Scanning blockchain for names to create fast index..."); - rescanfornames(); - //PrintConsole("\n"); - } - else // Name bug workaround + filesystem::path nameindexfile_old = filesystem::path(GetDataDir()) / "nameindexfull.dat"; + filesystem::path nameindexfile = filesystem::path(GetDataDir()) / "nameindex.dat"; + + if (filesystem::exists(nameindexfile_old)) { - CDB dbName(name_db_file, "r"); - int nVersion; - if (!dbName.ReadVersion(nVersion) || nVersion < 37200) - { - dbName.Close(); - CDB::CloseDb(name_db_file); + // If old file exists - delete it and recan + filesystem::remove(nameindexfile_old); + // Also delete new file if it exists together with the old one, as it could be the one from a much older version + if (filesystem::exists(nameindexfile)) filesystem::remove(nameindexfile); - printf("Name DB is of old version containing a bug. Forcing rescan.\n"); - rescanfornames(); - } + rescanfornames(); } + else if (!filesystem::exists(nameindexfile)) + rescanfornames(); if (!CreateThread(StartNode, NULL)) wxMessageBox("Error: CreateThread(StartNode) failed", "Namecoin"); diff --git a/src/namecoin.cpp b/src/namecoin.cpp index 0ff200844..89a086f8a 100644 --- a/src/namecoin.cpp +++ b/src/namecoin.cpp @@ -1497,7 +1497,7 @@ bool CNameDB::ScanNames( } // true - accept, false - reject -bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb) +bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CTransaction *pTxPrev = NULL) { // Find previous name tx bool found = false; @@ -1526,6 +1526,8 @@ bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb) if (found) return error("NameBugWorkaround WARNING: multiple previous name transactions"); found = true; + if (pTxPrev) + *pTxPrev = txPrev; } } @@ -1561,11 +1563,30 @@ bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb) return true; } +// Checks that the last transaction in vtxPos is equal to tx +// The test is optimistic, i.e. it is assumed that tx is correct, +// unless name bug is in action (so outside of the buggy block range +// the test is skipped). This function must be called only if op == OP_NAME_UPDATE, +// becauseduring firstupdate there is no entry in vtxPos +bool CheckNameTxPos(const vector &vtxPos, const CTransaction& tx) +{ + if (vtxPos.empty()) + return false; + if (vtxPos.back().nHeight < BUG_WORKAROUND_BLOCK_START || vtxPos.back().nHeight >= BUG_WORKAROUND_BLOCK) + return true; + + // During the name bug period we must check that tx was really used, + // as it could have been rejected - in this case do not disconnect + const CDiskTxPos &txPos = vtxPos.back().txPos; + CTransaction prevTx; + if (prevTx.ReadFromDisk(txPos) && prevTx == tx) + return true; + return false; +} + bool CNameDB::ReconstructNameIndex() { CTxDB txdb("r"); - vector vchName; - vector vchValue; int nHeight; CTxIndex txindex; CBlockIndex* pindex = pindexGenesisBlock; @@ -1582,20 +1603,27 @@ bool CNameDB::ReconstructNameIndex() { if (tx.nVersion != NAMECOIN_TX_VERSION) continue; + + vector > vvchArgs; + int op; + int nOut; - if(!GetNameOfTx(tx, vchName)) + if (!DecodeNameTx(tx, op, nOut, vvchArgs)) continue; - if(!GetValueOfNameTx(tx, vchValue)) + if (op == OP_NAME_NEW) continue; + const vector &vchName = vvchArgs[0]; + const vector &vchValue = vvchArgs[op == OP_NAME_FIRSTUPDATE ? 2 : 1]; if(!txdb.ReadDiskTx(tx.GetHash(), tx, txindex)) continue; nHeight = GetTxPosHeight(txindex.pos); // Bug workaround + CTransaction txPrev; if (nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK) - if (!NameBugWorkaround(tx, txdb)) + if (!NameBugWorkaround(tx, txdb, &txPrev)) { printf("NameBugWorkaround rejected tx %s at height %d (name %s)\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str()); continue; @@ -1603,11 +1631,17 @@ bool CNameDB::ReconstructNameIndex() vector vtxPos; if (ExistsName(vchName)) - { + { if (!ReadName(vchName, vtxPos)) return error("Rescanfornames() : failed to read from name DB"); } + if (op == OP_NAME_UPDATE && nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK && !CheckNameTxPos(vtxPos, txPrev)) + { + printf("NameBugWorkaround rejected tx %s at height %d (name %s), because previous tx %s was also rejected\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str(), txPrev.GetHash().ToString().c_str()); + continue; + } + CNameIndex txPos2; txPos2.nHeight = nHeight; txPos2.vValue = vchValue; @@ -1814,7 +1848,7 @@ bool CNamecoinHooks::IsMine(const CTransaction& tx, const CTxOut& txout, bool ig if (!DecodeNameScript(txout.scriptPubKey, op, vvch)) return false; - + if (ignore_name_new && op == OP_NAME_NEW) return false; @@ -1925,13 +1959,14 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, bool fBlock, bool fMiner) { - bool nInput; + int nInput; bool found = false; int prevOp; vector > vvchPrevArgs; - for (int i = 0 ; i < tx.vin.size() ; i++) { + for (int i = 0; i < tx.vin.size(); i++) + { CTxOut& out = vTxPrev[i].vout[tx.vin[i].prevout.n]; if (DecodeNameScript(out.scriptPubKey, prevOp, vvchPrevArgs)) { @@ -2063,12 +2098,24 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, if (!fBugWorkaround) { - if (fBlock) - { - CNameDB dbName("cr+", txdb); + CNameDB dbName("cr+", txdb); + dbName.TxnBegin(); - dbName.TxnBegin(); + if (!fBlock && op == OP_NAME_UPDATE) + { + vector vtxPos; + if (dbName.ExistsName(vvchArgs[0])) + { + if (!dbName.ReadName(vvchArgs[0], vtxPos)) + return error("ConnectInputsHook() : failed to read from name DB"); + } + // Valid tx on top of buggy tx: if not in block, reject + if (!CheckNameTxPos(vtxPos, vTxPrev[nInput])) + return error("ConnectInputsHook() : Name bug workaround: tx %s rejected, since previous tx (%s) is not in the name DB\n", tx.GetHash().ToString().c_str(), vTxPrev[nInput].GetHash().ToString().c_str()); + } + if (fBlock) + { if (op == OP_NAME_FIRSTUPDATE || op == OP_NAME_UPDATE) { //vector vtxPos; @@ -2078,32 +2125,45 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, if (!dbName.ReadName(vvchArgs[0], vtxPos)) return error("ConnectInputsHook() : failed to read from name DB"); } - vector vchValue; // add - int nHeight; - uint256 hash; - GetValueOfTxPos(txPos, vchValue, hash, nHeight); - CNameIndex txPos2; - txPos2.nHeight = pindexBlock->nHeight; - txPos2.vValue = vchValue; - txPos2.txPos = txPos; - vtxPos.push_back(txPos2); // fin add - //vtxPos.push_back(txPos); - if (!dbName.WriteName(vvchArgs[0], vtxPos)) + if (op == OP_NAME_UPDATE) { - return error("ConnectInputsHook() : failed to write to name DB"); + if (CheckNameTxPos(vtxPos, vTxPrev[nInput])) + { + vector vchValue; // add + int nHeight; + uint256 hash; + GetValueOfTxPos(txPos, vchValue, hash, nHeight); + CNameIndex txPos2; + txPos2.nHeight = pindexBlock->nHeight; + txPos2.vValue = vchValue; + txPos2.txPos = txPos; + vtxPos.push_back(txPos2); // fin add + //vtxPos.push_back(txPos); + if (!dbName.WriteName(vvchArgs[0], vtxPos)) + { + return error("ConnectInputsHook() : failed to write to name DB"); + } + } + else + { + printf("ConnectInputsHook() : Name bug workaround: tx %s rejected, since previous tx (%s) is not in the name DB\n", tx.GetHash().ToString().c_str(), vTxPrev[nInput].GetHash().ToString().c_str()); + // Valid tx on top of buggy tx: reject only after hard-fork + if (pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) + return false; + } } } - dbName.TxnCommit(); - } - if (fBlock && op != OP_NAME_NEW) - CRITICAL_BLOCK(cs_main) - { - std::map, std::set >::iterator mi = mapNamePending.find(vvchArgs[0]); - if (mi != mapNamePending.end()) - mi->second.erase(tx.GetHash()); - } + if (op != OP_NAME_NEW) + CRITICAL_BLOCK(cs_main) + { + std::map, std::set >::iterator mi = mapNamePending.find(vvchArgs[0]); + if (mi != mapNamePending.end()) + mi->second.erase(tx.GetHash()); + } + } + dbName.TxnCommit(); } return true; @@ -2137,7 +2197,9 @@ bool CNamecoinHooks::DisconnectInputs(CTxDB& txdb, // be empty, since a reorg cannot go that far back. Be safe anyway and do not try to pop if empty. if (vtxPos.size()) { - vtxPos.pop_back(); + if (op == OP_NAME_UPDATE && CheckNameTxPos(vtxPos, tx)) + vtxPos.pop_back(); + // TODO validate that the first pos is the current tx pos } if (!dbName.WriteName(vvchArgs[0], vtxPos)) diff --git a/src/namecoin.h b/src/namecoin.h index b0cb5482b..f9a8fdf33 100644 --- a/src/namecoin.h +++ b/src/namecoin.h @@ -6,11 +6,11 @@ class CNameDB : public CDB protected: bool fHaveParent; public: - CNameDB(const char* pszMode="r+") : CDB("nameindexfull.dat", pszMode) { + CNameDB(const char* pszMode="r+") : CDB("nameindex.dat", pszMode) { fHaveParent = false; } - CNameDB(const char* pszMode, CDB& parent) : CDB("nameindexfull.dat", pszMode) { + CNameDB(const char* pszMode, CDB& parent) : CDB("nameindex.dat", pszMode) { vTxn.push_back(parent.GetTxn()); fHaveParent = true; } From 3027e8c91302e6c5d2ad0489794c77ead61b7dba Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 18 Oct 2013 12:07:05 +0400 Subject: [PATCH 4/4] An important update to the patch. If you upgrade from the previous commit, please delete nameindex.dat to force rescan. There are some additional checks that hurt the performance slightly. We can remove some of them after the hard-fork (150000), if the chain happens to be consistent under a simple set of rules. This is because rules are "transitional" to avoid network fork right now. After the hard-fork we can remove them or simplify to minimal rules that validate the chain up to the block 150k correctly. I took some minor improvements to ReconstructNameIndex from cjdelisle's patch (full patch still crashes for me for some reason, so I do not merge it for now). Thus if you merge my commit on top of his patch, there may be a few conflicts. Sorry for inconvenience. --- src/namecoin.cpp | 343 +++++++++++++++++++++++++++++++---------------- src/namecoin.h | 2 +- src/walletdb.cpp | 5 +- 3 files changed, 232 insertions(+), 118 deletions(-) diff --git a/src/namecoin.cpp b/src/namecoin.cpp index 89a086f8a..20b1352e7 100644 --- a/src/namecoin.cpp +++ b/src/namecoin.cpp @@ -520,7 +520,8 @@ bool GetNameAddress(const CTransaction& tx, std::string& strAddress) int op; int nOut; vector > vvch; - DecodeNameTx(tx, op, nOut, vvch); + if (!DecodeNameTx(tx, op, nOut, vvch, BUG_WORKAROUND_BLOCK)) + return false; const CTxOut& txout = tx.vout[nOut]; const CScript& scriptPubKey = RemoveNameScriptPrefix(txout.scriptPubKey); strAddress = scriptPubKey.GetBitcoinAddress(); @@ -1497,7 +1498,7 @@ bool CNameDB::ScanNames( } // true - accept, false - reject -bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CTransaction *pTxPrev = NULL) +bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CDiskTxPos *pPrevTxPos = NULL) { // Find previous name tx bool found = false; @@ -1526,8 +1527,8 @@ bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CTransaction *pTxPre if (found) return error("NameBugWorkaround WARNING: multiple previous name transactions"); found = true; - if (pTxPrev) - *pTxPrev = txPrev; + if (pPrevTxPos) + *pPrevTxPos = txindex.pos; } } @@ -1538,7 +1539,9 @@ bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CTransaction *pTxPre int op; int nOut; - if (!DecodeNameTx(tx, op, nOut, vvchArgs)) + // NameBugWorkaround is always called for the buggy interval only (before block BUG_WORKAROUND_BLOCK), + // so we provide height as zero + if (!DecodeNameTx(tx, op, nOut, vvchArgs, 0)) return error("NameBugWorkaround WARNING: cannot decode name tx\n"); if (op == OP_NAME_FIRSTUPDATE) @@ -1563,42 +1566,31 @@ bool NameBugWorkaround(const CTransaction& tx, CTxDB &txdb, CTransaction *pTxPre return true; } -// Checks that the last transaction in vtxPos is equal to tx -// The test is optimistic, i.e. it is assumed that tx is correct, -// unless name bug is in action (so outside of the buggy block range -// the test is skipped). This function must be called only if op == OP_NAME_UPDATE, -// becauseduring firstupdate there is no entry in vtxPos -bool CheckNameTxPos(const vector &vtxPos, const CTransaction& tx) +// Check that the last entry in name history matches the given tx pos +bool CheckNameTxPos(const vector &vtxPos, const CDiskTxPos& txPos) { if (vtxPos.empty()) return false; - if (vtxPos.back().nHeight < BUG_WORKAROUND_BLOCK_START || vtxPos.back().nHeight >= BUG_WORKAROUND_BLOCK) - return true; - // During the name bug period we must check that tx was really used, - // as it could have been rejected - in this case do not disconnect - const CDiskTxPos &txPos = vtxPos.back().txPos; - CTransaction prevTx; - if (prevTx.ReadFromDisk(txPos) && prevTx == tx) - return true; - return false; + return vtxPos.back().txPos == txPos; } bool CNameDB::ReconstructNameIndex() { CTxDB txdb("r"); - int nHeight; CTxIndex txindex; CBlockIndex* pindex = pindexGenesisBlock; CRITICAL_BLOCK(pwalletMain->cs_mapWallet) { //CNameDB dbName("cr+", txdb); - TxnBegin(); while (pindex) { + TxnBegin(); CBlock block; block.ReadFromDisk(pindex, true); + int nHeight = pindex->nHeight; + BOOST_FOREACH(CTransaction& tx, block.vtx) { if (tx.nVersion != NAMECOIN_TX_VERSION) @@ -1608,7 +1600,7 @@ bool CNameDB::ReconstructNameIndex() int op; int nOut; - if (!DecodeNameTx(tx, op, nOut, vvchArgs)) + if (!DecodeNameTx(tx, op, nOut, vvchArgs, nHeight)) continue; if (op == OP_NAME_NEW) @@ -1619,11 +1611,10 @@ bool CNameDB::ReconstructNameIndex() if(!txdb.ReadDiskTx(tx.GetHash(), tx, txindex)) continue; - nHeight = GetTxPosHeight(txindex.pos); // Bug workaround - CTransaction txPrev; + CDiskTxPos prevTxPos; if (nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK) - if (!NameBugWorkaround(tx, txdb, &txPrev)) + if (!NameBugWorkaround(tx, txdb, &prevTxPos)) { printf("NameBugWorkaround rejected tx %s at height %d (name %s)\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str()); continue; @@ -1636,9 +1627,9 @@ bool CNameDB::ReconstructNameIndex() return error("Rescanfornames() : failed to read from name DB"); } - if (op == OP_NAME_UPDATE && nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK && !CheckNameTxPos(vtxPos, txPrev)) + if (op == OP_NAME_UPDATE && nHeight >= BUG_WORKAROUND_BLOCK_START && nHeight < BUG_WORKAROUND_BLOCK && !CheckNameTxPos(vtxPos, prevTxPos)) { - printf("NameBugWorkaround rejected tx %s at height %d (name %s), because previous tx %s was also rejected\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str(), txPrev.GetHash().ToString().c_str()); + printf("NameBugWorkaround rejected tx %s at height %d (name %s), because previous tx was also rejected\n", tx.GetHash().ToString().c_str(), nHeight, stringFromVch(vchName).c_str()); continue; } @@ -1656,9 +1647,8 @@ bool CNameDB::ReconstructNameIndex() // ret++; } pindex = pindex->pnext; + TxnCommit(); } - - TxnCommit(); } } @@ -1730,21 +1720,77 @@ bool DecodeNameScript(const CScript& script, int& op, vector >& vvch) +bool DecodeNameTx(const CTransaction& tx, int& op, int& nOut, vector >& vvch, int nHeight) { bool found = false; - for (int i = 0 ; i < tx.vout.size() ; i++) + if (nHeight < 0) { - const CTxOut& out = tx.vout[i]; - if (DecodeNameScript(out.scriptPubKey, op, vvch)) + nHeight = pindexBest->nHeight; + /* + CTxDB txdb("r"); + CTxIndex txindex; + if (txdb.ReadTxIndex(tx.GetHash(), txindex)) { - // If more than one name op, fail - if (found) - return false; - nOut = i; - found = true; + nHeight = GetTxPosHeight(txindex.pos); + if (nHeight == 0) + nHeight = BUG_WORKAROUND_BLOCK; + } + else + nHeight = BUG_WORKAROUND_BLOCK; + */ + } + + // Bug workaround + if (nHeight >= BUG_WORKAROUND_BLOCK) + { + // Strict check - bug disallowed + for (int i = 0; i < tx.vout.size(); i++) + { + const CTxOut& out = tx.vout[i]; + + vector > vvchRead; + + if (DecodeNameScript(out.scriptPubKey, op, vvchRead)) + { + // If more than one name op, fail + if (found) + { + vvch.clear(); + return false; + } + nOut = i; + found = true; + vvch = vvchRead; + } + } + + if (!found) + vvch.clear(); + } + else + { + // Name bug: before hard-fork point, we reproduce the buggy behavior + // of concatenating args (vvchPrevArgs not cleared between calls) + bool fBug = false; + for (int i = 0; i < tx.vout.size(); i++) + { + const CTxOut& out = tx.vout[i]; + + int nOldSize = vvch.size(); + if (DecodeNameScript(out.scriptPubKey, op, vvch)) + { + // If more than one name op, fail + if (found) + return false; + nOut = i; + found = true; + } + if (nOldSize != 0 && vvch.size() != nOldSize) + fBug = true; } + if (fBug) + printf("Name bug warning: argument concatenation happened in tx %s (block height %d)\n", tx.GetHash().GetHex().c_str(), nHeight); } return found; @@ -1773,7 +1819,7 @@ bool GetValueOfNameTx(const CTransaction& tx, vector& value) int op; int nOut; - if (!DecodeNameTx(tx, op, nOut, vvch)) + if (!DecodeNameTx(tx, op, nOut, vvch, -1)) return false; switch (op) @@ -1798,7 +1844,7 @@ int IndexOfNameOutput(const CTransaction& tx) int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvch); + bool good = DecodeNameTx(tx, op, nOut, vvch, -1); if (!good) throw runtime_error("IndexOfNameOutput() : name output not found"); @@ -1819,7 +1865,8 @@ bool CNamecoinHooks::IsMine(const CTransaction& tx) int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvch); + // We do the check under the correct rule set (post-hardfork) + bool good = DecodeNameTx(tx, op, nOut, vvch, BUG_WORKAROUND_BLOCK); if (!good) { @@ -1876,7 +1923,7 @@ void CNamecoinHooks::AcceptToMemoryPool(CTxDB& txdb, const CTransaction& tx) int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvch); + bool good = DecodeNameTx(tx, op, nOut, vvch, BUG_WORKAROUND_BLOCK); if (!good) { @@ -1909,7 +1956,7 @@ bool GetNameOfTx(const CTransaction& tx, vector& name) int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvchArgs); + bool good = DecodeNameTx(tx, op, nOut, vvchArgs, -1); if (!good) return error("GetNameOfTx() : could not decode a namecoin tx"); @@ -1931,7 +1978,7 @@ bool IsConflictedTx(CTxDB& txdb, const CTransaction& tx, vector& int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvchArgs); + bool good = DecodeNameTx(tx, op, nOut, vvchArgs, pindexBest->nHeight); if (!good) return error("IsConflictedTx() : could not decode a namecoin tx"); int nPrevHeight; @@ -1965,17 +2012,50 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, int prevOp; vector > vvchPrevArgs; - for (int i = 0; i < tx.vin.size(); i++) + // Bug workaround + if (fMiner || !fBlock || pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) { - CTxOut& out = vTxPrev[i].vout[tx.vin[i].prevout.n]; - if (DecodeNameScript(out.scriptPubKey, prevOp, vvchPrevArgs)) + // Strict check - bug disallowed + for (int i = 0; i < tx.vin.size(); i++) { - if (found) - return error("ConnectInputHook() : multiple previous name transactions"); - found = true; - nInput = i; + CTxOut& out = vTxPrev[i].vout[tx.vin[i].prevout.n]; + + vector > vvchPrevArgsRead; + + if (DecodeNameScript(out.scriptPubKey, prevOp, vvchPrevArgsRead)) + { + if (found) + return error("ConnectInputHook() : multiple previous name transactions"); + found = true; + nInput = i; + + vvchPrevArgs = vvchPrevArgsRead; + } } } + else + { + // Name bug: before hard-fork point, we reproduce the buggy behavior + // of concatenating args (vvchPrevArgs not cleared between calls) + bool fBug = false; + for (int i = 0; i < tx.vin.size(); i++) + { + CTxOut& out = vTxPrev[i].vout[tx.vin[i].prevout.n]; + + int nOldSize = vvchPrevArgs.size(); + if (DecodeNameScript(out.scriptPubKey, prevOp, vvchPrevArgs)) + { + if (found) + return error("ConnectInputHook() : multiple previous name transactions"); + found = true; + nInput = i; + } + if (nOldSize != 0 && vvchPrevArgs.size() != nOldSize) + fBug = true; + } + if (fBug) + printf("Name bug warning: argument concatenation happened in tx %s (block height %d)\n", tx.GetHash().GetHex().c_str(), pindexBlock->nHeight); + } if (tx.nVersion != NAMECOIN_TX_VERSION) { @@ -1990,7 +2070,7 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvchArgs); + bool good = DecodeNameTx(tx, op, nOut, vvchArgs, pindexBlock->nHeight); if (!good) return error("ConnectInputsHook() : could not decode a namecoin tx"); @@ -2000,11 +2080,24 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, bool fBugWorkaround = false; + // HACK: The following two checks are redundant after hard-fork at block 150000, because it is performed + // in CheckTransaction. However, before that, we do not know height during CheckTransaction + // and cannot apply the right set of rules + if (vvchArgs[0].size() > MAX_NAME_LENGTH) + return error("name transaction with name too long"); + switch (op) { case OP_NAME_NEW: if (found) return error("ConnectInputsHook() : name_new tx pointing to previous namecoin tx"); + + // HACK: The following check is redundant after hard-fork at block 150000, because it is performed + // in CheckTransaction. However, before that, we do not know height during CheckTransaction + // and cannot apply the right set of rules + if (vvchArgs[0].size() != 20) + return error("name_new tx with incorrect hash length"); + break; case OP_NAME_FIRSTUPDATE: nNetFee = GetNameNetFee(tx); @@ -2012,6 +2105,14 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, return error("ConnectInputsHook() : got tx %s with fee too low %d", tx.GetHash().GetHex().c_str(), nNetFee); if (!found || prevOp != OP_NAME_NEW) return error("ConnectInputsHook() : name_firstupdate tx without previous name_new tx"); + + // HACK: The following two checks are redundant after hard-fork at block 150000, because it is performed + // in CheckTransaction. However, before that, we do not know height during CheckTransaction + // and cannot apply the right set of rules + if (vvchArgs[1].size() > 20) + return error("name_firstupdate tx with rand too big"); + if (vvchArgs[2].size() > MAX_VALUE_LENGTH) + return error("name_firstupdate tx with value too long"); { // Check hash @@ -2067,6 +2168,12 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, case OP_NAME_UPDATE: if (!found || (prevOp != OP_NAME_FIRSTUPDATE && prevOp != OP_NAME_UPDATE)) return error("name_update tx without previous update tx"); + + // HACK: The following check is redundant after hard-fork at block 150000, because it is performed + // in CheckTransaction. However, before that, we do not know height during CheckTransaction + // and cannot apply the right set of rules + if (vvchArgs[1].size() > MAX_VALUE_LENGTH) + return error("name_update tx with value too long"); // Check name if (vvchPrevArgs[0] != vvchArgs[0]) @@ -2110,7 +2217,7 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, return error("ConnectInputsHook() : failed to read from name DB"); } // Valid tx on top of buggy tx: if not in block, reject - if (!CheckNameTxPos(vtxPos, vTxPrev[nInput])) + if (!CheckNameTxPos(vtxPos, vTxindex[nInput].pos)) return error("ConnectInputsHook() : Name bug workaround: tx %s rejected, since previous tx (%s) is not in the name DB\n", tx.GetHash().ToString().c_str(), vTxPrev[nInput].GetHash().ToString().c_str()); } @@ -2125,32 +2232,30 @@ bool CNamecoinHooks::ConnectInputs(CTxDB& txdb, if (!dbName.ReadName(vvchArgs[0], vtxPos)) return error("ConnectInputsHook() : failed to read from name DB"); } - if (op == OP_NAME_UPDATE) + + if (op == OP_NAME_UPDATE && !CheckNameTxPos(vtxPos, vTxindex[nInput].pos)) { - if (CheckNameTxPos(vtxPos, vTxPrev[nInput])) - { - vector vchValue; // add - int nHeight; - uint256 hash; - GetValueOfTxPos(txPos, vchValue, hash, nHeight); - CNameIndex txPos2; - txPos2.nHeight = pindexBlock->nHeight; - txPos2.vValue = vchValue; - txPos2.txPos = txPos; - vtxPos.push_back(txPos2); // fin add - //vtxPos.push_back(txPos); - if (!dbName.WriteName(vvchArgs[0], vtxPos)) - { - return error("ConnectInputsHook() : failed to write to name DB"); - } - } + printf("ConnectInputsHook() : Name bug workaround: tx %s rejected, since previous tx (%s) is not in the name DB\n", tx.GetHash().ToString().c_str(), vTxPrev[nInput].GetHash().ToString().c_str()); + // Valid tx on top of buggy tx: reject only after hard-fork + if (pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) + return false; else - { - printf("ConnectInputsHook() : Name bug workaround: tx %s rejected, since previous tx (%s) is not in the name DB\n", tx.GetHash().ToString().c_str(), vTxPrev[nInput].GetHash().ToString().c_str()); - // Valid tx on top of buggy tx: reject only after hard-fork - if (pindexBlock->nHeight >= BUG_WORKAROUND_BLOCK) - return false; - } + fBugWorkaround = true; + } + + if (!fBugWorkaround) + { + vector vchValue; // add + int nHeight; + uint256 hash; + GetValueOfTxPos(txPos, vchValue, hash, nHeight); + CNameIndex txPos2; + txPos2.nHeight = pindexBlock->nHeight; + txPos2.vValue = vchValue; + txPos2.txPos = txPos; + vtxPos.push_back(txPos2); // fin add + if (!dbName.WriteName(vvchArgs[0], vtxPos)) + return error("ConnectInputsHook() : failed to write to name DB"); } } @@ -2180,7 +2285,7 @@ bool CNamecoinHooks::DisconnectInputs(CTxDB& txdb, int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvchArgs); + bool good = DecodeNameTx(tx, op, nOut, vvchArgs, pindexBlock->nHeight); if (!good) return error("DisconnectInputsHook() : could not decode namecoin tx"); if (op == OP_NAME_FIRSTUPDATE || op == OP_NAME_UPDATE) @@ -2197,7 +2302,11 @@ bool CNamecoinHooks::DisconnectInputs(CTxDB& txdb, // be empty, since a reorg cannot go that far back. Be safe anyway and do not try to pop if empty. if (vtxPos.size()) { - if (op == OP_NAME_UPDATE && CheckNameTxPos(vtxPos, tx)) + CTxIndex txindex; + if (!txdb.ReadTxIndex(tx.GetHash(), txindex)) + return error("DisconnectInputsHook() : failed to read tx index"); + + if (vtxPos.back().txPos == txindex.pos) vtxPos.pop_back(); // TODO validate that the first pos is the current tx pos @@ -2220,46 +2329,48 @@ bool CNamecoinHooks::CheckTransaction(const CTransaction& tx) int op; int nOut; - bool good = DecodeNameTx(tx, op, nOut, vvch); - - if (!good) + // HACK: We do not know height here, so we check under both old and new rule sets (before/after hardfork) + // The correct check is duplicated in ConnectInputs. + bool ret[2]; + for (int iter = 0; iter < 2; iter++) { - return error("name transaction has unknown script format"); - } + ret[iter] = true; - if (vvch[0].size() > MAX_NAME_LENGTH) - { - return error("name transaction with name too long"); - } + bool good = DecodeNameTx(tx, op, nOut, vvch, iter == 0 ? 0 : BUG_WORKAROUND_BLOCK); - switch (op) - { - case OP_NAME_NEW: - if (vvch[0].size() != 20) - { - return error("name_new tx with incorrect hash length"); - } - break; - case OP_NAME_FIRSTUPDATE: - if (vvch[1].size() > 20) - { - return error("name_firstupdate tx with rand too big"); - } - if (vvch[2].size() > MAX_VALUE_LENGTH) - { - return error("name_firstupdate tx with value too long"); - } - break; - case OP_NAME_UPDATE: - if (vvch[1].size() > MAX_VALUE_LENGTH) - { - return error("name_update tx with value too long"); - } - break; - default: - return error("name transaction has unknown op"); + if (!good) + { + ret[iter] = error("name transaction has unknown script format"); + continue; + } + + if (vvch[0].size() > MAX_NAME_LENGTH) + { + ret[iter] = error("name transaction with name too long"); + continue; + } + + switch (op) + { + case OP_NAME_NEW: + if (vvch[0].size() != 20) + ret[iter] = error("name_new tx with incorrect hash length"); + break; + case OP_NAME_FIRSTUPDATE: + if (vvch[1].size() > 20) + ret[iter] = error("name_firstupdate tx with rand too big"); + if (vvch[2].size() > MAX_VALUE_LENGTH) + ret[iter] = error("name_firstupdate tx with value too long"); + break; + case OP_NAME_UPDATE: + if (vvch[1].size() > MAX_VALUE_LENGTH) + ret[iter] = error("name_update tx with value too long"); + break; + default: + ret[iter] = error("name transaction has unknown op"); + } } - return true; + return ret[0] || ret[1]; } static string nameFromOp(int op) diff --git a/src/namecoin.h b/src/namecoin.h index f9a8fdf33..4cd67b273 100644 --- a/src/namecoin.h +++ b/src/namecoin.h @@ -84,7 +84,7 @@ bool GetValueOfTxPos(const CDiskTxPos& txPos, std::vector& vchVal int GetDisplayExpirationDepth(int nHeight); bool GetNameOfTx(const CTransaction& tx, std::vector& name); bool GetValueOfNameTx(const CTransaction& tx, std::vector& value); -bool DecodeNameTx(const CTransaction& tx, int& op, int& nOut, std::vector >& vvch); +bool DecodeNameTx(const CTransaction& tx, int& op, int& nOut, std::vector >& vvch, int nHeight); bool DecodeNameScript(const CScript& script, int& op, std::vector > &vvch); bool GetNameAddress(const CTransaction& tx, std::string& strAddress); std::string SendMoneyWithInputTx(CScript scriptPubKey, int64 nValue, int64 nNetFee, CWalletTx& wtxIn, CWalletTx& wtxNew, bool fAskFee); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 43d71ab01..f35ca9918 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -217,7 +217,10 @@ bool CWalletDB::LoadWallet(CWallet* pwallet) #ifdef GUI int op, nOut; std::vector > vvch; - if (DecodeNameTx(wtx, op, nOut, vvch) && op == OP_NAME_FIRSTUPDATE && vvch.size() == 3) + int nHeight; + if (!wtx.GetDepthInMainChain(nHeight)) + nHeight = pindexBest->nHeight + 1; + if (DecodeNameTx(wtx, op, nOut, vvch, nHeight) && op == OP_NAME_FIRSTUPDATE && vvch.size() == 3) { std::vector &vchName = vvch[0]; std::vector &vchRand = vvch[1];