Browse Source

WIP: started implementing namespace, key and value.

cn
Jianping Wu 6 years ago
parent
commit
4837682b4d
  1. 2
      .gitignore
  2. 0
      src/keva/common.h
  3. 760
      src/keva/main.cpp
  4. 324
      src/keva/main.h
  5. 0
      src/script/keva.h
  6. 137
      src/wallet/rpckeva.cpp
  7. 5
      src/wallet/rpcwallet.cpp

2
.gitignore vendored

@ -115,3 +115,5 @@ test/cache/* @@ -115,3 +115,5 @@ test/cache/*
libbitcoinconsensus.pc
contrib/devtools/split-debug.sh
*/.vscode/*

0
src/keva/common.h

760
src/keva/main.cpp

@ -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);
}
}

324
src/keva/main.h

@ -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
src/script/keva.h

137
src/wallet/rpckeva.cpp

@ -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 ();
}

5
src/wallet/rpcwallet.cpp

@ -3532,6 +3532,8 @@ extern UniValue removeprunedfunds(const JSONRPCRequest& request); @@ -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[] = @@ -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…
Cancel
Save