mirror of
https://github.com/kvazar-network/kevacoin.git
synced 2025-01-14 00:58:09 +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
|
libbitcoinconsensus.pc
|
||||||
contrib/devtools/split-debug.sh
|
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 importmulti(const JSONRPCRequest& request);
|
||||||
extern UniValue rescanblockchain(const JSONRPCRequest& request);
|
extern UniValue rescanblockchain(const JSONRPCRequest& request);
|
||||||
|
|
||||||
|
extern UniValue keva_put(const JSONRPCRequest& request); // in rpckeva.cpp
|
||||||
|
|
||||||
static const CRPCCommand commands[] =
|
static const CRPCCommand commands[] =
|
||||||
{ // category name actor (function) argNames
|
{ // category name actor (function) argNames
|
||||||
// --------------------- ------------------------ ----------------------- ----------
|
// --------------------- ------------------------ ----------------------- ----------
|
||||||
@ -3588,6 +3590,9 @@ static const CRPCCommand commands[] =
|
|||||||
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||||
|
|
||||||
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
||||||
|
|
||||||
|
// Kevacoin-specific wallet calls.
|
||||||
|
{ "kevacoin", "keva_put", &keva_put, {"namespace", "key", "value", "create_namespace"} }
|
||||||
};
|
};
|
||||||
|
|
||||||
void RegisterWalletRPCCommands(CRPCTable &t)
|
void RegisterWalletRPCCommands(CRPCTable &t)
|
||||||
|
Loading…
Reference in New Issue
Block a user