Jianping Wu
6 years ago
7 changed files with 1228 additions and 0 deletions
@ -0,0 +1,760 @@
@@ -0,0 +1,760 @@
|
||||
// Copyright (c) 2018 Jianping Wu
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <keva/main.h> |
||||
|
||||
#include <chainparams.h> |
||||
#include <coins.h> |
||||
#include <consensus/validation.h> |
||||
#include <hash.h> |
||||
#include <dbwrapper.h> |
||||
#include <script/interpreter.h> |
||||
#include <script/keva.h> |
||||
#include <txmempool.h> |
||||
#include <undo.h> |
||||
#include <util.h> |
||||
#include <utilstrencodings.h> |
||||
#include <validation.h> |
||||
|
||||
|
||||
/* ************************************************************************** */ |
||||
/* CNameData. */ |
||||
|
||||
bool |
||||
CNameData::isExpired () const |
||||
{ |
||||
return isExpired (chainActive.Height ()); |
||||
} |
||||
|
||||
bool |
||||
CNameData::isExpired (unsigned h) const |
||||
{ |
||||
return ::isExpired (nHeight, h); |
||||
} |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CNameTxUndo. */ |
||||
|
||||
void |
||||
CNameTxUndo::fromOldState (const valtype& nm, const CCoinsView& view) |
||||
{ |
||||
name = nm; |
||||
isNew = !view.GetName (name, oldData); |
||||
} |
||||
|
||||
void |
||||
CNameTxUndo::apply (CCoinsViewCache& view) const |
||||
{ |
||||
if (isNew) |
||||
view.DeleteName (name); |
||||
else |
||||
view.SetName (name, oldData, true); |
||||
} |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CKevaMemPool. */ |
||||
|
||||
uint256 |
||||
CKevaMemPool::getTxForName (const valtype& name) const |
||||
{ |
||||
NameTxMap::const_iterator mi; |
||||
|
||||
mi = mapNameRegs.find (name); |
||||
if (mi != mapNameRegs.end ()) |
||||
{ |
||||
assert (mapNameUpdates.count (name) == 0); |
||||
return mi->second; |
||||
} |
||||
|
||||
mi = mapNameUpdates.find (name); |
||||
if (mi != mapNameUpdates.end ()) |
||||
{ |
||||
assert (mapNameRegs.count (name) == 0); |
||||
return mi->second; |
||||
} |
||||
|
||||
return uint256 (); |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::addUnchecked (const uint256& hash, const CTxMemPoolEntry& entry) |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
if (entry.isNameNew ()) |
||||
{ |
||||
const valtype& newHash = entry.getNameNewHash (); |
||||
const NameTxMap::const_iterator mit = mapNameNews.find (newHash); |
||||
if (mit != mapNameNews.end ()) |
||||
assert (mit->second == hash); |
||||
else |
||||
mapNameNews.insert (std::make_pair (newHash, hash)); |
||||
} |
||||
|
||||
if (entry.isNameRegistration ()) |
||||
{ |
||||
const valtype& name = entry.getName (); |
||||
assert (mapNameRegs.count (name) == 0); |
||||
mapNameRegs.insert (std::make_pair (name, hash)); |
||||
} |
||||
|
||||
if (entry.isNameUpdate ()) |
||||
{ |
||||
const valtype& name = entry.getName (); |
||||
assert (mapNameUpdates.count (name) == 0); |
||||
mapNameUpdates.insert (std::make_pair (name, hash)); |
||||
} |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::remove (const CTxMemPoolEntry& entry) |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
if (entry.isNameRegistration ()) |
||||
{ |
||||
const NameTxMap::iterator mit = mapNameRegs.find (entry.getName ()); |
||||
assert (mit != mapNameRegs.end ()); |
||||
mapNameRegs.erase (mit); |
||||
} |
||||
if (entry.isNameUpdate ()) |
||||
{ |
||||
const NameTxMap::iterator mit = mapNameUpdates.find (entry.getName ()); |
||||
assert (mit != mapNameUpdates.end ()); |
||||
mapNameUpdates.erase (mit); |
||||
} |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::removeConflicts (const CTransaction& tx) |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
if (!tx.IsNamecoin ()) |
||||
return; |
||||
|
||||
for (const auto& txout : tx.vout) |
||||
{ |
||||
const CNameScript nameOp(txout.scriptPubKey); |
||||
if (nameOp.isNameOp () && nameOp.getNameOp () == OP_NAME_FIRSTUPDATE) |
||||
{ |
||||
const valtype& name = nameOp.getOpName (); |
||||
const NameTxMap::const_iterator mit = mapNameRegs.find (name); |
||||
if (mit != mapNameRegs.end ()) |
||||
{ |
||||
const CTxMemPool::txiter mit2 = pool.mapTx.find (mit->second); |
||||
assert (mit2 != pool.mapTx.end ()); |
||||
pool.removeRecursive (mit2->GetTx (), |
||||
MemPoolRemovalReason::NAME_CONFLICT); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::removeUnexpireConflicts (const std::set<valtype>& unexpired) |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
for (const auto& name : unexpired) |
||||
{ |
||||
LogPrint (BCLog::NAMES, "unexpired: %s, mempool: %u\n", |
||||
ValtypeToString (name).c_str (), mapNameRegs.count (name)); |
||||
|
||||
const NameTxMap::const_iterator mit = mapNameRegs.find (name); |
||||
if (mit != mapNameRegs.end ()) |
||||
{ |
||||
const CTxMemPool::txiter mit2 = pool.mapTx.find (mit->second); |
||||
assert (mit2 != pool.mapTx.end ()); |
||||
pool.removeRecursive (mit2->GetTx (), |
||||
MemPoolRemovalReason::NAME_CONFLICT); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::removeExpireConflicts (const std::set<valtype>& expired) |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
for (const auto& name : expired) |
||||
{ |
||||
LogPrint (BCLog::NAMES, "expired: %s, mempool: %u\n", |
||||
ValtypeToString (name).c_str (), mapNameUpdates.count (name)); |
||||
|
||||
const NameTxMap::const_iterator mit = mapNameUpdates.find (name); |
||||
if (mit != mapNameUpdates.end ()) |
||||
{ |
||||
const CTxMemPool::txiter mit2 = pool.mapTx.find (mit->second); |
||||
assert (mit2 != pool.mapTx.end ()); |
||||
pool.removeRecursive (mit2->GetTx (), |
||||
MemPoolRemovalReason::NAME_CONFLICT); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void |
||||
CKevaMemPool::check (const CCoinsView& coins) const |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
const uint256 blockHash = coins.GetBestBlock (); |
||||
int nHeight; |
||||
if (blockHash.IsNull()) |
||||
nHeight = 0; |
||||
else |
||||
nHeight = mapBlockIndex.find (blockHash)->second->nHeight; |
||||
|
||||
std::set<valtype> nameRegs; |
||||
std::set<valtype> nameUpdates; |
||||
for (const auto& entry : pool.mapTx) |
||||
{ |
||||
const uint256 txHash = entry.GetTx ().GetHash (); |
||||
if (entry.isNameNew ()) |
||||
{ |
||||
const valtype& newHash = entry.getNameNewHash (); |
||||
const NameTxMap::const_iterator mit = mapNameNews.find (newHash); |
||||
|
||||
assert (mit != mapNameNews.end ()); |
||||
assert (mit->second == txHash); |
||||
} |
||||
|
||||
if (entry.isNameRegistration ()) |
||||
{ |
||||
const valtype& name = entry.getName (); |
||||
|
||||
const NameTxMap::const_iterator mit = mapNameRegs.find (name); |
||||
assert (mit != mapNameRegs.end ()); |
||||
assert (mit->second == txHash); |
||||
|
||||
assert (nameRegs.count (name) == 0); |
||||
nameRegs.insert (name); |
||||
|
||||
/* The old name should be expired already. Note that we use
|
||||
nHeight+1 for the check, because that's the height at which |
||||
the mempool tx will actually be mined. */ |
||||
CNameData data; |
||||
if (coins.GetName (name, data)) |
||||
assert (data.isExpired (nHeight + 1)); |
||||
} |
||||
|
||||
if (entry.isNameUpdate ()) |
||||
{ |
||||
const valtype& name = entry.getName (); |
||||
|
||||
const NameTxMap::const_iterator mit = mapNameUpdates.find (name); |
||||
assert (mit != mapNameUpdates.end ()); |
||||
assert (mit->second == txHash); |
||||
|
||||
assert (nameUpdates.count (name) == 0); |
||||
nameUpdates.insert (name); |
||||
|
||||
/* As above, use nHeight+1 for the expiration check. */ |
||||
CNameData data; |
||||
if (!coins.GetName (name, data)) |
||||
assert (false); |
||||
assert (!data.isExpired (nHeight + 1)); |
||||
} |
||||
} |
||||
|
||||
assert (nameRegs.size () == mapNameRegs.size ()); |
||||
assert (nameUpdates.size () == mapNameUpdates.size ()); |
||||
|
||||
/* Check that nameRegs and nameUpdates are disjoint. They must be since
|
||||
a name can only be in either category, depending on whether it exists |
||||
at the moment or not. */ |
||||
for (const auto& name : nameRegs) |
||||
assert (nameUpdates.count (name) == 0); |
||||
for (const auto& name : nameUpdates) |
||||
assert (nameRegs.count (name) == 0); |
||||
} |
||||
|
||||
bool |
||||
CKevaMemPool::checkTx (const CTransaction& tx) const |
||||
{ |
||||
AssertLockHeld (pool.cs); |
||||
|
||||
if (!tx.IsNamecoin ()) |
||||
return true; |
||||
|
||||
/* In principle, multiple name_updates could be performed within the
|
||||
mempool at once (building upon each other). This is disallowed, though, |
||||
since the current mempool implementation does not like it. (We keep |
||||
track of only a single update tx for each name.) */ |
||||
|
||||
for (const auto& txout : tx.vout) |
||||
{ |
||||
const CNameScript nameOp(txout.scriptPubKey); |
||||
if (!nameOp.isNameOp ()) |
||||
continue; |
||||
|
||||
switch (nameOp.getNameOp ()) |
||||
{ |
||||
case OP_NAME_NEW: |
||||
{ |
||||
const valtype& newHash = nameOp.getOpHash (); |
||||
std::map<valtype, uint256>::const_iterator mi; |
||||
mi = mapNameNews.find (newHash); |
||||
if (mi != mapNameNews.end () && mi->second != tx.GetHash ()) |
||||
return false; |
||||
break; |
||||
} |
||||
|
||||
case OP_NAME_FIRSTUPDATE: |
||||
{ |
||||
const valtype& name = nameOp.getOpName (); |
||||
if (registersName (name)) |
||||
return false; |
||||
break; |
||||
} |
||||
|
||||
case OP_NAME_UPDATE: |
||||
{ |
||||
const valtype& name = nameOp.getOpName (); |
||||
if (updatesName (name)) |
||||
return false; |
||||
break; |
||||
} |
||||
|
||||
default: |
||||
assert (false); |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CNameConflictTracker. */ |
||||
|
||||
namespace |
||||
{ |
||||
|
||||
void |
||||
ConflictTrackerNotifyEntryRemoved (CNameConflictTracker* tracker, |
||||
CTransactionRef txRemoved, |
||||
MemPoolRemovalReason reason) |
||||
{ |
||||
if (reason == MemPoolRemovalReason::NAME_CONFLICT) |
||||
tracker->AddConflictedEntry (txRemoved); |
||||
} |
||||
|
||||
} // anonymous namespace
|
||||
|
||||
CNameConflictTracker::CNameConflictTracker (CTxMemPool &p) |
||||
: txNameConflicts(std::make_shared<std::vector<CTransactionRef>>()), pool(p) |
||||
{ |
||||
pool.NotifyEntryRemoved.connect ( |
||||
boost::bind (&ConflictTrackerNotifyEntryRemoved, this, _1, _2)); |
||||
} |
||||
|
||||
CNameConflictTracker::~CNameConflictTracker () |
||||
{ |
||||
pool.NotifyEntryRemoved.disconnect ( |
||||
boost::bind (&ConflictTrackerNotifyEntryRemoved, this, _1, _2)); |
||||
} |
||||
|
||||
void |
||||
CNameConflictTracker::AddConflictedEntry (CTransactionRef txRemoved) |
||||
{ |
||||
txNameConflicts->emplace_back (std::move (txRemoved)); |
||||
} |
||||
|
||||
/* ************************************************************************** */ |
||||
|
||||
bool |
||||
CheckNameTransaction (const CTransaction& tx, unsigned nHeight, |
||||
const CCoinsView& view, |
||||
CValidationState& state, unsigned flags) |
||||
{ |
||||
const std::string strTxid = tx.GetHash ().GetHex (); |
||||
const char* txid = strTxid.c_str (); |
||||
const bool fMempool = (flags & SCRIPT_VERIFY_NAMES_MEMPOOL); |
||||
|
||||
/* Ignore historic bugs. */ |
||||
CChainParams::BugType type; |
||||
if (Params ().IsHistoricBug (tx.GetHash (), nHeight, type)) |
||||
return true; |
||||
|
||||
/* As a first step, try to locate inputs and outputs of the transaction
|
||||
that are name scripts. At most one input and output should be |
||||
a name operation. */ |
||||
|
||||
int nameIn = -1; |
||||
CNameScript nameOpIn; |
||||
Coin coinIn; |
||||
for (unsigned i = 0; i < tx.vin.size (); ++i) |
||||
{ |
||||
const COutPoint& prevout = tx.vin[i].prevout; |
||||
Coin coin; |
||||
if (!view.GetCoin (prevout, coin)) |
||||
return error ("%s: failed to fetch input coin for %s", __func__, txid); |
||||
|
||||
const CNameScript op(coin.out.scriptPubKey); |
||||
if (op.isNameOp ()) |
||||
{ |
||||
if (nameIn != -1) |
||||
return state.Invalid (error ("%s: multiple name inputs into" |
||||
" transaction %s", __func__, txid)); |
||||
nameIn = i; |
||||
nameOpIn = op; |
||||
coinIn = coin; |
||||
} |
||||
} |
||||
|
||||
int nameOut = -1; |
||||
CNameScript nameOpOut; |
||||
for (unsigned i = 0; i < tx.vout.size (); ++i) |
||||
{ |
||||
const CNameScript op(tx.vout[i].scriptPubKey); |
||||
if (op.isNameOp ()) |
||||
{ |
||||
if (nameOut != -1) |
||||
return state.Invalid (error ("%s: multiple name outputs from" |
||||
" transaction %s", __func__, txid)); |
||||
nameOut = i; |
||||
nameOpOut = op; |
||||
} |
||||
} |
||||
|
||||
/* Check that no name inputs/outputs are present for a non-Namecoin tx.
|
||||
If that's the case, all is fine. For a Namecoin tx instead, there |
||||
should be at least an output (for NAME_NEW, no inputs are expected). */ |
||||
|
||||
if (!tx.IsNamecoin ()) |
||||
{ |
||||
if (nameIn != -1) |
||||
return state.Invalid (error ("%s: non-Namecoin tx %s has name inputs", |
||||
__func__, txid)); |
||||
if (nameOut != -1) |
||||
return state.Invalid (error ("%s: non-Namecoin tx %s at height %u" |
||||
" has name outputs", |
||||
__func__, txid, nHeight)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
assert (tx.IsNamecoin ()); |
||||
if (nameOut == -1) |
||||
return state.Invalid (error ("%s: Namecoin tx %s has no name outputs", |
||||
__func__, txid)); |
||||
|
||||
/* Reject "greedy names". */ |
||||
const Consensus::Params& params = Params ().GetConsensus (); |
||||
if (tx.vout[nameOut].nValue < params.rules->MinNameCoinAmount(nHeight)) |
||||
return state.Invalid (error ("%s: greedy name", __func__)); |
||||
|
||||
/* Handle NAME_NEW now, since this is easy and different from the other
|
||||
operations. */ |
||||
|
||||
if (nameOpOut.getNameOp () == OP_NAME_NEW) |
||||
{ |
||||
if (nameIn != -1) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_NEW with" |
||||
" previous name input")); |
||||
|
||||
if (nameOpOut.getOpHash ().size () != 20) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_NEW's hash" |
||||
" has wrong size")); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/* Now that we have ruled out NAME_NEW, check that we have a previous
|
||||
name input that is being updated. */ |
||||
|
||||
assert (nameOpOut.isAnyUpdate ()); |
||||
if (nameIn == -1) |
||||
return state.Invalid (error ("CheckNameTransaction: update without" |
||||
" previous name input")); |
||||
const valtype& name = nameOpOut.getOpName (); |
||||
|
||||
if (name.size () > MAX_NAME_LENGTH) |
||||
return state.Invalid (error ("CheckNameTransaction: name too long")); |
||||
if (nameOpOut.getOpValue ().size () > MAX_VALUE_LENGTH) |
||||
return state.Invalid (error ("CheckNameTransaction: value too long")); |
||||
|
||||
/* Process NAME_UPDATE next. */ |
||||
|
||||
if (nameOpOut.getNameOp () == OP_NAME_UPDATE) |
||||
{ |
||||
if (!nameOpIn.isAnyUpdate ()) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_UPDATE with" |
||||
" prev input that is no update")); |
||||
|
||||
if (name != nameOpIn.getOpName ()) |
||||
return state.Invalid (error ("%s: NAME_UPDATE name mismatch to prev tx" |
||||
" found in %s", __func__, txid)); |
||||
|
||||
/* This is actually redundant, since expired names are removed
|
||||
from the UTXO set and thus not available to be spent anyway. |
||||
But it does not hurt to enforce this here, too. It is also |
||||
exercised by the unit tests. */ |
||||
CNameData oldName; |
||||
if (!view.GetName (name, oldName)) |
||||
return state.Invalid (error ("%s: NAME_UPDATE name does not exist", |
||||
__func__)); |
||||
if (oldName.isExpired (nHeight)) |
||||
return state.Invalid (error ("%s: trying to update expired name", |
||||
__func__)); |
||||
|
||||
/* This is an internal consistency check. If everything is fine,
|
||||
the input coins from the UTXO database should match the |
||||
name database. */ |
||||
assert (static_cast<unsigned> (coinIn.nHeight) == oldName.getHeight ()); |
||||
assert (tx.vin[nameIn].prevout == oldName.getUpdateOutpoint ()); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/* Finally, NAME_FIRSTUPDATE. */ |
||||
|
||||
assert (nameOpOut.getNameOp () == OP_NAME_FIRSTUPDATE); |
||||
if (nameOpIn.getNameOp () != OP_NAME_NEW) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_FIRSTUPDATE" |
||||
" with non-NAME_NEW prev tx")); |
||||
|
||||
/* Maturity of NAME_NEW is checked only if we're not adding
|
||||
to the mempool. */ |
||||
if (!fMempool) |
||||
{ |
||||
assert (static_cast<unsigned> (coinIn.nHeight) != MEMPOOL_HEIGHT); |
||||
if (coinIn.nHeight + MIN_FIRSTUPDATE_DEPTH > nHeight) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_NEW" |
||||
" is not mature for FIRST_UPDATE")); |
||||
} |
||||
|
||||
if (nameOpOut.getOpRand ().size () > 20) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_FIRSTUPDATE" |
||||
" rand too large, %d bytes", |
||||
nameOpOut.getOpRand ().size ())); |
||||
|
||||
{ |
||||
valtype toHash(nameOpOut.getOpRand ()); |
||||
toHash.insert (toHash.end (), name.begin (), name.end ()); |
||||
const uint160 hash = Hash160 (toHash); |
||||
if (hash != uint160 (nameOpIn.getOpHash ())) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_FIRSTUPDATE" |
||||
" hash mismatch")); |
||||
} |
||||
|
||||
CNameData oldName; |
||||
if (view.GetName (name, oldName) && !oldName.isExpired (nHeight)) |
||||
return state.Invalid (error ("CheckNameTransaction: NAME_FIRSTUPDATE" |
||||
" on an unexpired name")); |
||||
|
||||
/* We don't have to specifically check that miners don't create blocks with
|
||||
conflicting NAME_FIRSTUPDATE's, since the mining's CCoinsViewCache |
||||
takes care of this with the check above already. */ |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void |
||||
ApplyNameTransaction (const CTransaction& tx, unsigned nHeight, |
||||
CCoinsViewCache& view, CBlockUndo& undo) |
||||
{ |
||||
assert (nHeight != MEMPOOL_HEIGHT); |
||||
|
||||
/* Handle historic bugs that should *not* be applied. Names that are
|
||||
outputs should be marked as unspendable in this case. Otherwise, |
||||
we get an inconsistency between the UTXO set and the name database. */ |
||||
CChainParams::BugType type; |
||||
const uint256 txHash = tx.GetHash (); |
||||
if (Params ().IsHistoricBug (txHash, nHeight, type) |
||||
&& type != CChainParams::BUG_FULLY_APPLY) |
||||
{ |
||||
if (type == CChainParams::BUG_FULLY_IGNORE) |
||||
for (unsigned i = 0; i < tx.vout.size (); ++i) |
||||
{ |
||||
const CNameScript op(tx.vout[i].scriptPubKey); |
||||
if (op.isNameOp () && op.isAnyUpdate ()) |
||||
view.SpendCoin (COutPoint (txHash, i)); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
/* This check must be done *after* the historic bug fixing above! Some
|
||||
of the names that must be handled above are actually produced by |
||||
transactions *not* marked as Namecoin tx. */ |
||||
if (!tx.IsNamecoin ()) |
||||
return; |
||||
|
||||
/* Changes are encoded in the outputs. We don't have to do any checks,
|
||||
so simply apply all these. */ |
||||
|
||||
for (unsigned i = 0; i < tx.vout.size (); ++i) |
||||
{ |
||||
const CNameScript op(tx.vout[i].scriptPubKey); |
||||
if (op.isNameOp () && op.isAnyUpdate ()) |
||||
{ |
||||
const valtype& name = op.getOpName (); |
||||
LogPrint (BCLog::NAMES, "Updating name at height %d: %s\n", |
||||
nHeight, ValtypeToString (name).c_str ()); |
||||
|
||||
CNameTxUndo opUndo; |
||||
opUndo.fromOldState (name, view); |
||||
undo.vnameundo.push_back (opUndo); |
||||
|
||||
CNameData data; |
||||
data.fromScript (nHeight, COutPoint (tx.GetHash (), i), op); |
||||
view.SetName (name, data, false); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool |
||||
ExpireNames (unsigned nHeight, CCoinsViewCache& view, CBlockUndo& undo, |
||||
std::set<valtype>& names) |
||||
{ |
||||
names.clear (); |
||||
|
||||
/* The genesis block contains no name expirations. */ |
||||
if (nHeight == 0) |
||||
return true; |
||||
|
||||
/* Otherwise, find out at which update heights names have expired
|
||||
since the last block. If the expiration depth changes, this could |
||||
be multiple heights at once. */ |
||||
|
||||
const Consensus::Params& params = Params ().GetConsensus (); |
||||
const unsigned expDepthOld = params.rules->NameExpirationDepth (nHeight - 1); |
||||
const unsigned expDepthNow = params.rules->NameExpirationDepth (nHeight); |
||||
|
||||
if (expDepthNow > nHeight) |
||||
return true; |
||||
|
||||
/* Both are inclusive! The last expireTo was nHeight - 1 - expDepthOld,
|
||||
now we start at this value + 1. */ |
||||
const unsigned expireFrom = nHeight - expDepthOld; |
||||
const unsigned expireTo = nHeight - expDepthNow; |
||||
|
||||
/* It is possible that expireFrom = expireTo + 1, in case that the
|
||||
expiration period is raised together with the block height. In this |
||||
case, no names expire in the current step. This case means that |
||||
the absolute expiration height "n - expirationDepth(n)" is |
||||
flat -- which is fine. */ |
||||
assert (expireFrom <= expireTo + 1); |
||||
|
||||
/* Find all names that expire at those depths. Note that GetNamesForHeight
|
||||
clears the output set, to we union all sets here. */ |
||||
for (unsigned h = expireFrom; h <= expireTo; ++h) |
||||
{ |
||||
std::set<valtype> newNames; |
||||
view.GetNamesForHeight (h, newNames); |
||||
names.insert (newNames.begin (), newNames.end ()); |
||||
} |
||||
|
||||
/* Expire all those names. */ |
||||
for (std::set<valtype>::const_iterator i = names.begin (); |
||||
i != names.end (); ++i) |
||||
{ |
||||
const std::string nameStr = ValtypeToString (*i); |
||||
|
||||
CNameData data; |
||||
if (!view.GetName (*i, data)) |
||||
return error ("%s : name '%s' not found in the database", |
||||
__func__, nameStr.c_str ()); |
||||
if (!data.isExpired (nHeight)) |
||||
return error ("%s : name '%s' is not actually expired", |
||||
__func__, nameStr.c_str ()); |
||||
|
||||
/* Special rule: When d/postmortem expires (the name used by
|
||||
libcoin in the name-stealing demonstration), it's coin |
||||
is already spent. Ignore. */ |
||||
if (nHeight == 175868 && nameStr == "d/postmortem") |
||||
continue; |
||||
|
||||
const COutPoint& out = data.getUpdateOutpoint (); |
||||
Coin coin; |
||||
if (!view.GetCoin(out, coin)) |
||||
return error ("%s : name coin for '%s' is not available", |
||||
__func__, nameStr.c_str ()); |
||||
const CNameScript nameOp(coin.out.scriptPubKey); |
||||
if (!nameOp.isNameOp () || !nameOp.isAnyUpdate () |
||||
|| nameOp.getOpName () != *i) |
||||
return error ("%s : name coin to be expired is wrong script", __func__); |
||||
|
||||
if (!view.SpendCoin (out, &coin)) |
||||
return error ("%s : spending name coin failed", __func__); |
||||
undo.vexpired.push_back (coin); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool |
||||
UnexpireNames (unsigned nHeight, CBlockUndo& undo, CCoinsViewCache& view, |
||||
std::set<valtype>& names) |
||||
{ |
||||
names.clear (); |
||||
|
||||
/* The genesis block contains no name expirations. */ |
||||
if (nHeight == 0) |
||||
return true; |
||||
|
||||
std::vector<Coin>::reverse_iterator i; |
||||
for (i = undo.vexpired.rbegin (); i != undo.vexpired.rend (); ++i) |
||||
{ |
||||
const CNameScript nameOp(i->out.scriptPubKey); |
||||
if (!nameOp.isNameOp () || !nameOp.isAnyUpdate ()) |
||||
return error ("%s : wrong script to be unexpired", __func__); |
||||
|
||||
const valtype& name = nameOp.getOpName (); |
||||
if (names.count (name) > 0) |
||||
return error ("%s : name '%s' unexpired twice", |
||||
__func__, ValtypeToString (name).c_str ()); |
||||
names.insert (name); |
||||
|
||||
CNameData data; |
||||
if (!view.GetName (nameOp.getOpName (), data)) |
||||
return error ("%s : no data for name '%s' to be unexpired", |
||||
__func__, ValtypeToString (name).c_str ()); |
||||
if (!data.isExpired (nHeight) || data.isExpired (nHeight - 1)) |
||||
return error ("%s : name '%s' to be unexpired is not expired in the DB" |
||||
" or it was already expired before the current height", |
||||
__func__, ValtypeToString (name).c_str ()); |
||||
|
||||
if (ApplyTxInUndo (std::move(*i), view, |
||||
data.getUpdateOutpoint ()) != DISCONNECT_OK) |
||||
return error ("%s : failed to undo name coin spending", __func__); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void |
||||
CheckNameDB (bool disconnect) |
||||
{ |
||||
const int option |
||||
= gArgs.GetArg ("-checknamedb", Params ().DefaultCheckNameDB ()); |
||||
|
||||
if (option == -1) |
||||
return; |
||||
|
||||
assert (option >= 0); |
||||
if (option != 0) |
||||
{ |
||||
if (disconnect || chainActive.Height () % option != 0) |
||||
return; |
||||
} |
||||
|
||||
pcoinsTip->Flush (); |
||||
const bool ok = pcoinsTip->ValidateNameDB (); |
||||
|
||||
/* The DB is inconsistent (mismatch between UTXO set and names DB) between
|
||||
(roughly) blocks 139,000 and 180,000. This is caused by libcoin's |
||||
"name stealing" bug. For instance, d/postmortem is removed from |
||||
the UTXO set shortly after registration (when it is used to steal |
||||
names), but it remains in the name DB until it expires. */ |
||||
if (!ok) |
||||
{ |
||||
const unsigned nHeight = chainActive.Height (); |
||||
LogPrintf ("ERROR: %s : name database is inconsistent\n", __func__); |
||||
if (nHeight >= 139000 && nHeight <= 180000) |
||||
LogPrintf ("This is expected due to 'name stealing'.\n"); |
||||
else |
||||
assert (false); |
||||
} |
||||
} |
@ -0,0 +1,324 @@
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) 2018 Jianping Wu
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef H_BITCOIN_KEVA_MAIN |
||||
#define H_BITCOIN_KEVA_MAIN |
||||
|
||||
#include <amount.h> |
||||
#include <keva/common.h> |
||||
#include <primitives/transaction.h> |
||||
#include <serialize.h> |
||||
#include <uint256.h> |
||||
|
||||
#include <list> |
||||
#include <map> |
||||
#include <memory> |
||||
#include <set> |
||||
#include <string> |
||||
|
||||
class CBlockUndo; |
||||
class CCoinsView; |
||||
class CCoinsViewCache; |
||||
class CTxMemPool; |
||||
class CTxMemPoolEntry; |
||||
class CValidationState; |
||||
|
||||
/* Some constants defining namespace, key and value limits. */ |
||||
static const unsigned MAX_NAMESPACE_LENGTH = 255; |
||||
static const unsigned MAX_KEY_LENGTH = 255; |
||||
static const unsigned MAX_VALUE_LENGTH = 1023; |
||||
|
||||
/** The amount of coins to lock in created transactions. */ |
||||
static const CAmount KEVA_LOCKED_AMOUNT = COIN / 100; |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CNameTxUndo. */ |
||||
|
||||
/**
|
||||
* Undo information for one name operation. This contains either the |
||||
* information that the name was newly created (and should thus be |
||||
* deleted entirely) or that it was updated including the old value. |
||||
*/ |
||||
class CNameTxUndo |
||||
{ |
||||
|
||||
private: |
||||
|
||||
/** The name this concerns. */ |
||||
valtype name; |
||||
|
||||
/** Whether this was an entirely new name (no update). */ |
||||
bool isNew; |
||||
|
||||
/** The old name value that was overwritten by the operation. */ |
||||
CNameData oldData; |
||||
|
||||
public: |
||||
|
||||
ADD_SERIALIZE_METHODS; |
||||
|
||||
template<typename Stream, typename Operation> |
||||
inline void SerializationOp (Stream& s, Operation ser_action) |
||||
{ |
||||
READWRITE (name); |
||||
READWRITE (isNew); |
||||
if (!isNew) |
||||
READWRITE (oldData); |
||||
} |
||||
|
||||
/**
|
||||
* Set the data for an update/registration of the given name. The CCoinsView |
||||
* is used to find out all the necessary information. |
||||
* @param nm The name that is being updated. |
||||
* @param view The (old!) chain state. |
||||
*/ |
||||
void fromOldState (const valtype& nm, const CCoinsView& view); |
||||
|
||||
/**
|
||||
* Apply the undo to the chain state given. |
||||
* @param view The chain state to update ("undo"). |
||||
*/ |
||||
void apply (CCoinsViewCache& view) const; |
||||
|
||||
}; |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CKevaMemPool. */ |
||||
|
||||
/**
|
||||
* Handle the keva component of the transaction mempool. This keeps track |
||||
* of keva operations that are in the mempool and ensures that all transactions |
||||
* kept are consistent. E. g., no two transactions are allowed to register |
||||
* the same namespace, or the same name with the same namespace. |
||||
*/ |
||||
class CKevaMemPool |
||||
{ |
||||
|
||||
private: |
||||
|
||||
/** The parent mempool object. Used to, e. g., remove conflicting tx. */ |
||||
CTxMemPool& pool; |
||||
|
||||
/** Type used for internal indices. */ |
||||
typedef std::map<valtype, uint256> NamespaceTxMap; |
||||
|
||||
/** Type used for indexing Tx */ |
||||
typedef std::tuple<valtype, valtype> NamespaceKeyTuple; |
||||
|
||||
/** Type used for internal indices. */ |
||||
typedef std::map<NamespaceKeyTuple, uint256> NamespaceKeyTxMap; |
||||
|
||||
|
||||
/**
|
||||
* Keep track of namespaces that are registered by transactions in the pool. |
||||
* Map name to registering transaction. |
||||
*/ |
||||
NamespaceTxMap mapNamespaceRegs; |
||||
|
||||
/** Map pending name updates to transaction IDs. */ |
||||
NamespaceTxMap mapNamespaceUpdates; |
||||
|
||||
/**
|
||||
* Keep track of key that are registered by transactions in the pool. |
||||
* Map key to registering transaction. |
||||
*/ |
||||
NamespaceKeyTxMap mapNamespaceKeyRegs; |
||||
|
||||
public: |
||||
|
||||
/**
|
||||
* Construct with reference to parent mempool. |
||||
* @param p The parent pool. |
||||
*/ |
||||
explicit inline CNameMemPool (CTxMemPool& p) |
||||
: pool(p), mapNamespaceRegs(), mapKeyRegs(), mapNameUpdates() |
||||
{} |
||||
|
||||
/**
|
||||
* Check whether a particular namespace is being registered by |
||||
* some transaction in the mempool. Does not lock, this is |
||||
* done by the parent mempool (which calls through afterwards). |
||||
* @param name The name to check for. |
||||
* @return True iff there's a matching namespace registration in the pool. |
||||
*/ |
||||
inline bool |
||||
registersNamespace (const valtype& namespace) const |
||||
{ |
||||
return mapNamespaceRegs.count (name) > 0; |
||||
} |
||||
|
||||
/**
|
||||
* Check whether a particular name has a pending update. Does not lock. |
||||
* @param name The name to check for. |
||||
* @return True iff there's a matching name update in the pool. |
||||
*/ |
||||
inline bool |
||||
updatesName (const valtype& name) const |
||||
{ |
||||
return mapNameUpdates.count (name) > 0; |
||||
} |
||||
|
||||
/**
|
||||
* Return txid of transaction registering or updating a name. The returned |
||||
* txid is null if no such tx exists. |
||||
* @param name The name to check for. |
||||
* @return The txid that registers/updates it. Null if none. |
||||
*/ |
||||
uint256 getTxForName (const valtype& name) const; |
||||
|
||||
/**
|
||||
* Clear all data. |
||||
*/ |
||||
inline void |
||||
clear () |
||||
{ |
||||
mapNameRegs.clear (); |
||||
mapNameUpdates.clear (); |
||||
mapNameNews.clear (); |
||||
} |
||||
|
||||
/**
|
||||
* Add an entry without checking it. It should have been checked |
||||
* already. If this conflicts with the mempool, it may throw. |
||||
* @param hash The tx hash. |
||||
* @param entry The new mempool entry. |
||||
*/ |
||||
void addUnchecked (const uint256& hash, const CTxMemPoolEntry& entry); |
||||
|
||||
/**
|
||||
* Remove the given mempool entry. It is assumed that it is present. |
||||
* @param entry The entry to remove. |
||||
*/ |
||||
void remove (const CTxMemPoolEntry& entry); |
||||
|
||||
/**
|
||||
* Remove conflicts for the given tx, based on name operations. I. e., |
||||
* if the tx registers a name that conflicts with another registration |
||||
* in the mempool, detect this and remove the mempool tx accordingly. |
||||
* @param tx The transaction for which we look for conflicts. |
||||
* @param removed Put removed tx here. |
||||
*/ |
||||
void removeConflicts (const CTransaction& tx); |
||||
|
||||
/**
|
||||
* Remove conflicts in the mempool due to unexpired names. This removes |
||||
* conflicting name registrations that are no longer possible. |
||||
* @param unexpired The set of unexpired names. |
||||
* @param removed Put removed tx here. |
||||
*/ |
||||
void removeUnexpireConflicts (const std::set<valtype>& unexpired); |
||||
/**
|
||||
* Remove conflicts in the mempool due to expired names. This removes |
||||
* conflicting name updates that are no longer possible. |
||||
* @param expired The set of expired names. |
||||
* @param removed Put removed tx here. |
||||
*/ |
||||
void removeExpireConflicts (const std::set<valtype>& expired); |
||||
|
||||
/**
|
||||
* Perform sanity checks. Throws if it fails. |
||||
* @param coins The coins view this represents. |
||||
*/ |
||||
void check (const CCoinsView& coins) const; |
||||
|
||||
/**
|
||||
* Check if a tx can be added (based on name criteria) without |
||||
* causing a conflict. |
||||
* @param tx The transaction to check. |
||||
* @return True if it doesn't conflict. |
||||
*/ |
||||
bool checkTx (const CTransaction& tx) const; |
||||
|
||||
}; |
||||
|
||||
/* ************************************************************************** */ |
||||
/* CNameConflictTracker. */ |
||||
|
||||
/**
|
||||
* Utility class that listens to a mempool's removal notifications to track |
||||
* name conflicts. This is used for DisconnectTip and unit testing. |
||||
*/ |
||||
class CNameConflictTracker |
||||
{ |
||||
|
||||
private: |
||||
|
||||
std::shared_ptr<std::vector<CTransactionRef>> txNameConflicts; |
||||
CTxMemPool& pool; |
||||
|
||||
public: |
||||
|
||||
explicit CNameConflictTracker (CTxMemPool &p); |
||||
~CNameConflictTracker (); |
||||
|
||||
inline const std::shared_ptr<const std::vector<CTransactionRef>> |
||||
GetNameConflicts () const |
||||
{ |
||||
return txNameConflicts; |
||||
} |
||||
|
||||
void AddConflictedEntry (CTransactionRef txRemoved); |
||||
|
||||
}; |
||||
|
||||
/* ************************************************************************** */ |
||||
|
||||
/**
|
||||
* Check a transaction according to the additional Namecoin rules. This |
||||
* ensures that all name operations (if any) are valid and that it has |
||||
* name operations iff it is marked as Namecoin tx by its version. |
||||
* @param tx The transaction to check. |
||||
* @param nHeight Height at which the tx will be. |
||||
* @param view The current chain state. |
||||
* @param state Resulting validation state. |
||||
* @param flags Verification flags. |
||||
* @return True in case of success. |
||||
*/ |
||||
bool CheckNameTransaction (const CTransaction& tx, unsigned nHeight, |
||||
const CCoinsView& view, |
||||
CValidationState& state, unsigned flags); |
||||
|
||||
/**
|
||||
* Apply the changes of a name transaction to the name database. |
||||
* @param tx The transaction to apply. |
||||
* @param nHeight Height at which the tx is. Used for CNameData. |
||||
* @param view The chain state to update. |
||||
* @param undo Record undo information here. |
||||
*/ |
||||
void ApplyNameTransaction (const CTransaction& tx, unsigned nHeight, |
||||
CCoinsViewCache& view, CBlockUndo& undo); |
||||
|
||||
/**
|
||||
* Expire all names at the given height. This removes their coins |
||||
* from the UTXO set. |
||||
* @param height The new block height. |
||||
* @param view The coins view to update. |
||||
* @param undo The block undo object to record undo information. |
||||
* @param names List all expired names here. |
||||
* @return True if successful. |
||||
*/ |
||||
bool ExpireNames (unsigned nHeight, CCoinsViewCache& view, CBlockUndo& undo, |
||||
std::set<valtype>& names); |
||||
|
||||
/**
|
||||
* Undo name coin expirations. This also does some checks verifying |
||||
* that all is fine. |
||||
* @param nHeight The height at which the names were expired. |
||||
* @param undo The block undo object to use. |
||||
* @param view The coins view to update. |
||||
* @param names List all unexpired names here. |
||||
* @return True if successful. |
||||
*/ |
||||
bool UnexpireNames (unsigned nHeight, CBlockUndo& undo, |
||||
CCoinsViewCache& view, std::set<valtype>& names); |
||||
|
||||
/**
|
||||
* Check the name database consistency. This calls CCoinsView::ValidateNameDB, |
||||
* but only if applicable depending on the -checknamedb setting. If it fails, |
||||
* this throws an assertion failure. |
||||
* @param disconnect Whether we are disconnecting blocks. |
||||
*/ |
||||
void CheckNameDB (bool disconnect); |
||||
|
||||
#endif // H_BITCOIN_KEVA_MAIN
|
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2018 Jianping Wu
|
||||
// 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 "coins.h" |
||||
#include "init.h" |
||||
#include "keva/common.h" |
||||
#include "keva/main.h" |
||||
#include "primitives/transaction.h" |
||||
#include "random.h" |
||||
#include "rpc/mining.h" |
||||
#include "rpc/safemode.h" |
||||
#include "rpc/server.h" |
||||
#include "script/kava.h" |
||||
#include "txmempool.h" |
||||
#include "util.h" |
||||
#include "validation.h" |
||||
#include "wallet/coincontrol.h" |
||||
#include "wallet/wallet.h" |
||||
|
||||
#include <univalue.h> |
||||
|
||||
|
||||
/* ************************************************************************** */ |
||||
|
||||
UniValue |
||||
keva_put (const JSONRPCRequest& request) |
||||
{ |
||||
CWallet* const pwallet = GetWalletForJSONRPCRequest(request); |
||||
if (!EnsureWalletIsAvailable (pwallet, request.fHelp)) |
||||
return NullUniValue; |
||||
|
||||
if (request.fHelp |
||||
|| (request.params.size () != 3 && request.params.size () != 4)) |
||||
throw std::runtime_error ( |
||||
"keva_put \"namespace\" \"key\" \"value\" (\"create_namespace\")\n" |
||||
"\nUpdate a name and possibly transfer it.\n" |
||||
+ HelpRequiringPassphrase (pwallet) + |
||||
"\nArguments:\n" |
||||
"1. \"namespace\" (string, required) the namespace to insert the key to\n" |
||||
"2. \"key\" (string, required) value for the key\n" |
||||
"4. \"value\" (string, required) value for the name\n" |
||||
"5. \"create_namespace\" (boolean, optional, default=false) create the namespace if it does not exist yet\n" |
||||
"\nResult:\n" |
||||
"\"txid\" (string) the keva_put's txid\n" |
||||
"\nExamples:\n" |
||||
+ HelpExampleCli ("keva_put", "\"mynamespace\", \"new-key\", \"new-value\"") |
||||
+ HelpExampleCli ("keva_put", "\"mynamespace\", \"new-key\", \"new-value\", true") |
||||
); |
||||
|
||||
RPCTypeCheck (request.params, |
||||
{UniValue::VSTR, UniValue::VSTR, UniValue::VSTR, UniValue::VSTR, UniValue::VBOOL}); |
||||
|
||||
ObserveSafeMode (); |
||||
|
||||
const std::string namespaceStr = request.params[0].get_str (); |
||||
const valtype namespaceVal = ValtypeFromString (namespaceStr); |
||||
if (namespaceVal.size () > MAX_NAMESPACE_LENGTH) |
||||
throw JSONRPCError (RPC_INVALID_PARAMETER, "the namespace is too long"); |
||||
|
||||
const std::string keyStr = request.params[1].get_str (); |
||||
const valtype key = ValtypeFromString (keyStr); |
||||
if (key.size () > MAX_KEY_LENGTH) |
||||
throw JSONRPCError (RPC_INVALID_PARAMETER, "the key is too long"); |
||||
|
||||
const std::string valueStr = request.params[2].get_str (); |
||||
const valtype value = ValtypeFromString (valueStr); |
||||
if (value.size () > MAX_VALUE_LENGTH) |
||||
throw JSONRPCError (RPC_INVALID_PARAMETER, "the value is too long"); |
||||
|
||||
bool createNamespace = false; |
||||
if (!request.params[3].isNull()) { |
||||
createNamespace = request.params[3].get_bool(); |
||||
} |
||||
|
||||
/* Reject updates to a name for which the mempool already has
|
||||
a pending update. This is not a hard rule enforced by network |
||||
rules, but it is necessary with the current mempool implementation. */ |
||||
{ |
||||
LOCK (mempool.cs); |
||||
if (mempool.updatesName (name)) |
||||
throw JSONRPCError (RPC_TRANSACTION_ERROR, |
||||
"there is already a pending update for this name"); |
||||
} |
||||
|
||||
CNameData oldData; |
||||
{ |
||||
LOCK (cs_main); |
||||
if (!pcoinsTip->GetName (name, oldData) || oldData.isExpired ()) |
||||
throw JSONRPCError (RPC_TRANSACTION_ERROR, |
||||
"this name can not be updated"); |
||||
} |
||||
|
||||
const COutPoint outp = oldData.getUpdateOutpoint (); |
||||
const CTxIn txIn(outp); |
||||
|
||||
/* No more locking required, similarly to name_new. */ |
||||
|
||||
EnsureWalletIsUnlocked (pwallet); |
||||
|
||||
CReserveKey keyName(pwallet); |
||||
CPubKey pubKeyReserve; |
||||
const bool ok = keyName.GetReservedKey (pubKeyReserve, true); |
||||
assert (ok); |
||||
bool usedKey = false; |
||||
|
||||
CScript addrName; |
||||
if (request.params.size () == 3) |
||||
{ |
||||
keyName.ReturnKey (); |
||||
const CTxDestination dest |
||||
= DecodeDestination (request.params[2].get_str ()); |
||||
if (!IsValidDestination (dest)) |
||||
throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, "invalid address"); |
||||
|
||||
addrName = GetScriptForDestination (dest); |
||||
} |
||||
else |
||||
{ |
||||
usedKey = true; |
||||
addrName = GetScriptForDestination (pubKeyReserve.GetID ()); |
||||
} |
||||
|
||||
const CScript nameScript |
||||
= CNameScript::buildNameUpdate (addrName, name, value); |
||||
|
||||
CCoinControl coinControl; |
||||
CWalletTx wtx; |
||||
SendMoneyToScript (pwallet, nameScript, &txIn, |
||||
NAME_LOCKED_AMOUNT, false, wtx, coinControl); |
||||
|
||||
if (usedKey) |
||||
keyName.KeepKey (); |
||||
|
||||
return wtx.GetHash ().GetHex (); |
||||
} |
Loading…
Reference in new issue