mirror of
https://github.com/kvazar-network/kevacoin.git
synced 2025-01-13 16:48:08 +00:00
WIP: started implementing namespace, key and value.
This commit is contained in:
parent
ec7aaa8b1f
commit
4837682b4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -115,3 +115,5 @@ test/cache/*
|
||||
|
||||
libbitcoinconsensus.pc
|
||||
contrib/devtools/split-debug.sh
|
||||
|
||||
*/.vscode/*
|
||||
|
0
src/keva/common.h
Normal file
0
src/keva/common.h
Normal file
760
src/keva/main.cpp
Normal file
760
src/keva/main.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
324
src/keva/main.h
Normal file
324
src/keva/main.h
Normal file
@ -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
src/script/keva.h
Normal file
0
src/script/keva.h
Normal file
137
src/wallet/rpckeva.cpp
Normal file
137
src/wallet/rpckeva.cpp
Normal file
@ -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 ();
|
||||
}
|
@ -3532,6 +3532,8 @@ extern UniValue removeprunedfunds(const JSONRPCRequest& request);
|
||||
extern UniValue importmulti(const JSONRPCRequest& request);
|
||||
extern UniValue rescanblockchain(const JSONRPCRequest& request);
|
||||
|
||||
extern UniValue keva_put(const JSONRPCRequest& request); // in rpckeva.cpp
|
||||
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) argNames
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
@ -3588,6 +3590,9 @@ static const CRPCCommand commands[] =
|
||||
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||
|
||||
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
||||
|
||||
// Kevacoin-specific wallet calls.
|
||||
{ "kevacoin", "keva_put", &keva_put, {"namespace", "key", "value", "create_namespace"} }
|
||||
};
|
||||
|
||||
void RegisterWalletRPCCommands(CRPCTable &t)
|
||||
|
Loading…
Reference in New Issue
Block a user