/* undo is set if the change is due to disconnecting blocks / going back in time. The ordinary case (!undo) means that we update the name normally, going forward in time. This is important for keeping track of the name history. */ void CCoinsViewCache::SetName(const valtype &name, const CNameData& data, bool undo) { CNameData oldData; if (GetName(name, oldData)) { cacheNames.removeExpireIndex(name, oldData.getHeight()); /* Update the name history. If we are undoing, we expect that the top history item matches the data being set now. If we are not undoing, push the overwritten data onto the history stack. Note that we only have to do this if the name already existed in the database. Otherwise, no special action is required for the name history. */ if (fNameHistory) { CNameHistory history; if (!GetNameHistory(name, history)) { /* Ensure that the history stack is indeed (still) empty and was not modified by the failing GetNameHistory call. */ assert(history.empty()); } if (undo) history.pop(data); else history.push(oldData); cacheNames.setHistory(name, history); } } else assert (!undo); cacheNames.set(name, data); cacheNames.addExpireIndex(name, data.getHeight()); }
void refreshName(const std::vector<unsigned char> &inName) { LOCK(cs_main); NameTableEntry nameObj(ValtypeToString(inName), std::string(""), NameTableEntry::NAME_NON_EXISTING); CNameData data; { LOCK (cs_main); if (!pcoinsTip->GetName (inName, data)) { LogPrintf ("name not found: '%s'\n", ValtypeToString (inName).c_str()); return; } nameObj = NameTableEntry(ValtypeToString(inName), ValtypeToString(data.getValue ()), data.getHeight ()); } // Find name in model QList<NameTableEntry>::iterator lower = qLowerBound( cachedNameTable.begin(), cachedNameTable.end(), nameObj.name, NameTableEntryLessThan()); QList<NameTableEntry>::iterator upper = qUpperBound( cachedNameTable.begin(), cachedNameTable.end(), nameObj.name, NameTableEntryLessThan()); bool inModel = (lower != upper); if (inModel) { // In model - update or delete if (nameObj.nHeight != NameTableEntry::NAME_NON_EXISTING) { LogPrintf ("refreshName result : %s - refreshed in the table\n", qPrintable(nameObj.name)); updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_UPDATED); } else { LogPrintf("refreshName result : %s - deleted from the table\n", qPrintable(nameObj.name)); updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_DELETED); } } else { // Not in model - add or do nothing if (nameObj.nHeight != NameTableEntry::NAME_NON_EXISTING) { LogPrintf("refreshName result : %s - added to the table\n", qPrintable(nameObj.name)); updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_NEW); } else { LogPrintf("refreshName result : %s - ignored (not in the table)\n", qPrintable(nameObj.name)); } } }
CNameData NameIterationTester::getNextData () { const CScript addr = getTestAddress (); const valtype name = ValtypeFromString ("dummy"); const valtype value = ValtypeFromString ("abc"); const CScript updateScript = CNameScript::buildNameUpdate (addr, name, value); const CNameScript nameOp(updateScript); CNameData res; res.fromScript (++counter, COutPoint (uint256 (), 0), nameOp); return res; }
void CCoinsViewCache::DeleteName(const valtype &name) { CNameData oldData; if (GetName(name, oldData)) cacheNames.removeExpireIndex(name, oldData.getHeight()); else assert(false); if (fNameHistory) { /* When deleting a name, the history should already be clean. */ CNameHistory history; assert (!GetNameHistory(name, history) || history.empty()); } cacheNames.remove(name); }
void ApplyGameTransactions (const std::vector<CTransaction>& vGameTx, const StepResult& stepResult, unsigned nHeight, CValidationState& state, CCoinsViewCache& view, CBlockUndo& undo) { for (unsigned i = 0; i < vGameTx.size (); ++i) { undo.vtxundo.push_back (CTxUndo ()); UpdateCoins (vGameTx[i], state, view, undo.vtxundo.back (), nHeight); } /* Update name db for killed players. */ const PlayerSet& victims = stepResult.GetKilledPlayers (); if (!victims.empty ()) { assert (!vGameTx.empty ()); const CTransaction& txKills = vGameTx.front (); assert (txKills.vout.empty ()); assert (txKills.vin.size () == victims.size ()); BOOST_FOREACH(const PlayerID& name, victims) { const valtype& vchName = ValtypeFromString (name); LogPrint ("names", "Killing player at height %d: %s\n", nHeight, name.c_str ()); CNameTxUndo opUndo; opUndo.fromOldState (vchName, view); undo.vnameundo.push_back (opUndo); CNameData data; data.setDead (nHeight, txKills.GetHash ()); view.SetName (vchName, data, false); } }
/** * Return name info object for a CNameData object. * @param name The name. * @param data The name's data. * @return A JSON object to return. */ UniValue getNameInfo (const valtype& name, const CNameData& data) { return getNameInfo (name, data.getValue (), data.getUpdateOutpoint (), data.getAddress (), data.getHeight ()); }
UniValue name_filter (const UniValue& params, bool fHelp) { if (fHelp || params.size () > 5) throw std::runtime_error ( "name_filter (\"regexp\" (\"maxage\" (\"from\" (\"nb\" (\"stat\")))))\n" "\nScan and list names matching a regular expression.\n" "\nArguments:\n" "1. \"regexp\" (string, optional) filter names with this regexp\n" "2. \"maxage\" (numeric, optional, default=36000) only consider names updated in the last \"maxage\" blocks; 0 means all names\n" "3. \"from\" (numeric, optional, default=0) return from this position onward; index starts at 0\n" "4. \"nb\" (numeric, optional, default=0) return only \"nb\" entries; 0 means all\n" "5. \"stat\" (string, optional) if set to the string \"stat\", print statistics instead of returning the names\n" "\nResult:\n" "[\n" + getNameInfoHelp (" ", ",") + " ...\n" "]\n" "\nExamples:\n" + HelpExampleCli ("name_filter", "\"\" 5") + HelpExampleCli ("name_filter", "\"^id/\"") + HelpExampleCli ("name_filter", "\"^id/\" 36000 0 0 \"stat\"") + HelpExampleRpc ("name_scan", "\"^d/\"") ); if (IsInitialBlockDownload ()) throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Namecoin is downloading blocks..."); /* ********************** */ /* Interpret parameters. */ bool haveRegexp(false); boost::xpressive::sregex regexp; int maxage(36000), from(0), nb(0); bool stats(false); if (params.size () >= 1) { haveRegexp = true; regexp = boost::xpressive::sregex::compile (params[0].get_str ()); } if (params.size () >= 2) maxage = params[1].get_int (); if (maxage < 0) throw JSONRPCError (RPC_INVALID_PARAMETER, "'maxage' should be non-negative"); if (params.size () >= 3) from = params[2].get_int (); if (from < 0) throw JSONRPCError (RPC_INVALID_PARAMETER, "'from' should be non-negative"); if (params.size () >= 4) nb = params[3].get_int (); if (nb < 0) throw JSONRPCError (RPC_INVALID_PARAMETER, "'nb' should be non-negative"); if (params.size () >= 5) { if (params[4].get_str () != "stat") throw JSONRPCError (RPC_INVALID_PARAMETER, "fifth argument must be the literal string 'stat'"); stats = true; } /* ******************************************* */ /* Iterate over names to build up the result. */ UniValue names(UniValue::VARR); unsigned count(0); LOCK (cs_main); valtype name; CNameData data; std::unique_ptr<CNameIterator> iter(pcoinsTip->IterateNames ()); while (iter->next (name, data)) { const int age = chainActive.Height () - data.getHeight (); assert (age >= 0); if (maxage != 0 && age >= maxage) continue; if (haveRegexp) { const std::string nameStr = ValtypeToString (name); boost::xpressive::smatch matches; if (!boost::xpressive::regex_search (nameStr, matches, regexp)) continue; } if (from > 0) { --from; continue; } assert (from == 0); if (stats) ++count; else names.push_back (getNameInfo (name, data)); if (nb > 0) { --nb; if (nb == 0) break; } } /* ********************************************************** */ /* Return the correct result (take stats mode into account). */ if (stats) { UniValue res(UniValue::VOBJ); res.push_back (Pair ("blocks", chainActive.Height ())); res.push_back (Pair ("count", static_cast<int> (count))); return res; } return names; }
bool CCoinsViewDB::ValidateNameDB(CGameDB& gameDb) const { /* Skip for genesis block, since there is no game state available yet (test would fail below). There's not really anything to verify for the genesis block anyway. */ const uint256 blockHash = GetBestBlock(); if (blockHash.IsNull()) return true; /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ boost::scoped_ptr<CDBIterator> pcursor(const_cast<CDBWrapper*>(&db)->NewIterator()); pcursor->SeekToFirst(); /* Loop over the total database and read interesting things to memory. We later use that to check everything against each other. */ std::set<valtype> namesTotal; std::set<valtype> namesInDB; std::set<valtype> namesWithHistory; std::map<valtype, CAmount> namesInUTXO; for (; pcursor->Valid(); pcursor->Next()) { boost::this_thread::interruption_point(); char chType; if (!pcursor->GetKey(chType)) continue; switch (chType) { case DB_COINS: { CCoins coins; if (!pcursor->GetValue(coins)) return error("%s : failed to read coins", __func__); BOOST_FOREACH(const CTxOut& txout, coins.vout) if (!txout.IsNull()) { const CNameScript nameOp(txout.scriptPubKey); if (nameOp.isNameOp() && nameOp.isAnyUpdate()) { const valtype& name = nameOp.getOpName(); if (namesInUTXO.count(name) > 0) return error("%s : name %s duplicated in UTXO set", __func__, ValtypeToString(name).c_str()); namesInUTXO.insert(std::make_pair(nameOp.getOpName(), txout.nValue)); } } break; } case DB_NAME: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME) return error("%s : failed to read DB_NAME key", __func__); const valtype& name = key.second; CNameData data; if (!pcursor->GetValue(data)) return error("%s : failed to read name value", __func__); if (namesTotal.count(name) > 0) return error("%s : name %s duplicated in name index", __func__, ValtypeToString(name).c_str()); namesTotal.insert(name); assert(namesInDB.count(name) == 0); if (!data.isDead ()) namesInDB.insert(name); break; } case DB_NAME_HISTORY: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_HISTORY) return error("%s : failed to read DB_NAME_HISTORY key", __func__); const valtype& name = key.second; if (namesWithHistory.count(name) > 0) return error("%s : name %s has duplicate history", __func__, ValtypeToString(name).c_str()); namesWithHistory.insert(name); break; } default: break; } } std::map<valtype, CAmount> namesInGame; GameState state(Params().GetConsensus()); if (!gameDb.get(blockHash, state)) return error("%s : failed to read game state", __func__); for (PlayerStateMap::const_iterator mi = state.players.begin(); mi != state.players.end(); ++mi) { const valtype cur = ValtypeFromString(mi->first); if (namesInGame.count(cur) > 0) return error("%s : name %s is duplicate in the game state", __func__, mi->first.c_str()); namesInGame.insert(std::make_pair(cur, mi->second.lockedCoins)); } /* Now verify the collected data. */ assert (namesTotal.size() >= namesInDB.size()); if (namesInGame != namesInUTXO) return error("%s : game state and name DB mismatch", __func__); BOOST_FOREACH(const valtype& name, namesInDB) if (namesInUTXO.count(name) == 0) return error("%s : name '%s' in DB but not UTXO set", __func__, ValtypeToString(name).c_str()); BOOST_FOREACH(const PAIRTYPE(valtype, CAmount)& pair, namesInUTXO) if (namesInDB.count(pair.first) == 0) return error("%s : name '%s' in UTXO set but not DB", __func__, ValtypeToString(pair.first).c_str()); if (fNameHistory) { BOOST_FOREACH(const valtype& name, namesWithHistory) if (namesTotal.count(name) == 0) return error("%s : history entry for name '%s' not in main DB", __func__, ValtypeToString(name).c_str()); } else if (!namesWithHistory.empty ())
bool CreateGameTransactions (const CCoinsView& view, unsigned nHeight, const StepResult& stepResult, std::vector<CTransaction>& vGameTx) { vGameTx.clear (); /* Destroy name-coins of killed players. */ bool haveTxKills = false; CMutableTransaction txKills; txKills.SetGameTx (); const PlayerSet& killedPlayers = stepResult.GetKilledPlayers (); const KilledByMap& killedBy = stepResult.GetKilledBy (); txKills.vin.reserve (killedPlayers.size ()); BOOST_FOREACH(const PlayerID &victim, killedPlayers) { const valtype vchName = ValtypeFromString (victim); CNameData data; if (!view.GetName (vchName, data)) return error ("Game engine killed a non-existing player %s", victim.c_str ()); CTxIn txin(data.getUpdateOutpoint ()); /* List all killers, if player was simultaneously killed by several other players. If the reason was not KILLED_DESTRUCT, handle it also. If multiple reasons apply, the game tx is constructed for the first reason according to the ordering inside of KilledByMap. (Which in turn is determined by the enum values for KILLED_*.) */ typedef KilledByMap::const_iterator Iter; const std::pair<Iter, Iter> iters = killedBy.equal_range (victim); if (iters.first == iters.second) return error ("No reason for killed player %s", victim.c_str ()); const KilledByInfo::Reason reason = iters.first->second.reason; /* Unless we have destruct, there should be exactly one entry with the "first" reason. There may be multiple entries for different reasons, for instance, killed by poison and staying in spawn area at the same time. */ { Iter it = iters.first; ++it; if (reason != KilledByInfo::KILLED_DESTRUCT && it != iters.second && reason == it->second.reason) return error ("Multiple same-reason, non-destruct killed-by" " entries for %s", victim.c_str ()); } switch (reason) { case KilledByInfo::KILLED_DESTRUCT: txin.scriptSig << vchName << GAMEOP_KILLED_BY; for (Iter it = iters.first; it != iters.second; ++it) { if (it->second.reason != KilledByInfo::KILLED_DESTRUCT) { assert (it != iters.first); break; } txin.scriptSig << ValtypeFromString (it->second.killer.ToString ()); } break; case KilledByInfo::KILLED_SPAWN: txin.scriptSig << vchName << GAMEOP_KILLED_BY; break; case KilledByInfo::KILLED_POISON: txin.scriptSig << vchName << GAMEOP_KILLED_POISON; break; default: assert (false); } txKills.vin.push_back (txin); haveTxKills = true; } if (haveTxKills) { const CTransaction tx(txKills); assert (tx.IsGameTx () && !tx.IsBountyTx ()); vGameTx.push_back (tx); } /* Pay bounties to the players who collected them. The transaction inputs are just "dummy" containing informational messages. */ bool haveTxBounties = false; CMutableTransaction txBounties; txBounties.SetGameTx (); txBounties.vin.reserve (stepResult.bounties.size ()); txBounties.vout.reserve (stepResult.bounties.size ()); BOOST_FOREACH(const CollectedBounty& bounty, stepResult.bounties) { const valtype vchName = ValtypeFromString (bounty.character.player); CNameData data; if (!view.GetName (vchName, data)) return error ("Game engine created bounty for non-existing player"); CTxOut txout; txout.nValue = bounty.loot.nAmount; if (!bounty.address.empty ()) { /* Player-provided addresses are validated before accepting them, so failing here is ok. */ CBitcoinAddress addr(bounty.address); if (!addr.IsValid ()) return error ("Failed to set player-provided address for bounty"); txout.scriptPubKey = GetScriptForDestination (addr.Get ()); } else txout.scriptPubKey = data.getAddress (); txBounties.vout.push_back (txout); CTxIn txin; if (bounty.loot.IsRefund ()) txin.scriptSig << vchName << GAMEOP_REFUND << bounty.character.index << bounty.loot.GetRefundHeight (); else txin.scriptSig << vchName << GAMEOP_COLLECTED_BOUNTY << bounty.character.index << bounty.loot.firstBlock << bounty.loot.lastBlock << bounty.loot.collectedFirstBlock << bounty.loot.collectedLastBlock; txBounties.vin.push_back (txin); haveTxBounties = true; } if (haveTxBounties) { const CTransaction tx(txBounties); assert (tx.IsGameTx () && tx.IsBountyTx ()); vGameTx.push_back (tx); } /* Print log chatter. */ if (haveTxKills || haveTxBounties) { LogPrint ("game", "Game transactions @%d:\n", nHeight); if (haveTxKills) LogPrint ("game", " kills: %s\n", txKills.GetHash ().ToString ()); if (haveTxBounties) LogPrint ("game", " bounties: %s\n", txBounties.GetHash ().ToString ()); } return true; }
bool CCoinsViewDB::ValidateNameDB() const { const uint256 blockHash = GetBestBlock(); int nHeight; if (blockHash.IsNull()) nHeight = 0; else nHeight = mapBlockIndex.find(blockHash)->second->nHeight; /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ boost::scoped_ptr<CDBIterator> pcursor(const_cast<CDBWrapper*>(&db)->NewIterator()); pcursor->SeekToFirst(); /* Loop over the total database and read interesting things to memory. We later use that to check everything against each other. */ std::map<valtype, unsigned> nameHeightsIndex; std::map<valtype, unsigned> nameHeightsData; std::set<valtype> namesInDB; std::set<valtype> namesInUTXO; std::set<valtype> namesWithHistory; for (; pcursor->Valid(); pcursor->Next()) { boost::this_thread::interruption_point(); char chType; if (!pcursor->GetKey(chType)) continue; switch (chType) { case DB_COINS: { CCoins coins; if (!pcursor->GetValue(coins)) return error("%s : failed to read coins", __func__); BOOST_FOREACH(const CTxOut& txout, coins.vout) if (!txout.IsNull()) { const CNameScript nameOp(txout.scriptPubKey); if (nameOp.isNameOp() && nameOp.isAnyUpdate()) { const valtype& name = nameOp.getOpName(); if (namesInUTXO.count(name) > 0) return error("%s : name %s duplicated in UTXO set", __func__, ValtypeToString(name).c_str()); namesInUTXO.insert(nameOp.getOpName()); } } break; } case DB_NAME: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME) return error("%s : failed to read DB_NAME key", __func__); const valtype& name = key.second; CNameData data; if (!pcursor->GetValue(data)) return error("%s : failed to read name value", __func__); if (nameHeightsData.count(name) > 0) return error("%s : name %s duplicated in name index", __func__, ValtypeToString(name).c_str()); nameHeightsData.insert(std::make_pair(name, data.getHeight())); /* Expiration is checked at height+1, because that matches how the UTXO set is cleared in ExpireNames. */ assert(namesInDB.count(name) == 0); if (!data.isExpired(nHeight + 1)) namesInDB.insert(name); break; } case DB_NAME_HISTORY: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_HISTORY) return error("%s : failed to read DB_NAME_HISTORY key", __func__); const valtype& name = key.second; if (namesWithHistory.count(name) > 0) return error("%s : name %s has duplicate history", __func__, ValtypeToString(name).c_str()); namesWithHistory.insert(name); break; } case DB_NAME_EXPIRY: { std::pair<char, CNameCache::ExpireEntry> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_EXPIRY) return error("%s : failed to read DB_NAME_EXPIRY key", __func__); const CNameCache::ExpireEntry& entry = key.second; const valtype& name = entry.name; if (nameHeightsIndex.count(name) > 0) return error("%s : name %s duplicated in expire idnex", __func__, ValtypeToString(name).c_str()); nameHeightsIndex.insert(std::make_pair(name, entry.nHeight)); break; } default: break; } } /* Now verify the collected data. */ assert (nameHeightsData.size() >= namesInDB.size()); if (nameHeightsIndex != nameHeightsData) return error("%s : name height data mismatch", __func__); BOOST_FOREACH(const valtype& name, namesInDB) if (namesInUTXO.count(name) == 0) return error("%s : name '%s' in DB but not UTXO set", __func__, ValtypeToString(name).c_str()); BOOST_FOREACH(const valtype& name, namesInUTXO) if (namesInDB.count(name) == 0) return error("%s : name '%s' in UTXO set but not DB", __func__, ValtypeToString(name).c_str()); if (fNameHistory) { BOOST_FOREACH(const valtype& name, namesWithHistory) if (nameHeightsData.count(name) == 0) return error("%s : history entry for name '%s' not in main DB", __func__, ValtypeToString(name).c_str()); } else if (!namesWithHistory.empty ())