Jianping Wu
6 years ago
7 changed files with 1228 additions and 0 deletions
@ -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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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