diff --git a/src/Makefile.am b/src/Makefile.am index ac31044c3e95efba5e77f81787ccefa487a609b6..560e6bb2bf2cd124c672491496e83912f6762741 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,6 +49,7 @@ BITCOIN_CORE_H = \ main.h \ miner.h \ mruset.h \ + names.h \ netbase.h \ net.h \ noui.h \ @@ -98,11 +99,13 @@ libbitcoin_server_a_SOURCES = \ leveldbwrapper.cpp \ main.cpp \ miner.cpp \ + names.cpp \ net.cpp \ noui.cpp \ rpcblockchain.cpp \ rpcmining.cpp \ rpcmisc.cpp \ + rpcnames.cpp \ rpcnet.cpp \ rpcrawtransaction.cpp \ rpcserver.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b9e0f46afe7dec3fadad87a5a28b3077cfb4b41c..37e100ce32ea78a3f244888737f45f04ca98895b 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -39,6 +39,8 @@ public: bnProofOfWorkLimit = CBigNum(~uint256(0) >> 32); nSubsidyHalvingInterval = 2100000; nAuxpowStartHeight = 453273; + /* TODO: Decide about fork height. */ + namesForkHeight = 1000000; // Build the genesis block. Note that the output of the genesis coinbase cannot // be spent as it did not originally exist in the database. @@ -123,6 +125,8 @@ public: nRPCPort = 19341; nAuxpowStartHeight = 453273; strDataDir = "testnet3"; + /* TODO: Decide about fork height. */ + namesForkHeight = 1000000; // Modify the testnet genesis block so the timestamp is valid for a later start. genesis.nTime = 1405338325; @@ -165,11 +169,17 @@ public: nDefaultPort = 19444; nRPCPort = 19445; nAuxpowStartHeight = 250; + namesForkHeight = 250; strDataDir = "regtest"; hashGenesisBlock = genesis.GetHash(); assert(hashGenesisBlock == uint256("0x231de73ec08234a4adff3c71e57271a13fa73f5ae1ca6b0ded89275e557a6207")); + /* Fork height for names: The regtest framework constructs initial + chains of height 200. Use 250 here so that we can test both + before and after the fork. */ + namesForkHeight = 250; + vSeeds.clear(); // Regtest mode doesn't have any DNS seeds. } diff --git a/src/chainparams.h b/src/chainparams.h index 43890e60b8ca0e9c5c38f3e21b8b028059c7bd23..e854847d5361f170c0d41a935e09ab1acc316236 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -68,6 +68,14 @@ public: int RPCPort() const { return nRPCPort; } int AuxpowChainId() const { return 20; } int AuxpowStartHeight() const { return nAuxpowStartHeight; } + + /* Height at which names are enabled as a softfork. */ + inline int + GetNamesForkHeight() const + { + return namesForkHeight; + } + protected: CChainParams() {} @@ -83,6 +91,7 @@ protected: string strDataDir; vector<CDNSSeedData> vSeeds; std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES]; + int namesForkHeight; }; /** diff --git a/src/coins.cpp b/src/coins.cpp index 86b2a6ef178b4c809370aa630511e2cf65888d71..daac765aa3014426727a617f3947b8f52d98b856 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2012-2013 The Bitcoin developers +// Copyright (c) 2012-2014 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -55,7 +55,10 @@ bool CCoinsView::SetCoins(const uint256 &txid, const CCoins &coins) { return fal bool CCoinsView::HaveCoins(const uint256 &txid) { return false; } uint256 CCoinsView::GetBestBlock() { return uint256(0); } bool CCoinsView::SetBestBlock(const uint256 &hashBlock) { return false; } -bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) { return false; } +bool CCoinsView::GetName (const CName& name, CNameData& data) const { return false; } +bool CCoinsView::SetName (const CName& name, const CNameData& data) { return false; } +bool CCoinsView::DeleteName (const CName& name) { return false; } +bool CCoinsView::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) { return false; } bool CCoinsView::GetStats(CCoinsStats &stats) { return false; } @@ -65,8 +68,11 @@ bool CCoinsViewBacked::SetCoins(const uint256 &txid, const CCoins &coins) { retu bool CCoinsViewBacked::HaveCoins(const uint256 &txid) { return base->HaveCoins(txid); } uint256 CCoinsViewBacked::GetBestBlock() { return base->GetBestBlock(); } bool CCoinsViewBacked::SetBestBlock(const uint256 &hashBlock) { return base->SetBestBlock(hashBlock); } +bool CCoinsViewBacked::GetName (const CName& name, CNameData& data) const { return base->GetName (name, data); } +bool CCoinsViewBacked::SetName (const CName& name, const CNameData& data) { return base->SetName (name, data); } +bool CCoinsViewBacked::DeleteName (const CName& name) { return base->DeleteName (name); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } -bool CCoinsViewBacked::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } +bool CCoinsViewBacked::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) { return base->BatchWrite(mapCoins, hashBlock, names); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) { return base->GetStats(stats); } CCoinsViewCache::CCoinsViewCache(CCoinsView &baseIn, bool fDummy) : CCoinsViewBacked(baseIn), hashBlock(0) { } @@ -121,21 +127,48 @@ bool CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { return true; } -bool CCoinsViewCache::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlockIn) { +bool CCoinsViewCache::GetName (const CName& name, CNameData& data) const { + if (cacheNames.IsDeleted (name)) + return false; + if (cacheNames.Get (name, data)) + return true; + + /* Note: This does not attempt to cache name queries. The cache + only keeps track of changes! */ + + return base->GetName (name, data); +} + +bool CCoinsViewCache::SetName (const CName& name, const CNameData& data) { + cacheNames.Set (name, data); + return true; +} + +bool CCoinsViewCache::DeleteName (const CName& name) { + cacheNames.Delete (name); + return true; +} + +bool CCoinsViewCache::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlockIn, const CNameCache& names) { for (std::map<uint256, CCoins>::const_iterator it = mapCoins.begin(); it != mapCoins.end(); it++) cacheCoins[it->first] = it->second; hashBlock = hashBlockIn; + cacheNames.Apply (names); return true; } bool CCoinsViewCache::Flush() { - bool fOk = base->BatchWrite(cacheCoins, hashBlock); + bool fOk = base->BatchWrite(cacheCoins, hashBlock, cacheNames); if (fOk) + { cacheCoins.clear(); + cacheNames.Clear(); + } return fOk; } unsigned int CCoinsViewCache::GetCacheSize() { + // Do not take name operations into account here. return cacheCoins.size(); } diff --git a/src/coins.h b/src/coins.h index 1ee342313c46b525698b88d9c94085afbcb8e20a..930381e7acc52c3a38210bf5c3754fea0b66e6ea 100644 --- a/src/coins.h +++ b/src/coins.h @@ -1,11 +1,12 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2013 The Bitcoin developers +// Copyright (c) 2009-2014 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_COINS_H #define BITCOIN_COINS_H #include "core.h" +#include "names.h" #include "serialize.h" #include "uint256.h" @@ -274,8 +275,16 @@ public: // Modify the currently active block hash virtual bool SetBestBlock(const uint256 &hashBlock); - // Do a bulk modification (multiple SetCoins + one SetBestBlock) - virtual bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock); + // Get a name (if it exists) + virtual bool GetName (const CName& name, CNameData& data) const; + // Set a name (or add it if not exists) + virtual bool SetName (const CName& name, const CNameData& data); + // Delete a name. + virtual bool DeleteName (const CName& name); + + // Do a bulk modification (multiple SetCoins, one SetBestBlock + // and name updates) + virtual bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names); // Calculate statistics about the unspent transaction output set virtual bool GetStats(CCoinsStats &stats); @@ -298,8 +307,11 @@ public: bool HaveCoins(const uint256 &txid); uint256 GetBestBlock(); bool SetBestBlock(const uint256 &hashBlock); + bool GetName (const CName& name, CNameData& data) const; + bool SetName (const CName& name, const CNameData& data); + bool DeleteName (const CName& name); void SetBackend(CCoinsView &viewIn); - bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock); + bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names); bool GetStats(CCoinsStats &stats); }; @@ -310,6 +322,7 @@ class CCoinsViewCache : public CCoinsViewBacked protected: uint256 hashBlock; std::map<uint256,CCoins> cacheCoins; + CNameCache cacheNames; public: CCoinsViewCache(CCoinsView &baseIn, bool fDummy = false); @@ -320,7 +333,10 @@ public: bool HaveCoins(const uint256 &txid); uint256 GetBestBlock(); bool SetBestBlock(const uint256 &hashBlock); - bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock); + bool GetName (const CName& name, CNameData& data) const; + bool SetName (const CName& name, const CNameData& data); + bool DeleteName (const CName& name); + bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names); // Return a modifiable reference to a CCoins. Check HaveCoins first. // Many methods explicitly require a CCoinsViewCache because of this method, to reduce @@ -332,6 +348,7 @@ public: bool Flush(); // Calculate the size of the cache (in number of transactions) + // This doesn't take name operations into account. unsigned int GetCacheSize(); /** Amount of bitcoins coming in to a transaction diff --git a/src/leveldbwrapper.h b/src/leveldbwrapper.h index 53e9e439bdd7f90a88fe20c12489a9e78a0d2286..4f0662aa9cda85912412946ebc0e1bc545f7c8c7 100644 --- a/src/leveldbwrapper.h +++ b/src/leveldbwrapper.h @@ -82,7 +82,7 @@ public: CLevelDBWrapper(const boost::filesystem::path &path, size_t nCacheSize, bool fMemory = false, bool fWipe = false); ~CLevelDBWrapper(); - template<typename K, typename V> bool Read(const K& key, V& value) throw(leveldb_error) { + template<typename K, typename V> bool Read(const K& key, V& value) const throw(leveldb_error) { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(ssKey.GetSerializeSize(key)); ssKey << key; @@ -111,7 +111,7 @@ public: return WriteBatch(batch, fSync); } - template<typename K> bool Exists(const K& key) throw(leveldb_error) { + template<typename K> bool Exists(const K& key) const throw(leveldb_error) { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(ssKey.GetSerializeSize(key)); ssKey << key; diff --git a/src/main.cpp b/src/main.cpp index b2fa8165c5f6cf1f31c9bff49f2488c9f2ca8de2..166194fdca87202b37e14e5535232c4880744bf1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -403,6 +403,17 @@ CBlockIndex *CChain::FindFork(const CBlockLocator &locator) const { CCoinsViewCache *pcoinsTip = NULL; CBlockTreeDB *pblocktree = NULL; +////////////////////////////////////////////////////////////////////////////// +// +// CBlockUndo +// + +CBlockUndo::CBlockUndo (const CBlockIndex* pindex) + : vtxundo(), names() +{ + supportsNames = (pindex->nHeight >= Params ().GetNamesForkHeight ()); +} + ////////////////////////////////////////////////////////////////////////////// // // mapOrphanTransactions @@ -895,12 +906,19 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa hash.ToString(), nFees, CTransaction::nMinRelayTxFee * 10000); + /* Check that there's not already a pending name operation. */ + if (!pool.names.checkTransaction (tx)) + return error ("AcceptToMemoryPool: already have an operation on" + " the same name"); + // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC)) - { + const unsigned checkFlags = SCRIPT_VERIFY_P2SH + | SCRIPT_VERIFY_STRICTENC + | SCRIPT_VERIFY_NAMES; + if (!CheckInputs (tx, state, view, true, checkFlags)) return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString()); - } + // Store transaction in memory pool.addUnchecked(hash, entry); } @@ -1562,6 +1580,15 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCach } } + /* Check name operations if the flag is set. */ + if (flags & SCRIPT_VERIFY_NAMES) + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + if (!CheckNameOperation (*out, inputs, state)) + return error ("CheckInputs: name operation is invalid"); + } + return true; } @@ -1576,7 +1603,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex bool fClean = true; - CBlockUndo blockUndo; + CBlockUndo blockUndo(pindex); CDiskBlockPos pos = pindex->GetUndoPos(); if (pos.IsNull()) return error("DisconnectBlock() : no undo data available"); @@ -1644,6 +1671,10 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex } } + /* Undo name operations. */ + if (!blockUndo.names.applyUndo (view)) + return error ("DisconnectBlock: failed to undo name operations"); + // move best block pointer to prevout block view.SetBestBlock(pindex->pprev->GetBlockHash()); @@ -1694,6 +1725,15 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C if (!CheckBlock(block, state, !fJustCheck, !fJustCheck)) return false; + /* Check that no names appear multiple times in an operation + in the single block. This needs the current height (since we only + do this check after the softfork), thus it can't be part of + the initial CheckBlock routine. */ + const int namesForkHeight = Params ().GetNamesForkHeight (); + const bool considerNames = (pindex->nHeight >= namesForkHeight); + if (considerNames && !CheckNamesInBlock (block, state)) + return error ("CheckBlock: CheckNamesInBlock failed"); + // verify that the view's current state corresponds to the previous block uint256 hashPrevBlock = pindex->pprev == NULL ? uint256(0) : pindex->pprev->GetBlockHash(); assert(hashPrevBlock == view.GetBestBlock()); @@ -1737,8 +1777,10 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C unsigned int flags = SCRIPT_VERIFY_NOCACHE | (fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE); + if (considerNames) + flags |= SCRIPT_VERIFY_NAMES; - CBlockUndo blockundo; + CBlockUndo blockundo(pindex); CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL); @@ -1789,6 +1831,14 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C if (!tx.IsCoinBase()) blockundo.vtxundo.push_back(txundo); + /* Update the name database for all name operations found. */ + if (considerNames) + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + ApplyNameOperation (*out, view, blockundo.names, state); + } + vPos.push_back(std::make_pair(block.GetTxHash(i), pos)); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); } @@ -2901,7 +2951,7 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth) return error("VerifyDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); // check level 2: verify undo validity if (nCheckLevel >= 2 && pindex) { - CBlockUndo undo; + CBlockUndo undo(pindex); CDiskBlockPos pos = pindex->GetUndoPos(); if (!pos.IsNull()) { if (!undo.ReadFromDisk(pos, pindex->pprev->GetBlockHash())) diff --git a/src/main.h b/src/main.h index eb7ba80b90b939e59ac42d90934d8b1c7a26a3b8..ca77853d2dee21e6bab00ef1e793877e74f3ffa7 100644 --- a/src/main.h +++ b/src/main.h @@ -14,6 +14,7 @@ #include "chainparams.h" #include "coins.h" #include "core.h" +#include "names.h" #include "net.h" #include "script.h" #include "sync.h" @@ -331,8 +332,27 @@ class CBlockUndo public: std::vector<CTxUndo> vtxundo; // for all but the coinbase + /** + * Determine whether or not to read/write the names information. + * This flag is set depending on whether the block this corresponds to + * is before or after the fork point. + */ + bool supportsNames; + /** Undo information for names. */ + CNameUndo names; + + /* Construct the undo object empty. It uses the CBlockIndex object + (and in particular the block height) to initialise the + "supportsNames" field appropriately. */ + explicit CBlockUndo (const CBlockIndex* pindex); + IMPLEMENT_SERIALIZE( READWRITE(vtxundo); + + if (supportsNames) + READWRITE (names); + else + assert (names.IsNull ()); ) bool WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock) diff --git a/src/miner.cpp b/src/miner.cpp index 20305ca771ab88fcd09d76afc09fac2b53d3490c..b76111d6bf4dbb79c27d77ccc9736763512c0335 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -280,7 +280,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) continue; CValidationState state; - if (!CheckInputs(tx, state, view, true, SCRIPT_VERIFY_P2SH)) + if (!CheckInputs (tx, state, view, true, + SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_NAMES)) continue; CTxUndo txundo; diff --git a/src/names.cpp b/src/names.cpp new file mode 100644 index 0000000000000000000000000000000000000000..008756ce87c2c8c8d6c8af0dd3f9916361bbd392 --- /dev/null +++ b/src/names.cpp @@ -0,0 +1,363 @@ +// Copyright (c) 2014-2015 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "names.h" + +#include "leveldbwrapper.h" +#include "main.h" +#include "util.h" + +#include <assert.h> + +/* Construct a name from a string. */ +CName +NameFromString (const std::string& str) +{ + const unsigned char* strPtr; + strPtr = reinterpret_cast<const unsigned char*> (str.c_str ()); + return CName(strPtr, strPtr + str.size ()); +} + +/* Convert a name to a string. */ +std::string +NameToString (const CName& name) +{ + return std::string (name.begin (), name.end ()); +} + +/* Return the required (minimum) cost of a name registration. */ +int64_t +GetNameCost (const CName& name) +{ + const vchType::size_type len = name.size (); + + /* TODO: Decide about real cost schedule. */ + + if (len < 3) + return -1; + + return COIN; +} + +/* ************************************************************************** */ +/* CNameCache. */ + +/* Try to get a name's associated data. This looks only + in entries, and doesn't care about deleted data. */ +bool +CNameCache::Get (const CName& name, CNameData& data) const +{ + const std::map<CName, CNameData>::const_iterator i = entries.find (name); + if (i == entries.end ()) + return false; + + data = i->second; + return true; +} + +/* Insert (or update) a name. If it is marked as "deleted", this also + removes the "deleted" mark. */ +void +CNameCache::Set (const CName& name, const CNameData& data) +{ + const std::set<CName>::iterator di = deleted.find (name); + if (di != deleted.end ()) + deleted.erase (di); + + const std::map<CName, CNameData>::iterator ei = entries.find (name); + if (ei != entries.end ()) + ei->second = data; + else + entries.insert (std::make_pair (name, data)); +} + +/* Delete a name. If it is in the "entries" set also, remove it there. */ +void +CNameCache::Delete (const CName& name) +{ + const std::map<CName, CNameData>::iterator ei = entries.find (name); + if (ei != entries.end ()) + entries.erase (ei); + + deleted.insert (name); +} + +/* Apply all the changes in the passed-in record on top of this one. */ +void +CNameCache::Apply (const CNameCache& cache) +{ + for (std::map<CName, CNameData>::const_iterator i = cache.entries.begin (); + i != cache.entries.end (); ++i) + Set (i->first, i->second); + + for (std::set<CName>::const_iterator i = cache.deleted.begin (); + i != cache.deleted.end (); ++i) + Delete (*i); +} + +/* Write all cached changes to a database batch update object. */ +void +CNameCache::WriteBatch (CLevelDBBatch& batch) const +{ + for (std::map<CName, CNameData>::const_iterator i = entries.begin (); + i != entries.end (); ++i) + batch.Write (std::make_pair ('n', i->first), i->second); + + for (std::set<CName>::const_iterator i = deleted.begin (); + i != deleted.end (); ++i) + batch.Erase (std::make_pair ('n', *i)); +} + +/* ************************************************************************** */ +/* CNameUndo. */ + +/* Undo everything in here on the given coins view. */ +bool +CNameUndo::applyUndo (CCoinsView& view) const +{ + for (std::set<CName>::const_iterator i = registrations.begin (); + i != registrations.end (); ++i) + if (!view.DeleteName (*i)) + return error ("CNameUndo::applyUndo: failed to delete name '%s'", + NameToString (*i).c_str ()); + + return true; +} + +/* ************************************************************************** */ +/* CNameMemPool. */ + +/* Check if a given new transaction conflicts with the names + already in here. */ +bool +CNameMemPool::checkTransaction (const CTransaction& tx) const +{ + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + CName name; + if (!IsNameOperation (*out, name)) + continue; + + if (hasName (name)) + return error ("CNameMemPool: name '%s' has a pending operation", + NameToString (name).c_str ()); + } + + return true; +} + +/* Add all names appearing in the given tx. This should only be + called after CheckTransaction has already been fine. */ +void +CNameMemPool::addTransaction (const CTransaction& tx) +{ + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + CName name; + if (!IsNameOperation (*out, name)) + continue; + + names.insert (name); + } +} + +/* Remove all entries for the given tx. */ +void +CNameMemPool::removeTransaction (const CTransaction& tx) +{ + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + CName name; + if (!IsNameOperation (*out, name)) + continue; + + names.erase (name); + } +} + +/* ************************************************************************** */ + +/* Decode a tx output script and see if it is a name operation. This also + checks that the operation is well-formed. If it looks like a name operation + (OP_RETURN OP_NAME_*) but isn't well-formed, it isn't accepted at all + (not just ignored). In that case, fError is set to true. */ +bool +DecodeNameScript (const CScript& script, opcodetype& op, CName& name, + std::vector<vchType>& args, bool& fError) +{ + CScript::const_iterator pc = script.begin (); + + opcodetype cur; + if (!script.GetOp (pc, cur) || cur != OP_RETURN) + { + fError = false; + return false; + } + if (!script.GetOp (pc, op) || op != OP_NAME_REGISTER) + { + fError = false; + return false; + } + + /* Get remaining data as arguments. The name itself is also taken care of + as the first argument. */ + bool haveName = false; + args.clear (); + while (pc != script.end ()) + { + vchType arg; + if (!script.GetOp (pc, cur, arg) || cur < 0 || cur > OP_PUSHDATA4) + { + fError = true; + return error ("fetching name script arguments failed"); + } + + if (haveName) + args.push_back (arg); + else + { + name = arg; + haveName = true; + } + } + + if (!haveName) + { + fError = true; + return error ("no name found in name script"); + } + + /* For now, only OP_NAME_REGISTER is implemented. Thus verify that the + arguments match what they should be. */ + if (args.size () != 1) + { + fError = true; + return error ("wrong argument count for OP_NAME_REGISTER"); + } + + fError = false; + return true; +} + +/* See if a given tx output is a name operation. */ +bool +IsNameOperation (const CTxOut& txo, CName& name) +{ + opcodetype op; + std::vector<vchType> args; + bool fError; + + return DecodeNameScript (txo.scriptPubKey, op, name, args, fError); +} + +/* Construct a name registration script. The passed-in script is + overwritten with the constructed one. */ +void +ConstructNameRegistration (CScript& out, const CName& name, + const CNameData& data) +{ + out = CScript(); + out << OP_RETURN << OP_NAME_REGISTER << name + << static_cast<const vchType&> (data.address); +} + +/* "Hook" for basic checking of a block. This looks through all transactions + in it, and verifies that each name is touched at most once by an operation + in the block. This is done as a preparatory step for block validation, + before checking the transactions in detail. */ +bool +CheckNamesInBlock (const CBlock& block, CValidationState& state) +{ + std::set<CName> names; + for (std::vector<CTransaction>::const_iterator tx = block.vtx.begin (); + tx != block.vtx.end (); ++tx) + for (std::vector<CTxOut>::const_iterator out = tx->vout.begin (); + out != tx->vout.end (); ++out) + { + CName name; + + /* Note: Actual checking of the transaction is not done here. So + we don't care about fError, and we don't do anything except keeping + track of the names that appear. */ + + if (!IsNameOperation (*out, name)) + continue; + + if (names.count (name) != 0) + return state.Invalid (error ("CheckNamesInBlock: duplicate name '%s'", + NameToString (name).c_str ())); + names.insert (name); + } + + return true; +} + +/* Check a tx output from the name point-of-view. If it looks like + a name operation, verify that it is valid (taking also the + chain state in coins into account). */ +bool +CheckNameOperation (const CTxOut& txo, const CCoinsView& coins, + CValidationState& state) +{ + opcodetype op; + CName name; + std::vector<vchType> args; + bool fError; + if (!DecodeNameScript (txo.scriptPubKey, op, name, args, fError)) + { + if (fError) + return state.Invalid (error ("CheckNameOperation: decoding of name" + " script returned an error flag")); + return true; + } + + /* Currently, only OP_NAME_REGISTER is implemented. */ + assert (op == OP_NAME_REGISTER && args.size () == 1); + + CNameData data; + if (coins.GetName (name, data)) + return state.Invalid (error ("CheckNameOperation: name '%s' exists already", + NameToString (name).c_str ())); + + const int64_t cost = GetNameCost (name); + if (cost == -1) + return state.Invalid (error ("CheckNameOperation: name '%s' is invalid", + NameToString (name).c_str ())); + + assert (cost >= 0); + if (txo.nValue < GetNameCost (name)) + return state.Invalid (error ("CheckNameOperation: not enough coins paid" + " for '%s'", + NameToString (name).c_str ())); + + return true; +} + +/* If the tx output is a name operation, apply it to the coin view. */ +bool +ApplyNameOperation (const CTxOut& txo, CCoinsView& coins, CNameUndo& undo, + CValidationState& state) +{ + opcodetype op; + CName name; + std::vector<vchType> args; + bool fError; + if (!DecodeNameScript (txo.scriptPubKey, op, name, args, fError)) + return true; + + /* Currently, only OP_NAME_REGISTER is implemented. */ + assert (op == OP_NAME_REGISTER && args.size () == 1); + + CNameData data; + data.address = CScript(args[0].begin (), args[0].end ()); + if (!coins.SetName (name, data)) + return state.Abort ("ApplyNameOperation: failed to write name"); + + undo.registrations.insert (name); + + return true; +} diff --git a/src/names.h b/src/names.h new file mode 100644 index 0000000000000000000000000000000000000000..16ed16e8015feaa6771df10664f450bbaf1f389b --- /dev/null +++ b/src/names.h @@ -0,0 +1,252 @@ +// Copyright (c) 2014-2015 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_NAMES_H +#define BITCOIN_NAMES_H + +#include "core.h" +#include "serialize.h" + +#include <stdint.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +class CCoinsView; +class CValidationState; + +/* Format of name scripts: + +OP_NAME_REGISTER: + + OP_RETURN OP_NAME_REGISTER <name> <script> + + <name> and <script> are vchType values, where <script> is the + script corresponding to the name's desired address. + +*/ + +class CLevelDBBatch; + +/** Type representing a name internally. */ +typedef vchType CName; + +/** Construct a name from a string. */ +CName NameFromString (const std::string& str); +/** Convert a name to a string. */ +std::string NameToString (const CName& name); + +/** + * Return the required (minimum) cost of a name registration. It may return + * -1 to signify that the name is not allowed to be registered at all. + */ +int64_t GetNameCost (const CName& name); + +/** + * Information stored internally for a name. For now, this is just + * the corresponding owner/recipient script. + */ +class CNameData +{ + +public: + + /** The name's ownership / recipient script. */ + CScript address; + + IMPLEMENT_SERIALIZE + ( + READWRITE (address); + ) + + /* Implement == and != operators. */ + friend inline bool + operator== (const CNameData& a, const CNameData& b) + { + return (a.address == b.address); + } + friend inline bool + operator!= (const CNameData& a, const CNameData& b) + { + return !(a == b); + } + +}; + +/** + * Cache / record of updates to the name database. In addition to + * new names (or updates to them), this also keeps track of deleted names + * (when rolling back changes). + */ +class CNameCache +{ + +public: + + /** New or updated names. */ + std::map<CName, CNameData> entries; + /** Deleted names. */ + std::set<CName> deleted; + + CNameCache () + : entries(), deleted() + {} + + inline void + Clear () + { + entries.clear (); + deleted.clear (); + } + + /* See if the given name is marked as deleted. */ + inline bool + IsDeleted (const CName& name) const + { + return (deleted.count (name) > 0); + } + + /* Try to get a name's associated data. This looks only + in entries, and doesn't care about deleted data. */ + bool Get (const CName& name, CNameData& data) const; + + /* Insert (or update) a name. If it is marked as "deleted", this also + removes the "deleted" mark. */ + void Set (const CName& name, const CNameData& data); + + /* Delete a name. If it is in the "entries" set also, remove it there. */ + void Delete (const CName& name); + + /* Apply all the changes in the passed-in record on top of this one. */ + void Apply (const CNameCache& cache); + + /* Write all cached changes to a database batch update object. */ + void WriteBatch (CLevelDBBatch& batch) const; + +}; + +/** + * Undo object for name operations in a block. + */ +class CNameUndo +{ + +public: + + /** + * Name registrations that have to be undone. These names are simply + * removed from the database when performing the undo. + */ + std::set<CName> registrations; + + IMPLEMENT_SERIALIZE + ( + /* For future extensions (changing of names), we store also + a "format version". */ + int nFormat = 1; + READWRITE (nFormat); + assert (nFormat == 1); + + READWRITE (registrations); + ) + + /* Check if this object is "empty". This is enforced when writing + the undo information before the hardfork point (so that we ensure + that no information is lost by *not* actually writing the object + before that). */ + inline bool + IsNull () const + { + return registrations.empty (); + } + + /* Undo everything in here on the given coins view. */ + bool applyUndo (CCoinsView& view) const; + +}; + +/** + * "Memory pool" for name operations. This is used by CTxMemPool, and + * makes sure that for each name, only a single tx operating on it + * will ever be held in memory. + */ +class CNameMemPool +{ + +public: + + /* The names that have pending operations in the mempool. */ + std::set<CName> names; + + inline CNameMemPool () + : names() + {} + + /* See if a name has already a pending operation. */ + inline bool + hasName (const CName& name) const + { + return (names.count (name) != 0); + } + + /* Check if a given new transaction conflicts with the names + already in here. */ + bool checkTransaction (const CTransaction& tx) const; + + /* Add all names appearing in the given tx. This should only be + called after CheckTransaction has already been fine. */ + void addTransaction (const CTransaction& tx); + + /* Remove all entries for the given tx. */ + void removeTransaction (const CTransaction& tx); + + /* Completely clear. */ + inline void + clear () + { + names.clear (); + } + + /* Return number of names in here. This is used by the sanity checks + of CTxMemPool. */ + inline unsigned + size () const + { + return names.size (); + } + +}; + +/* Decode a tx output script and see if it is a name operation. This also + checks that the operation is well-formed. If it looks like a name operation + (OP_RETURN OP_NAME_*) but isn't well-formed, it isn't accepted at all + (not just ignored). In that case, fError is set to true. */ +bool DecodeNameScript (const CScript& script, opcodetype& op, CName& name, + std::vector<vchType>& args, bool& fError); +/* See if a given tx output is a name operation. */ +bool IsNameOperation (const CTxOut& txo, CName& name); + +/* Construct a name registration script. The passed-in script is + overwritten with the constructed one. */ +void ConstructNameRegistration (CScript& out, const CName& name, + const CNameData& data); + +/* "Hook" for basic checking of a block. This looks through all transactions + in it, and verifies that each name is touched at most once by an operation + in the block. This is done as a preparatory step for block validation, + before checking the transactions in detail. */ +bool CheckNamesInBlock (const CBlock& block, CValidationState& state); + +/* Check a tx output from the name point-of-view. If it looks like + a name operation, verify that it is valid (taking also the + chain state in coins into account). */ +bool CheckNameOperation (const CTxOut& txo, const CCoinsView& coins, + CValidationState& state); +/* If the tx output is a name operation, apply it to the coin view. This also + fills in the appropriate undo information. */ +bool ApplyNameOperation (const CTxOut& txo, CCoinsView& coins, CNameUndo& undo, + CValidationState& state); + +#endif diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 8db5ea21269f13893d74064885b00891533ac310..1d32ff0fcc34e07369331a2c70b1631403c8fb63 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -132,6 +132,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector<std::stri if (strMethod == "getnetworkhashps" && n > 0) ConvertTo<int64_t>(params[0]); if (strMethod == "getnetworkhashps" && n > 1) ConvertTo<int64_t>(params[1]); if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); + if (strMethod == "sendtoname" && n > 1) ConvertTo<double>(params[1]); if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<int64_t>(params[1]); if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<int64_t>(params[1]); diff --git a/src/rpcnames.cpp b/src/rpcnames.cpp new file mode 100644 index 0000000000000000000000000000000000000000..76780eefbf0c55602a4229d337c34fe73b1c51e8 --- /dev/null +++ b/src/rpcnames.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2014-2015 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "core.h" +#include "init.h" +#include "main.h" +#include "names.h" +#include "rpcserver.h" + +#ifdef ENABLE_WALLET +# include "wallet.h" +#endif /* ENABLE_WALLET? */ + +#include "json/json_spirit_utils.h" +#include "json/json_spirit_value.h" + +#include <sstream> + +json_spirit::Value +name_getaddress (const json_spirit::Array& params, bool fHelp) +{ + if (fHelp || params.size () != 1) + throw std::runtime_error ( + "name_getaddress \"name\"\n" + "\nLook up the address corresponding to the given name." + " It fails if the name doesn't exist or if its associated" + " script cannot be parsed for an address.\n" + "\nResult:\n" + "\"xxxx\" (string) address of the name\n" + "\nExamples:\n" + + HelpExampleCli ("name_getaddress", "\"myname\"") + + HelpExampleRpc ("name_getaddress", "\"myname\"") + ); + + CName name; + CNameData data; + + name = NameFromString (params[0].get_str ()); + if (!pcoinsTip->GetName (name, data)) + { + std::ostringstream msg; + msg << "name not found: '" << NameToString (name) << "'"; + throw JSONRPCError(RPC_NAME_NOT_FOUND, msg.str ()); + } + + CTxDestination dest; + CBitcoinAddress addr; + if (!ExtractDestination (data.address, dest) || !addr.Set (dest)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "destination address cannot be extracted"); + + return addr.ToString (); +} + +#ifdef ENABLE_WALLET + +/* Check if there's a pending name operation and throw an appropriate + JSONRPCError if that is the case. */ +static void +CheckPendingNameOperation (const CName& name) +{ + if (mempool.names.hasName (name)) + throw JSONRPCError(RPC_NAME_PENDING_OPERATION, + "there is a pending operation on this name"); +} + +json_spirit::Value +name_register (const json_spirit::Array& params, bool fHelp) +{ + if (fHelp || params.size () != 2) + throw std::runtime_error ( + "name_register \"name\" \"address\"\n" + "\nRegister the given name as an alias for the address.\n" + + HelpRequiringPassphrase () + + "\nArguments:\n" + "1. \"name\" (string) The name to register.\n" + "2. \"amount\" (string) The address to which the name will resolve.\n" + "\nResult:\n" + "\"transactionid\" (string) The transaction id. (view at https://blockchain.info/tx/[transactionid])\n" + "\nExamples:\n" + + HelpExampleCli ("name_register", "\"myname\" \"i5qPw9kNW6Ce9T2jwMn3vWaRrPWDY8C4G9\"") + + HelpExampleRpc ("name_register", "\"myname\" \"i5qPw9kNW6Ce9T2jwMn3vWaRrPWDY8C4G9\"") + ); + + /* Check if we already had the softfork. */ + if (chainActive.Height () < Params ().GetNamesForkHeight ()) + throw JSONRPCError(RPC_MISC_ERROR, + "names are not yet supported by the chain"); + + /* Get the name and verify that it is open for registration. */ + const CName name = NameFromString (params[0].get_str ()); + CNameData data; + if (pcoinsTip->GetName (name, data)) + throw JSONRPCError(RPC_NAME_NOT_AVAILABLE, "the name is already taken"); + CheckPendingNameOperation (name); + + /* Validate the target address and build up the name + registration data we want in the end. */ + data = CNameData(); + CBitcoinAddress addr(params[1].get_str ()); + if (!addr.IsValid ()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "invalid Crowncoin address"); + data.address.SetDestination (addr.Get ()); + + /* Build up the final transaction and send it. */ + + const int64_t nAmount = GetNameCost (name); + if (nAmount == -1) + throw JSONRPCError(RPC_NAME_INVALID, "this name is not allowed"); + + assert (nAmount >= 0); + CScript script; + ConstructNameRegistration (script, name, data); + + CWalletTx wtx; + EnsureWalletIsUnlocked (); + + std::string strError = pwalletMain->SendMoney (script, nAmount, wtx); + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + return wtx.GetHash ().GetHex (); +} + +json_spirit::Value +sendtoname (const json_spirit::Array& params, bool fHelp) +{ + if (fHelp || params.size () < 2 || params.size () > 4) + throw std::runtime_error ( + "sendtoaddress \"name\" amount ( \"comment\" \"comment-to\" )\n" + "\nSent an amount to the address of a given name. The amount is a" + " real and is rounded to the nearest 0.00000001.\n" + + HelpRequiringPassphrase () + + "\nArguments:\n" + "1. \"name\" (string, required) The name to send to.\n" + "2. \"amount\" (numeric, required) The amount in ISR to send. eg 100.01\n" + "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" + " This is not part of the transaction, just kept in your wallet.\n" + "4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n" + " to which you're sending the transaction. This is not part of the \n" + " transaction, just kept in your wallet.\n" + "\nResult:\n" + "\"transactionid\" (string) The transaction id. (view at https://blockchain.info/tx/[transactionid])\n" + "\nExamples:\n" + + HelpExampleCli ("sendtoname", "\"myname\" 0.1") + + HelpExampleCli ("sendtoname", "\"myname\" 0.1 \"donation\" \"seans outpost\"") + + HelpExampleRpc ("sendtoname", "\"myname\", 0.1, \"donation\", \"seans outpost\"") + ); + + /* Extract destination script from name database. */ + + CName name; + CNameData data; + + name = NameFromString (params[0].get_str ()); + if (!pcoinsTip->GetName (name, data)) + { + std::ostringstream msg; + msg << "name not found: '" << NameToString (name) << "'"; + throw JSONRPCError (RPC_NAME_NOT_FOUND, msg.str ()); + } + + /* Amount and wallet comments, just as in "sendtoaddress". */ + + const int64_t nAmount = AmountFromValue (params[1]); + + CWalletTx wtx; + if (params.size () > 2 && params[2].type () != json_spirit::null_type + && !params[2].get_str ().empty ()) + wtx.mapValue["comment"] = params[2].get_str (); + if (params.size () > 3 && params[3].type () != json_spirit::null_type + && !params[3].get_str ().empty ()) + wtx.mapValue["to"] = params[3].get_str (); + + /* Perform the send. */ + + EnsureWalletIsUnlocked (); + + std::string strError = pwalletMain->SendMoney (data.address, nAmount, wtx); + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + return wtx.GetHash ().GetHex (); +} + +#endif /* ENABLE_WALLET? */ diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 8b3df1962142d08fbd3bef18b2c4e6c9b3892062..79b702fac4d5633e22893697c0470d6c2771a9c2 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -69,6 +69,12 @@ enum RPCErrorCode RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet RPC_WALLET_ALREADY_UNLOCKED = -17, // Wallet is already unlocked + + // Name errors + RPC_NAME_NOT_FOUND = -100, // Name is not in the database + RPC_NAME_NOT_AVAILABLE = -101, // Name is already taken + RPC_NAME_PENDING_OPERATION = -102, // There is already a pending name operation + RPC_NAME_INVALID = -103, // The name is not allowed for registration }; // diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index c3e71d1ba6e41664749315f95b8d8f0fd9161e61..8d2e49b7499933d18f824ee6aa431fb2f07e6832 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -268,6 +268,13 @@ static const CRPCCommand vRPCCommands[] = { "validateaddress", &validateaddress, true, false, false }, /* uses wallet if enabled */ { "verifymessage", &verifymessage, false, false, false }, + /* Names */ + { "name_getaddress", &name_getaddress, true, false, false }, +#ifdef ENABLE_WALLET + { "name_register", &name_register, false, false, true }, + { "sendtoname", &sendtoname, false, false, true }, +#endif /* ENABLE_WALLET? */ + #ifdef ENABLE_WALLET /* Wallet */ { "addmultisigaddress", &addmultisigaddress, false, false, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 0068975e612010c44cab137a884c4942da87a4cc..c5a76e898366fa0b38e92cf4dcd7f67ae58eb915 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -189,4 +189,8 @@ extern json_spirit::Value gettxoutsetinfo(const json_spirit::Array& params, bool extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value name_getaddress (const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value name_register (const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendtoname (const json_spirit::Array& params, bool fHelp); + #endif diff --git a/src/script.cpp b/src/script.cpp index d129f7665c7f976e7afc165304eedab02756f2d3..f2e1b8d361b3a3e7a29c7eedb8031baf22a90ef5 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -82,6 +82,7 @@ const char* GetTxnOutputType(txnouttype t) case TX_SCRIPTHASH: return "scripthash"; case TX_MULTISIG: return "multisig"; case TX_NULL_DATA: return "nulldata"; + case TX_NAME_OPERATION: return "nameop"; } return NULL; } @@ -1212,6 +1213,9 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi // Empty, provably prunable, data-carrying output mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN << OP_SMALLDATA)); mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN)); + + // Name operations + mTemplates.insert(make_pair(TX_NAME_OPERATION, CScript() << OP_RETURN << OP_NAME_REGISTER << OP_SMALLDATA << OP_SMALLDATA)); } // Shortcut for pay-to-script-hash, which are more constrained than the other types: @@ -1365,6 +1369,7 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash { case TX_NONSTANDARD: case TX_NULL_DATA: + case TX_NAME_OPERATION: return false; case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); @@ -1396,6 +1401,7 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned c { case TX_NONSTANDARD: case TX_NULL_DATA: + case TX_NAME_OPERATION: return -1; case TX_PUBKEY: return 1; @@ -1473,6 +1479,7 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) { case TX_NONSTANDARD: case TX_NULL_DATA: + case TX_NAME_OPERATION: return false; case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); @@ -1534,7 +1541,7 @@ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vecto vector<valtype> vSolutions; if (!Solver(scriptPubKey, typeRet, vSolutions)) return false; - if (typeRet == TX_NULL_DATA){ + if (typeRet == TX_NULL_DATA || typeRet == TX_NAME_OPERATION){ // This is data, not addresses return false; } @@ -1754,6 +1761,7 @@ static CScript CombineSignatures(CScript scriptPubKey, const CTransaction& txTo, { case TX_NONSTANDARD: case TX_NULL_DATA: + case TX_NAME_OPERATION: // Don't know anything about this, assume bigger one is correct: if (sigs1.size() >= sigs2.size()) return PushAll(sigs1); diff --git a/src/script.h b/src/script.h index 657ac0b388bf0957f0526ecdc6a997fd73f7a960..ec4b0d3f041f5ee7c4ed5d830dbd64ab74baebd6 100644 --- a/src/script.h +++ b/src/script.h @@ -25,6 +25,9 @@ class CTransaction; static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes static const unsigned int MAX_OP_RETURN_RELAY = 40; // bytes +/** Type used for generic data on the stack. */ +typedef std::vector<unsigned char> vchType; + /** Signature hash types/flags */ enum { @@ -42,6 +45,8 @@ enum SCRIPT_VERIFY_STRICTENC = (1U << 1), // enforce strict conformance to DER and SEC2 for signatures and pubkeys SCRIPT_VERIFY_EVEN_S = (1U << 2), // enforce even S values in signatures (depends on STRICTENC) SCRIPT_VERIFY_NOCACHE = (1U << 3), // do not store results in signature cache (but do query it) + + SCRIPT_VERIFY_NAMES = (1U << 10), // check name operations? }; enum txnouttype @@ -53,6 +58,7 @@ enum txnouttype TX_SCRIPTHASH, TX_MULTISIG, TX_NULL_DATA, + TX_NAME_OPERATION, }; class CNoDestination { @@ -84,6 +90,7 @@ enum opcodetype OP_RESERVED = 0x50, OP_1 = 0x51, OP_TRUE=OP_1, + OP_NAME_REGISTER=OP_1, OP_2 = 0x52, OP_3 = 0x53, OP_4 = 0x54, diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 6dd7a5ee3d515a9be01468df2d5f8b7b470bea39..50c32e83a0580f0f9a41d3b5a7a5d98d7d94d015 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -50,6 +50,7 @@ test_crowncoin_SOURCES = \ miner_tests.cpp \ mruset_tests.cpp \ multisig_tests.cpp \ + name_tests.cpp \ netbase_tests.cpp \ pmt_tests.cpp \ rpc_tests.cpp \ diff --git a/src/test/name_tests.cpp b/src/test/name_tests.cpp new file mode 100644 index 0000000000000000000000000000000000000000..53aaaa60fd71d9facc43509652bcc7bdaa670bb1 --- /dev/null +++ b/src/test/name_tests.cpp @@ -0,0 +1,144 @@ +// Copyright (c) 2014-2015 Daniel Kraft +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "core.h" +#include "main.h" +#include "names.h" + +#include <boost/test/unit_test.hpp> + +static const char* addrString = "1HbJ5rPgNLJzFMfkKDZSEoRv2ePtAZqh8q"; + +BOOST_AUTO_TEST_SUITE (name_tests) + +BOOST_AUTO_TEST_CASE (name_script_parsing) +{ + CNameData data; + + CBitcoinAddress addr(addrString); + BOOST_CHECK (addr.IsValid ()); + data.address.SetDestination (addr.Get ()); + const CName name = NameFromString ("my-cool-name"); + + CScript script; + ConstructNameRegistration (script, name, data); + + opcodetype op; + CName name2; + std::vector<vchType> args; + bool error; + + BOOST_CHECK (DecodeNameScript (script, op, name2, args, error)); + BOOST_CHECK (!error); + BOOST_CHECK (op == OP_NAME_REGISTER); + BOOST_CHECK (name2 == name); + BOOST_CHECK (args.size () == 1); + BOOST_CHECK (args[0] == data.address); + + BOOST_CHECK (!DecodeNameScript (data.address, op, name2, args, error)); + BOOST_CHECK (!error); + + script = CScript (); + script << OP_RETURN << OP_NAME_REGISTER; + BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error)); + BOOST_CHECK (error); + + script << name; + BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error)); + BOOST_CHECK (error); + + script << OP_NOP; + BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error)); + BOOST_CHECK (error); + + ConstructNameRegistration (script, name, data); + script << OP_NOP; + BOOST_CHECK (!DecodeNameScript (script, op, name2, args, error)); + BOOST_CHECK (error); +} + +BOOST_AUTO_TEST_CASE (names_in_block) +{ + CBlock block; + CValidationState state; + + CNameData data; + CBitcoinAddress addr(addrString); + BOOST_CHECK (addr.IsValid ()); + data.address.SetDestination (addr.Get ()); + + const CName name = NameFromString ("my-cool-name"); + CTxOut txo; + txo.nValue = GetNameCost (name); + ConstructNameRegistration (txo.scriptPubKey, name, data); + + CTransaction tx; + tx.vout.push_back (txo); + + block.vtx.push_back (tx); + BOOST_CHECK (CheckNamesInBlock (block, state)); + + block.vtx.push_back (tx); + BOOST_CHECK (!CheckNamesInBlock (block, state)); +} + +BOOST_AUTO_TEST_CASE (names_database) +{ + const CName name = NameFromString ("database-test-name"); + CNameData data, data2; + CBitcoinAddress addr(addrString); + BOOST_CHECK (addr.IsValid ()); + data.address.SetDestination (addr.Get ()); + + CCoinsViewCache& view = *pcoinsTip; + + BOOST_CHECK (!view.GetName (name, data2)); + BOOST_CHECK (view.SetName (name, data)); + BOOST_CHECK (view.GetName (name, data2)); + BOOST_CHECK (data == data2); + BOOST_CHECK (view.Flush ()); + + BOOST_CHECK (view.DeleteName (name)); + BOOST_CHECK (!view.GetName (name, data2)); + BOOST_CHECK (view.Flush ()); +} + +BOOST_AUTO_TEST_CASE (name_operations) +{ + CCoinsView dummy; + CCoinsViewCache view(dummy); + CNameUndo undo; + + const CName nameInvalid = NameFromString ("ab"); + BOOST_CHECK (GetNameCost (nameInvalid) == -1); + + const CName name = NameFromString ("database-test-name"); + CNameData data, data2; + CBitcoinAddress addr(addrString); + BOOST_CHECK (addr.IsValid ()); + data.address.SetDestination (addr.Get ()); + + CValidationState state; + CTxOut txo; + ConstructNameRegistration (txo.scriptPubKey, name, data); + + txo.nValue = GetNameCost (name) - 1; + BOOST_CHECK (!CheckNameOperation (txo, view, state)); + BOOST_CHECK (undo.IsNull ()); + + txo.nValue = GetNameCost (name); + BOOST_CHECK (CheckNameOperation (txo, view, state)); + BOOST_CHECK (ApplyNameOperation (txo, view, undo, state)); + BOOST_CHECK (!undo.IsNull ()); + + BOOST_CHECK (view.GetName (name, data2)); + BOOST_CHECK (data == data2); + BOOST_CHECK (!CheckNameOperation (txo, view, state)); + + undo.applyUndo (view); + BOOST_CHECK (CheckNameOperation (txo, view, state)); +} + +BOOST_AUTO_TEST_SUITE_END () diff --git a/src/txdb.cpp b/src/txdb.cpp index 272e026a1afdc7273fbce2783075f969a08c6c2d..0c98b5e2a5f072e763e485a8caff5e562439b241 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -53,7 +53,27 @@ bool CCoinsViewDB::SetBestBlock(const uint256 &hashBlock) { return db.WriteBatch(batch); } -bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock) { +bool CCoinsViewDB::GetName (const CName& name, CNameData& data) const { + return db.Read (std::make_pair ('n', name), data); +} + +bool CCoinsViewDB::SetName (const CName& name, const CNameData& data) { + CLevelDBBatch batch; + CNameCache cache; + cache.Set (name, data); + cache.WriteBatch (batch); + return db.WriteBatch (batch); +} + +bool CCoinsViewDB::DeleteName (const CName& name) { + CLevelDBBatch batch; + CNameCache cache; + cache.Delete (name); + cache.WriteBatch (batch); + return db.WriteBatch (batch); +} + +bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names) { LogPrint("coindb", "Committing %u changed transactions to coin database...\n", (unsigned int)mapCoins.size()); CLevelDBBatch batch; @@ -61,6 +81,7 @@ bool CCoinsViewDB::BatchWrite(const std::map<uint256, CCoins> &mapCoins, const u BatchWriteCoins(batch, it->first, it->second); if (hashBlock != uint256(0)) BatchWriteHashBestChain(batch, hashBlock); + names.WriteBatch (batch); return db.WriteBatch(batch); } diff --git a/src/txdb.h b/src/txdb.h index 5eb5731db3d6dd37b134b4c25876bc995ae94e77..7ca4bca15136c02aa9367e6b0211c8eba1c2a51e 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -8,6 +8,7 @@ #include "leveldbwrapper.h" #include "main.h" +#include "names.h" #include <map> #include <string> @@ -38,7 +39,10 @@ public: bool HaveCoins(const uint256 &txid); uint256 GetBestBlock(); bool SetBestBlock(const uint256 &hashBlock); - bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock); + bool GetName (const CName& name, CNameData& data) const; + bool SetName (const CName& name, const CNameData& data); + bool DeleteName (const CName& name); + bool BatchWrite(const std::map<uint256, CCoins> &mapCoins, const uint256 &hashBlock, const CNameCache& names); bool GetStats(CCoinsStats &stats); }; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 64c9eac73dcb560f4a73ddff42a0f10b4b1f053a..813ac1bb9c0367e73d736fcd04e123c6b6c54cf3 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -81,6 +81,16 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry) for (unsigned int i = 0; i < tx.vin.size(); i++) mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i); nTransactionsUpdated++; + + /* Names are handled here without checking the softfork height. + This is done because otherwise, we would need logic to add names + of transactions already in the mempool as soon as the chain + grows beyond the softfork height, which would get complicated. + Also, the worst that will happen, is that nodes don't relay or + mine transactions that look like name operations even before the + softfork -- since those do not really appear normally anyway, + that shouldn't be a problem. */ + names.addTransaction (tx); } return true; } @@ -106,6 +116,7 @@ void CTxMemPool::remove(const CTransaction &tx, std::list<CTransaction>& removed BOOST_FOREACH(const CTxIn& txin, tx.vin) mapNextTx.erase(txin.prevout); mapTx.erase(hash); + names.removeTransaction (tx); nTransactionsUpdated++; } } @@ -133,6 +144,7 @@ void CTxMemPool::clear() LOCK(cs); mapTx.clear(); mapNextTx.clear(); + names.clear (); ++nTransactionsUpdated; } @@ -174,6 +186,25 @@ void CTxMemPool::check(CCoinsViewCache *pcoins) const assert(tx.vin.size() > it->second.n); assert(it->first == it->second.ptx->vin[it->second.n].prevout); } + + /* Check the names mempool. */ + unsigned nameCount = 0; + for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin (); + it != mapTx.end (); it++) + { + const CTransaction& tx = it->second.GetTx (); + for (std::vector<CTxOut>::const_iterator out = tx.vout.begin (); + out != tx.vout.end (); ++out) + { + CName name; + if (!IsNameOperation (*out, name)) + continue; + + ++nameCount; + assert (names.hasName (name)); + } + } + assert (nameCount == names.size ()); } void CTxMemPool::queryHashes(vector<uint256>& vtxid) diff --git a/src/txmempool.h b/src/txmempool.h index 4509e95778c9ed98f7bd5c9803b90e61a6e602c7..91dd6996558173d2c70a69dad1a25568dd1103b9 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -9,6 +9,7 @@ #include "coins.h" #include "core.h" +#include "names.h" #include "sync.h" /** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ @@ -62,6 +63,15 @@ public: std::map<uint256, CTxMemPoolEntry> mapTx; std::map<COutPoint, CInPoint> mapNextTx; + /* Keep track of all names that are currently operated on by pending + transactions in the mempool. Alternatively, we could also adapt + CCoinsViewMemPool to be a "full" CCoinsView also with respect + to names -- but this makes things more complicated. Just checking + that no name appears twice in the mempool and that all name + operations are valid with respect to the "actual", DB-backed CCoinsView + should be enough for this purpose. */ + CNameMemPool names; + CTxMemPool(); /*