|
|
@ -51,15 +51,11 @@ CKevaMemPool::addUnchecked (const uint256& hash, const CTxMemPoolEntry& entry) |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
if (entry.isNamespaceRegistration()) { |
|
|
|
if (entry.isNamespaceRegistration()) { |
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
assert(mapNamespaceRegs.count(nameSpace) == 0); |
|
|
|
|
|
|
|
mapNamespaceRegs.insert(std::make_pair(nameSpace, hash)); |
|
|
|
|
|
|
|
listUnconfirmedNamespaces.push_back(std::make_tuple(hash, nameSpace, entry.getDisplayName())); |
|
|
|
listUnconfirmedNamespaces.push_back(std::make_tuple(hash, nameSpace, entry.getDisplayName())); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (entry.isNamespaceKeyUpdate ()) { |
|
|
|
if (entry.isNamespaceKeyUpdate ()) { |
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
assert(mapNamespaceUpdates.count(nameSpace) == 0); |
|
|
|
|
|
|
|
mapNamespaceUpdates.insert (std::make_pair(nameSpace, hash)); |
|
|
|
|
|
|
|
listUnconfirmedKeyValues.push_back(std::make_tuple(hash, nameSpace, entry.getKey(), entry.getValue())); |
|
|
|
listUnconfirmedKeyValues.push_back(std::make_tuple(hash, nameSpace, entry.getKey(), entry.getValue())); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
@ -94,9 +90,6 @@ void CKevaMemPool::remove(const CTxMemPoolEntry& entry) |
|
|
|
{ |
|
|
|
{ |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
if (entry.isNamespaceRegistration()) { |
|
|
|
if (entry.isNamespaceRegistration()) { |
|
|
|
const NamespaceTxMap::iterator mit = mapNamespaceRegs.find(entry.getNamespace()); |
|
|
|
|
|
|
|
assert (mit != mapNamespaceRegs.end()); |
|
|
|
|
|
|
|
mapNamespaceRegs.erase(mit); |
|
|
|
|
|
|
|
auto hash = entry.GetTx().GetHash(); |
|
|
|
auto hash = entry.GetTx().GetHash(); |
|
|
|
for (auto iter = listUnconfirmedNamespaces.begin(); iter != listUnconfirmedNamespaces.end(); ++iter) { |
|
|
|
for (auto iter = listUnconfirmedNamespaces.begin(); iter != listUnconfirmedNamespaces.end(); ++iter) { |
|
|
|
if (std::get<0>(*iter) == hash) { |
|
|
|
if (std::get<0>(*iter) == hash) { |
|
|
@ -107,9 +100,6 @@ void CKevaMemPool::remove(const CTxMemPoolEntry& entry) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (entry.isNamespaceKeyUpdate()) { |
|
|
|
if (entry.isNamespaceKeyUpdate()) { |
|
|
|
const NamespaceTxMap::iterator mit = mapNamespaceUpdates.find(entry.getNamespace()); |
|
|
|
|
|
|
|
assert (mit != mapNamespaceUpdates.end()); |
|
|
|
|
|
|
|
mapNamespaceUpdates.erase(mit); |
|
|
|
|
|
|
|
auto hash = entry.GetTx().GetHash(); |
|
|
|
auto hash = entry.GetTx().GetHash(); |
|
|
|
for (auto iter = listUnconfirmedKeyValues.begin(); iter != listUnconfirmedKeyValues.end(); ++iter) { |
|
|
|
for (auto iter = listUnconfirmedKeyValues.begin(); iter != listUnconfirmedKeyValues.end(); ++iter) { |
|
|
|
if (std::get<0>(*iter) == hash) { |
|
|
|
if (std::get<0>(*iter) == hash) { |
|
|
@ -146,79 +136,6 @@ CKevaMemPool::removeConflicts(const CTransaction& tx) |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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> namespaceKeyUpdates; |
|
|
|
|
|
|
|
for (const auto& entry : pool.mapTx) { |
|
|
|
|
|
|
|
const uint256 txHash = entry.GetTx ().GetHash (); |
|
|
|
|
|
|
|
if (entry.isNamespaceRegistration()) { |
|
|
|
|
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const NamespaceTxMap::const_iterator mit = mapNamespaceRegs.find(nameSpace); |
|
|
|
|
|
|
|
assert (mit != mapNamespaceRegs.end()); |
|
|
|
|
|
|
|
assert (mit->second == txHash); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert (nameRegs.count(nameSpace) == 0); |
|
|
|
|
|
|
|
nameRegs.insert(nameSpace); |
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* 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. */ |
|
|
|
|
|
|
|
CKevaData data; |
|
|
|
|
|
|
|
if (coins.GetName(name, data)) |
|
|
|
|
|
|
|
assert (data.isExpired (nHeight + 1)); |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (entry.isNamespaceKeyUpdate()) { |
|
|
|
|
|
|
|
const valtype& nameSpace = entry.getNamespace(); |
|
|
|
|
|
|
|
const NamespaceTxMap::const_iterator mit = mapNamespaceUpdates.find(nameSpace); |
|
|
|
|
|
|
|
assert (mit != mapNamespaceUpdates.end ()); |
|
|
|
|
|
|
|
assert (mit->second == txHash); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert (namespaceKeyUpdates.count(nameSpace) == 0); |
|
|
|
|
|
|
|
namespaceKeyUpdates.insert(nameSpace); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* As above, use nHeight+1 for the expiration check. */ |
|
|
|
|
|
|
|
CKevaData data; |
|
|
|
|
|
|
|
if (!coins.GetName(name, data)) { |
|
|
|
|
|
|
|
assert (false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
assert (!data.isExpired (nHeight + 1)); |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(nameRegs.size() == mapNamespaceRegs.size()); |
|
|
|
|
|
|
|
assert(namespaceKeyUpdates.size() == mapNamespaceUpdates.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. */ |
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
// Is this neccesary?
|
|
|
|
|
|
|
|
for (const auto& nameSpace : nameRegs) { |
|
|
|
|
|
|
|
assert(namespaceKeyUpdates.count(name) == 0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const auto& nameSpace : namespaceKeyUpdates) { |
|
|
|
|
|
|
|
assert(nameRegs.count(nameSpace) == 0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CKevaMemPool::validateNamespace(const CTransaction& tx, const valtype& nameSpace) const |
|
|
|
bool CKevaMemPool::validateNamespace(const CTransaction& tx, const valtype& nameSpace) const |
|
|
|
{ |
|
|
|
{ |
|
|
|
valtype kevaNamespace = ToByteVector(Hash160(ToByteVector(tx.vin[0].prevout.hash))); |
|
|
|
valtype kevaNamespace = ToByteVector(Hash160(ToByteVector(tx.vin[0].prevout.hash))); |
|
|
@ -227,17 +144,14 @@ bool CKevaMemPool::validateNamespace(const CTransaction& tx, const valtype& name |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool |
|
|
|
bool |
|
|
|
CKevaMemPool::checkTx (const CTransaction& tx) const |
|
|
|
CKevaMemPool::checkTx(const CTransaction& tx) const |
|
|
|
{ |
|
|
|
{ |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
AssertLockHeld (pool.cs); |
|
|
|
|
|
|
|
|
|
|
|
if (!tx.IsKevacoin ()) |
|
|
|
if (!tx.IsKevacoin ()) { |
|
|
|
return true; |
|
|
|
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) { |
|
|
|
for (const auto& txout : tx.vout) { |
|
|
|
const CKevaScript nameOp(txout.scriptPubKey); |
|
|
|
const CKevaScript nameOp(txout.scriptPubKey); |
|
|
|
if (!nameOp.isKevaOp ()) |
|
|
|
if (!nameOp.isKevaOp ()) |
|
|
@ -247,10 +161,6 @@ CKevaMemPool::checkTx (const CTransaction& tx) const |
|
|
|
{ |
|
|
|
{ |
|
|
|
const valtype& nameSpace = nameOp.getOpNamespace(); |
|
|
|
const valtype& nameSpace = nameOp.getOpNamespace(); |
|
|
|
std::map<valtype, uint256>::const_iterator mi; |
|
|
|
std::map<valtype, uint256>::const_iterator mi; |
|
|
|
mi = mapNamespaceRegs.find(nameSpace); |
|
|
|
|
|
|
|
if (mi != mapNamespaceRegs.end ()) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!validateNamespace(tx, nameSpace)) { |
|
|
|
if (!validateNamespace(tx, nameSpace)) { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
@ -259,11 +169,6 @@ CKevaMemPool::checkTx (const CTransaction& tx) const |
|
|
|
|
|
|
|
|
|
|
|
case OP_KEVA_PUT: |
|
|
|
case OP_KEVA_PUT: |
|
|
|
{ |
|
|
|
{ |
|
|
|
const valtype& nameSpace = nameOp.getOpNamespace(); |
|
|
|
|
|
|
|
const valtype& key = nameOp.getOpKey(); |
|
|
|
|
|
|
|
if (updatesNamespace(nameSpace)) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -320,33 +225,27 @@ CheckKevaTransaction (const CTransaction& tx, unsigned nHeight, |
|
|
|
{ |
|
|
|
{ |
|
|
|
const std::string strTxid = tx.GetHash ().GetHex (); |
|
|
|
const std::string strTxid = tx.GetHash ().GetHex (); |
|
|
|
const char* txid = strTxid.c_str (); |
|
|
|
const char* txid = strTxid.c_str (); |
|
|
|
const bool fMempool = (flags & SCRIPT_VERIFY_KEVA_MEMPOOL); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* Ignore historic bugs. */ |
|
|
|
|
|
|
|
CChainParams::BugType type; |
|
|
|
|
|
|
|
if (Params ().IsHistoricBug (tx.GetHash (), nHeight, type)) |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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 |
|
|
|
As a first step, try to locate inputs and outputs of the transaction |
|
|
|
a keva operation. */ |
|
|
|
that are keva scripts. At most one input and output should be |
|
|
|
|
|
|
|
a keva operation. |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
int nameIn = -1; |
|
|
|
int nameIn = -1; |
|
|
|
CKevaScript nameOpIn; |
|
|
|
CKevaScript nameOpIn; |
|
|
|
Coin coinIn; |
|
|
|
Coin coinIn; |
|
|
|
for (unsigned i = 0; i < tx.vin.size (); ++i) { |
|
|
|
for (unsigned i = 0; i < tx.vin.size(); ++i) { |
|
|
|
const COutPoint& prevout = tx.vin[i].prevout; |
|
|
|
const COutPoint& prevout = tx.vin[i].prevout; |
|
|
|
Coin coin; |
|
|
|
Coin coin; |
|
|
|
if (!view.GetCoin (prevout, coin)) { |
|
|
|
if (!view.GetCoin(prevout, coin)) { |
|
|
|
return error ("%s: failed to fetch input coin for %s", __func__, txid); |
|
|
|
return error("%s: failed to fetch input coin for %s", __func__, txid); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const CKevaScript op(coin.out.scriptPubKey); |
|
|
|
const CKevaScript op(coin.out.scriptPubKey); |
|
|
|
if (op.isKevaOp()) { |
|
|
|
if (op.isKevaOp()) { |
|
|
|
if (nameIn != -1) { |
|
|
|
if (nameIn != -1) { |
|
|
|
return state.Invalid (error ("%s: multiple name inputs into transaction %s", __func__, txid)); |
|
|
|
return state.Invalid(error("%s: multiple name inputs into transaction %s", __func__, txid)); |
|
|
|
} |
|
|
|
} |
|
|
|
nameIn = i; |
|
|
|
nameIn = i; |
|
|
|
nameOpIn = op; |
|
|
|
nameOpIn = op; |
|
|
@ -356,71 +255,43 @@ CheckKevaTransaction (const CTransaction& tx, unsigned nHeight, |
|
|
|
|
|
|
|
|
|
|
|
int nameOut = -1; |
|
|
|
int nameOut = -1; |
|
|
|
CKevaScript nameOpOut; |
|
|
|
CKevaScript nameOpOut; |
|
|
|
for (unsigned i = 0; i < tx.vout.size (); ++i) { |
|
|
|
for (unsigned i = 0; i < tx.vout.size(); ++i) { |
|
|
|
const CKevaScript op(tx.vout[i].scriptPubKey); |
|
|
|
const CKevaScript op(tx.vout[i].scriptPubKey); |
|
|
|
if (op.isKevaOp()) { |
|
|
|
if (op.isKevaOp()) { |
|
|
|
if (nameOut != -1) { |
|
|
|
if (nameOut != -1) { |
|
|
|
return state.Invalid (error ("%s: multiple name outputs from" |
|
|
|
return state.Invalid(error("%s: multiple name outputs from transaction %s", __func__, txid)); |
|
|
|
" transaction %s", __func__, txid)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
nameOut = i; |
|
|
|
nameOut = i; |
|
|
|
nameOpOut = op; |
|
|
|
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 |
|
|
|
Check that no keva inputs/outputs are present for a non-Kevacoin tx. |
|
|
|
should be at least an output (for NAME_NEW, no inputs are expected). */ |
|
|
|
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)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
assert(tx.IsKevacoin ()); |
|
|
|
assert(tx.IsKevacoin ()); |
|
|
|
if (nameOut == -1) |
|
|
|
if (nameOut == -1) { |
|
|
|
return state.Invalid (error ("%s: Kevacoin tx %s has no keva outputs", |
|
|
|
return state.Invalid (error ("%s: Kevacoin tx %s has no keva outputs", __func__, txid)); |
|
|
|
__func__, txid)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Reject "greedy names". */ |
|
|
|
/* Reject "greedy names". */ |
|
|
|
#if 0 |
|
|
|
|
|
|
|
const Consensus::Params& params = Params ().GetConsensus (); |
|
|
|
|
|
|
|
if (tx.vout[nameOut].nValue < params.rules->MinNameCoinAmount(nHeight)) { |
|
|
|
|
|
|
|
return state.Invalid (error ("%s: greedy name", __func__)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#else |
|
|
|
|
|
|
|
if (tx.vout[nameOut].nValue < KEVA_LOCKED_AMOUNT) { |
|
|
|
if (tx.vout[nameOut].nValue < KEVA_LOCKED_AMOUNT) { |
|
|
|
return state.Invalid (error ("%s: greedy name", __func__)); |
|
|
|
return state.Invalid (error ("%s: greedy name", __func__)); |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* 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 ("CheckKevaTransaction: NAME_NEW with" |
|
|
|
|
|
|
|
" previous name input")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nameOpOut.getOpHash ().size () != 20) |
|
|
|
|
|
|
|
return state.Invalid (error ("CheckKevaTransaction: 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. */ |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (nameOpOut.isNamespaceRegistration()) { |
|
|
|
if (nameOpOut.isNamespaceRegistration()) { |
|
|
|
if (nameOpOut.getOpNamespaceDisplayName().size () > MAX_VALUE_LENGTH) { |
|
|
|
if (nameOpOut.getOpNamespaceDisplayName().size () > MAX_VALUE_LENGTH) { |
|
|
@ -429,7 +300,8 @@ CheckKevaTransaction (const CTransaction& tx, unsigned nHeight, |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
assert (nameOpOut.isAnyUpdate()); |
|
|
|
assert(nameOpOut.isAnyUpdate()); |
|
|
|
|
|
|
|
|
|
|
|
if (nameIn == -1) { |
|
|
|
if (nameIn == -1) { |
|
|
|
return state.Invalid(error("CheckKevaTransaction: update without previous keva input")); |
|
|
|
return state.Invalid(error("CheckKevaTransaction: update without previous keva input")); |
|
|
|
} |
|
|
|
} |
|
|
@ -451,82 +323,9 @@ CheckKevaTransaction (const CTransaction& tx, unsigned nHeight, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (nameSpace != nameOpIn.getOpNamespace()) { |
|
|
|
if (nameSpace != nameOpIn.getOpNamespace()) { |
|
|
|
return state.Invalid (error ("%s: KEVA_PUT namespace mismatch to prev tx" |
|
|
|
return state.Invalid(error("%s: KEVA_PUT namespace mismatch to prev tx found in %s", __func__, txid)); |
|
|
|
" found in %s", __func__, txid)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CKevaData oldName; |
|
|
|
|
|
|
|
const valtype& namespaceIn = nameOpIn.getOpNamespace(); |
|
|
|
|
|
|
|
if (!view.GetNamespace(namespaceIn, oldName)) { |
|
|
|
|
|
|
|
return state.Invalid (error ("%s: Namespace not found", __func__)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
assert (tx.vin[nameIn].prevout == oldName.getUpdateOutpoint()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* 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. */ |
|
|
|
|
|
|
|
CKevaData oldName; |
|
|
|
|
|
|
|
const valtype& namespaceIn = nameOpIn.getOpNamespace(); |
|
|
|
|
|
|
|
const valtype& keyIn = nameOpIn.getOpKey(); |
|
|
|
|
|
|
|
if (!view.GetName(namespaceIn, keyIn, oldName)) { |
|
|
|
|
|
|
|
return state.Invalid (error ("%s: KEVA_PUT 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()); |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
/* Finally, NAME_FIRSTUPDATE. */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(nameOpOut.getNameOp () == OP_NAME_FIRSTUPDATE); |
|
|
|
|
|
|
|
if (nameOpIn.getNameOp () != OP_NAME_NEW) |
|
|
|
|
|
|
|
return state.Invalid (error ("CheckKevaTransaction: 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 ("CheckKevaTransaction: NAME_NEW" |
|
|
|
|
|
|
|
" is not mature for FIRST_UPDATE")); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (nameOpOut.getOpRand ().size () > 20) |
|
|
|
|
|
|
|
return state.Invalid (error ("CheckKevaTransaction: 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 ("CheckKevaTransaction: NAME_FIRSTUPDATE" |
|
|
|
|
|
|
|
" hash mismatch")); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
CNameData oldName; |
|
|
|
|
|
|
|
if (view.GetName (name, oldName) && !oldName.isExpired (nHeight)) |
|
|
|
|
|
|
|
return state.Invalid (error ("CheckKevaTransaction: 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. */ |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -578,128 +377,6 @@ void ApplyNameTransaction(const CTransaction& tx, unsigned nHeight, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#if 0 |
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void |
|
|
|
void |
|
|
|
CheckNameDB (bool disconnect) |
|
|
|
CheckNameDB (bool disconnect) |
|
|
|
{ |
|
|
|
{ |
|
|
|