kevacoin/src/keva/main.cpp
Jianping Wu e273036ab0 Cleaned up code - removed dead code from namecoin.
Removed lots of legacy checks from namecoin.
2018-11-26 20:51:19 -08:00

414 lines
12 KiB
C++

// Copyright (c) 2014-2017 Daniel Kraft
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// 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>
/* ************************************************************************** */
/* CKevaTxUndo. */
void
CKevaTxUndo::fromOldState(const valtype& nameSpace, const valtype& key, const CCoinsView& view)
{
this->nameSpace = nameSpace;
this->key = key;
isNew = !view.GetName(nameSpace, key, oldData);
}
void
CKevaTxUndo::apply(CCoinsViewCache& view) const
{
if (isNew)
view.DeleteName(nameSpace, key);
else
view.SetName(nameSpace, key, oldData, true);
}
/* ************************************************************************** */
/* CKevaMemPool. */
void
CKevaMemPool::addUnchecked (const uint256& hash, const CTxMemPoolEntry& entry)
{
AssertLockHeld (pool.cs);
if (entry.isNamespaceRegistration()) {
const valtype& nameSpace = entry.getNamespace();
listUnconfirmedNamespaces.push_back(std::make_tuple(hash, nameSpace, entry.getDisplayName()));
}
if (entry.isNamespaceKeyUpdate ()) {
const valtype& nameSpace = entry.getNamespace();
listUnconfirmedKeyValues.push_back(std::make_tuple(hash, nameSpace, entry.getKey(), entry.getValue()));
}
}
bool
CKevaMemPool::getUnconfirmedKeyValue(const valtype& nameSpace, const valtype& key, valtype& value) const {
bool found = false;
for (auto entry : listUnconfirmedKeyValues) {
auto ns = std::get<1>(entry);
auto k = std::get<2>(entry);
if (ns == nameSpace && key == k) {
value = std::get<3>(entry);
found = true;
}
}
return found;
}
bool
CKevaMemPool::getUnconfirmedNamespaces(std::vector<std::tuple<valtype, valtype>>& nameSpaces) const {
bool found = false;
for (auto entry : listUnconfirmedNamespaces) {
auto ns = std::get<1>(entry);
auto displayName = std::get<2>(entry);
nameSpaces.push_back(std::make_tuple(ns, displayName));
found = true;
}
return found;
}
void CKevaMemPool::remove(const CTxMemPoolEntry& entry)
{
AssertLockHeld (pool.cs);
if (entry.isNamespaceRegistration()) {
auto hash = entry.GetTx().GetHash();
for (auto iter = listUnconfirmedNamespaces.begin(); iter != listUnconfirmedNamespaces.end(); ++iter) {
if (std::get<0>(*iter) == hash) {
listUnconfirmedNamespaces.erase(iter);
break;
}
}
}
if (entry.isNamespaceKeyUpdate()) {
auto hash = entry.GetTx().GetHash();
for (auto iter = listUnconfirmedKeyValues.begin(); iter != listUnconfirmedKeyValues.end(); ++iter) {
if (std::get<0>(*iter) == hash) {
listUnconfirmedKeyValues.erase(iter);
break;
}
}
}
}
void
CKevaMemPool::removeConflicts(const CTransaction& tx)
{
// JWU TODO: is this required at all?
#if 0
AssertLockHeld (pool.cs);
if (!tx.IsKevacoin ())
return;
for (const auto& txout : tx.vout) {
const CKevaScript nameOp(txout.scriptPubKey);
if (nameOp.isKevaOp() && nameOp.getKevaOp() == OP_KEVA_PUT) {
const valtype& nameSpace = nameOp.getOpNamespace();
const NamespaceTxMap::const_iterator mit = mapNamespaceRegs.find(nameSpace);
if (mit != mapNamespaceRegs.end()) {
const CTxMemPool::txiter mit2 = pool.mapTx.find(mit->second);
assert(mit2 != pool.mapTx.end());
pool.removeRecursive (mit2->GetTx(),
MemPoolRemovalReason::KEVA_CONFLICT);
}
}
}
#endif
}
bool CKevaMemPool::validateNamespace(const CTransaction& tx, const valtype& nameSpace) const
{
valtype kevaNamespace = ToByteVector(Hash160(ToByteVector(tx.vin[0].prevout.hash)));
kevaNamespace.insert(kevaNamespace.begin(), CKevaScript::NAMESPACE_PREFIX);
return kevaNamespace == nameSpace;
}
bool
CKevaMemPool::checkTx(const CTransaction& tx) const
{
AssertLockHeld (pool.cs);
if (!tx.IsKevacoin ()) {
return true;
}
for (const auto& txout : tx.vout) {
const CKevaScript nameOp(txout.scriptPubKey);
if (!nameOp.isKevaOp ())
continue;
switch (nameOp.getKevaOp ()) {
case OP_KEVA_NAMESPACE:
{
const valtype& nameSpace = nameOp.getOpNamespace();
std::map<valtype, uint256>::const_iterator mi;
if (!validateNamespace(tx, nameSpace)) {
return false;
}
break;
}
case OP_KEVA_PUT:
{
break;
}
default:
assert (false);
}
}
return true;
}
/* ************************************************************************** */
/* CNameConflictTracker. */
namespace
{
void
ConflictTrackerNotifyEntryRemoved (CNameConflictTracker* tracker,
CTransactionRef txRemoved,
MemPoolRemovalReason reason)
{
if (reason == MemPoolRemovalReason::KEVA_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
CheckKevaTransaction (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 ();
/*
As a first step, try to locate inputs and outputs of the transaction
that are keva scripts. At most one input and output should be
a keva operation.
*/
int nameIn = -1;
CKevaScript 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 CKevaScript op(coin.out.scriptPubKey);
if (op.isKevaOp()) {
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;
CKevaScript nameOpOut;
for (unsigned i = 0; i < tx.vout.size(); ++i) {
const CKevaScript op(tx.vout[i].scriptPubKey);
if (op.isKevaOp()) {
if (nameOut != -1) {
return state.Invalid(error("%s: multiple name outputs from transaction %s", __func__, txid));
}
nameOut = i;
nameOpOut = op;
}
}
/*
Check that no keva inputs/outputs are present for a non-Kevacoin tx.
If that's the case, all is fine. For a kevacoin tx instead, there
should be at least an output.
*/
if (!tx.IsKevacoin()) {
if (nameIn != -1) {
return state.Invalid(error("%s: non-Kevacoin tx %s has keva inputs", __func__, txid));
}
if (nameOut != -1) {
return state.Invalid (error ("%s: non-Kevacoin tx %s at height %u has keva outputs",
__func__, txid, nHeight));
}
return true;
}
assert(tx.IsKevacoin ());
if (nameOut == -1) {
return state.Invalid (error ("%s: Kevacoin tx %s has no keva outputs", __func__, txid));
}
/* Reject "greedy names". */
if (tx.vout[nameOut].nValue < KEVA_LOCKED_AMOUNT) {
return state.Invalid (error ("%s: greedy name", __func__));
}
if (nameOpOut.isNamespaceRegistration()) {
if (nameOpOut.getOpNamespaceDisplayName().size () > MAX_VALUE_LENGTH) {
return state.Invalid (error ("CheckKevaTransaction: display name value too long"));
}
return true;
}
assert(nameOpOut.isAnyUpdate());
if (nameIn == -1) {
return state.Invalid(error("CheckKevaTransaction: update without previous keva input"));
}
const valtype& key = nameOpOut.getOpKey();
if (key.size() > MAX_KEY_LENGTH) {
return state.Invalid (error ("CheckKevaTransaction: key too long"));
}
if (nameOpOut.getOpValue().size () > MAX_VALUE_LENGTH) {
return state.Invalid (error ("CheckKevaTransaction: value too long"));
}
/* Process KEVA_PUT next. */
const valtype& nameSpace = nameOpOut.getOpNamespace();
if (nameOpOut.getKevaOp() == OP_KEVA_PUT) {
if (!nameOpIn.isAnyUpdate() && !nameOpIn.isNamespaceRegistration()) {
return state.Invalid(error("CheckKevaTransaction: KEVA_PUT with prev input that is no update"));
}
if (nameSpace != nameOpIn.getOpNamespace()) {
return state.Invalid(error("%s: KEVA_PUT namespace mismatch to prev tx found in %s", __func__, txid));
}
}
return true;
}
void ApplyNameTransaction(const CTransaction& tx, unsigned nHeight,
CCoinsViewCache& view, CBlockUndo& undo)
{
assert (nHeight != MEMPOOL_HEIGHT);
if (!tx.IsKevacoin ())
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 CKevaScript op(tx.vout[i].scriptPubKey);
if (!op.isKevaOp()) {
continue;
}
if (op.isNamespaceRegistration()) {
const valtype& nameSpace = op.getOpNamespace();
const valtype& displayName = op.getOpNamespaceDisplayName();
LogPrint (BCLog::KEVA, "Register name at height %d: %s, display name: %s\n",
nHeight, ValtypeToString(nameSpace).c_str(),
ValtypeToString(displayName).c_str());
const valtype& key = ValtypeFromString(CKevaScript::KEVA_DISPLAY_NAME_KEY);
CKevaTxUndo opUndo;
opUndo.fromOldState(nameSpace, key, view);
undo.vkevaundo.push_back(opUndo);
CKevaData data;
data.fromScript(nHeight, COutPoint(tx.GetHash(), i), op);
view.SetName(nameSpace, key, data, false);
} else if (op.isAnyUpdate()) {
const valtype& nameSpace = op.getOpNamespace();
const valtype& key = op.getOpKey();
LogPrint (BCLog::KEVA, "Updating name at height %d: %s\n",
nHeight, ValtypeToString (nameSpace).c_str ());
CKevaTxUndo opUndo;
opUndo.fromOldState(nameSpace, key, view);
undo.vkevaundo.push_back(opUndo);
CKevaData data;
data.fromScript(nHeight, COutPoint(tx.GetHash(), i), op);
view.SetName(nameSpace, key, data, false);
}
}
}
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);
}
}