From 661040d3c27a296483209dee65234f8daf9a9e5f Mon Sep 17 00:00:00 2001 From: Jianping Wu Date: Tue, 11 Dec 2018 21:32:19 -0800 Subject: [PATCH] WIP: implemented keva_delete. Problem: after the call, the corresponding keva coin is gone! --- src/keva/common.cpp | 10 +-- src/keva/main.cpp | 46 ++++++++++---- src/script/interpreter.cpp | 1 + src/script/keva.cpp | 15 +++++ src/script/keva.h | 39 +++++++++++- src/script/script.h | 2 +- src/txmempool.h | 5 ++ src/wallet/rpckeva.cpp | 126 +++++++++++++++++++++++++++++++++---- src/wallet/rpcwallet.cpp | 2 + 9 files changed, 217 insertions(+), 29 deletions(-) diff --git a/src/keva/common.cpp b/src/keva/common.cpp index 3992c27c9..ad2ab24e2 100644 --- a/src/keva/common.cpp +++ b/src/keva/common.cpp @@ -22,7 +22,9 @@ CKevaData::fromScript (unsigned h, const COutPoint& out, const CKevaScript& script) { if (script.isAnyUpdate()) { - value = script.getOpValue(); + if (!script.isDelete()) { + value = script.getOpValue(); + } } else if (script.isNamespaceRegistration()) { value = script.getOpNamespaceDisplayName(); } else { @@ -212,14 +214,14 @@ CKevaCache::set(const valtype& nameSpace, const valtype& key, const CKevaData& d auto name = std::make_tuple(nameSpace, key); const std::set::iterator di = deleted.find(name); if (di != deleted.end()) { - deleted.erase (di); + deleted.erase(di); } const EntryMap::iterator ei = entries.find(name); - if (ei != entries.end ()) + if (ei != entries.end()) ei->second = data; else - entries.insert (std::make_pair(name, data)); + entries.insert(std::make_pair(name, data)); } void diff --git a/src/keva/main.cpp b/src/keva/main.cpp index a5ecc7bf7..ae44e35ff 100644 --- a/src/keva/main.cpp +++ b/src/keva/main.cpp @@ -58,6 +58,12 @@ CKevaMemPool::addUnchecked (const uint256& hash, const CTxMemPoolEntry& entry) const valtype& nameSpace = entry.getNamespace(); listUnconfirmedKeyValues.push_back(std::make_tuple(hash, nameSpace, entry.getKey(), entry.getValue())); } + + if (entry.isKeyDelete()) { + const valtype& nameSpace = entry.getNamespace(); + const valtype& empty = ValtypeFromString(""); + listUnconfirmedKeyValues.push_back(std::make_tuple(hash, nameSpace, entry.getKey(), empty)); + } } bool @@ -113,7 +119,7 @@ void CKevaMemPool::remove(const CTxMemPoolEntry& entry) } } - if (entry.isKeyUpdate()) { + if (entry.isKeyUpdate() || entry.isKeyDelete()) { auto hash = entry.GetTx().GetHash(); for (auto iter = listUnconfirmedKeyValues.begin(); iter != listUnconfirmedKeyValues.end(); ++iter) { if (std::get<0>(*iter) == hash) { @@ -190,6 +196,11 @@ CKevaMemPool::checkTx(const CTransaction& tx) const break; } + case OP_KEVA_DELETE: + { + break; + } + default: assert (false); } @@ -329,19 +340,29 @@ CheckKevaTransaction (const CTransaction& tx, unsigned nHeight, return state.Invalid (error ("CheckKevaTransaction: key too long")); } - if (nameOpOut.getOpValue().size () > MAX_VALUE_LENGTH) { - return state.Invalid (error ("CheckKevaTransaction: value too long")); + const valtype& nameSpace = nameOpOut.getOpNamespace(); + if (nameSpace != nameOpIn.getOpNamespace()) { + return state.Invalid(error("%s: KEVA_PUT namespace mismatch to prev tx found in %s", __func__, txid)); } - /* Process KEVA_PUT next. */ - const valtype& nameSpace = nameOpOut.getOpNamespace(); if (nameOpOut.getKevaOp() == OP_KEVA_PUT) { + if (nameOpOut.getOpValue().size () > MAX_VALUE_LENGTH) { + return state.Invalid (error ("CheckKevaTransaction: value too long")); + } + 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)); + if (nameOpOut.getKevaOp() == OP_KEVA_DELETE) { + if (!nameOpIn.isAnyUpdate() && !nameOpIn.isNamespaceRegistration()) { + return state.Invalid(error("CheckKevaTransaction: KEVA_DELETE with prev input that is no update")); + } + CKevaData data; + const bool hasKey = view.GetName(nameSpace, nameOpOut.getOpKey(), data); + if (!hasKey) { + return state.Invalid(error("CheckKevaTransaction: no key to delete")); } } return true; @@ -351,7 +372,7 @@ void ApplyKevaTransaction(const CTransaction& tx, unsigned nHeight, CCoinsViewCache& view, CBlockUndo& undo) { assert (nHeight != MEMPOOL_HEIGHT); - if (!tx.IsKevacoin ()) + if (!tx.IsKevacoin()) return; /* Changes are encoded in the outputs. We don't have to do any checks, @@ -362,7 +383,6 @@ void ApplyKevaTransaction(const CTransaction& tx, unsigned nHeight, if (!op.isKevaOp()) { continue; } - if (op.isNamespaceRegistration()) { const valtype& nameSpace = op.getOpNamespace(); const valtype& displayName = op.getOpNamespaceDisplayName(); @@ -389,8 +409,12 @@ void ApplyKevaTransaction(const CTransaction& tx, unsigned nHeight, undo.vkevaundo.push_back(opUndo); CKevaData data; - data.fromScript(nHeight, COutPoint(tx.GetHash(), i), op); - view.SetName(nameSpace, key, data, false); + if (op.isDelete()) { + view.DeleteName(nameSpace, key); + } else { + data.fromScript(nHeight, COutPoint(tx.GetHash(), i), op); + view.SetName(nameSpace, key, data, false); + } } } } diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 791124b6c..61ecdd1a2 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -359,6 +359,7 @@ bool EvalScript(std::vector >& stack, const CScript& // case OP_KEVA_NAMESPACE: case OP_KEVA_PUT: + case OP_KEVA_DELETE: break; case OP_CHECKLOCKTIMEVERIFY: diff --git a/src/script/keva.cpp b/src/script/keva.cpp index edfa6b004..7500cc8bf 100644 --- a/src/script/keva.cpp +++ b/src/script/keva.cpp @@ -55,6 +55,12 @@ CKevaScript::CKevaScript (const CScript& script) } break; + case OP_KEVA_DELETE: + if (args.size() != 2) { + return; + } + break; + case OP_KEVA_NAMESPACE: if (args.size() != 2) { return; @@ -79,6 +85,15 @@ CKevaScript::buildKevaPut(const CScript& addr, const valtype& nameSpace, return prefix + addr; } +CScript +CKevaScript::buildKevaDelete(const CScript& addr, const valtype& nameSpace, const valtype& key) +{ + CScript prefix; + prefix << OP_KEVA_DELETE << nameSpace << key << OP_2DROP; + + return prefix + addr; +} + CScript CKevaScript::buildKevaNamespace(const CScript& addr, const valtype& nameSpace, const valtype& displayName) { diff --git a/src/script/keva.h b/src/script/keva.h index ee7842051..901d7b103 100644 --- a/src/script/keva.h +++ b/src/script/keva.h @@ -66,6 +66,9 @@ public: case OP_KEVA_NAMESPACE: return true; + case OP_KEVA_DELETE: + return true; + case OP_NOP: return false; @@ -94,6 +97,9 @@ public: case OP_KEVA_PUT: return op; + case OP_KEVA_DELETE: + return op; + case OP_KEVA_NAMESPACE: return op; @@ -115,6 +121,9 @@ public: case OP_KEVA_PUT: return false; + case OP_KEVA_DELETE: + return false; + default: assert(false); } @@ -133,6 +142,9 @@ public: case OP_KEVA_PUT: return true; + case OP_KEVA_DELETE: + return true; + default: assert(false); } @@ -149,6 +161,9 @@ public: case OP_KEVA_PUT: return args[0]; + case OP_KEVA_DELETE: + return args[0]; + case OP_KEVA_NAMESPACE: return args[0]; @@ -184,6 +199,9 @@ public: case OP_KEVA_PUT: return args[1]; + case OP_KEVA_DELETE: + return args[1]; + default: assert(false); } @@ -206,6 +224,16 @@ public: } } + /** + * Return the keva operation value. This call is only valid for + * OP_KEVA_PUT. + * @return The keva operation's value. + */ + inline bool isDelete() const + { + return (op == OP_KEVA_DELETE); + } + /** * Check if the given script is a keva script. This is a utility method. * @param script The script to parse. @@ -215,7 +243,7 @@ public: isKevaScript (const CScript& script) { const CKevaScript op(script); - return op.isKevaOp (); + return op.isKevaOp(); } /** @@ -237,6 +265,15 @@ public: const valtype& key, const valtype& value); + /** + * Build a KEVA_DELETE transaction. + * @param addr The address script to append. + * @param hash The hash to use. + * @return The full KEVA_DELETE script. + */ + static CScript buildKevaDelete(const CScript& addr, const valtype& nameSpace, const valtype& key); + + static CScript replaceKevaNamespace(const CScript& oldScript, const uint256& txId, valtype& kaveNamespace, const CChainParams& params); }; diff --git a/src/script/script.h b/src/script/script.h index e2672b652..4011ba59b 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -186,7 +186,7 @@ enum opcodetype // Keva OP_KEVA_PUT=0xd0, OP_KEVA_NAMESPACE=0xd1, - + OP_KEVA_DELETE=0xd2, // template matching params OP_SMALLINTEGER = 0xfa, diff --git a/src/txmempool.h b/src/txmempool.h index 3813a22cd..5642a82bf 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -142,6 +142,11 @@ public: return kevaOp.isKevaOp() && kevaOp.getKevaOp() == OP_KEVA_PUT; } + inline bool isKeyDelete() const + { + return kevaOp.isKevaOp() && kevaOp.getKevaOp() == OP_KEVA_DELETE; + } + inline const valtype& getNamespace() const { return kevaOp.getOpNamespace(); diff --git a/src/wallet/rpckeva.cpp b/src/wallet/rpckeva.cpp index 7510e6ced..cee089858 100644 --- a/src/wallet/rpckeva.cpp +++ b/src/wallet/rpckeva.cpp @@ -42,8 +42,8 @@ UniValue keva_namespace(const JSONRPCRequest& request) "1. \"display_name\" (string, required) the display name of the namespace\n" "\nResult:\n" "[\n" - " xxxxx, (string) the txid, required for keva_put\n" - " xxxxx, (string) the unique namespace id, required for keva_put\n" + " xxxxx, (string) the txid\n" + " xxxxx, (string) the unique namespace id\n" "]\n" "\nExamples:\n" + HelpExampleCli ("keva_namespace", "\"display name\"") @@ -96,8 +96,11 @@ UniValue keva_namespace(const JSONRPCRequest& request) kevaNamespaceBase58.c_str(), displayNameStr.c_str(), txid.c_str()); UniValue res(UniValue::VARR); - res.push_back(txid); - res.push_back(kevaNamespaceBase58); + UniValue obj(UniValue::VOBJ); + obj.pushKV("txid", txid); + obj.pushKV("namespaceId", kevaNamespaceBase58); + res.push_back(obj); + return res; } @@ -207,8 +210,8 @@ UniValue keva_put(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 3) { throw std::runtime_error ( - "keva_put \"namespace\" \"key\" \"value\" (\"create_namespace\")\n" - "\nUpdate a name and possibly transfer it.\n" + "keva_put \"namespace\" \"key\" \"value\"\n" + "\nInsert or update a key value pair in the given namespace.\n" + HelpRequiringPassphrase (pwallet) + "\nArguments:\n" "1. \"namespace\" (string, required) the namespace to insert the key to\n" @@ -251,7 +254,7 @@ UniValue keva_put(const JSONRPCRequest& request) COutput output; std::string kevaNamespce = namespaceStr; if (!pwallet->FindKevaCoin(output, kevaNamespce)) { - throw JSONRPCError (RPC_TRANSACTION_ERROR, "this name can not be updated"); + throw JSONRPCError (RPC_TRANSACTION_ERROR, "this namespace can not be updated"); } const COutPoint outp(output.tx->GetHash(), output.i); const CTxIn txIn(outp); @@ -281,6 +284,96 @@ UniValue keva_put(const JSONRPCRequest& request) return wtx.GetHash().GetHex(); } +UniValue keva_delete(const JSONRPCRequest& request) +{ + CWallet* const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable (pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) { + throw std::runtime_error ( + "keva_delete \"namespace\" \"key\"\n" + "\nRemove the specified key from the given namespace.\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" + "\nResult:\n" + "\"txid\" (string) the keva_delete's txid\n" + "\nExamples:\n" + + HelpExampleCli ("keva_delete", "\"mynamespace\", \"key\"") + ); + } + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}); + RPCTypeCheckArgument(request.params[0], UniValue::VSTR); + RPCTypeCheckArgument(request.params[1], UniValue::VSTR); + + ObserveSafeMode (); + + const std::string namespaceStr = request.params[0].get_str(); + valtype nameSpace; + if (!DecodeKevaNamespace(namespaceStr, Params(), nameSpace)) { + throw JSONRPCError (RPC_INVALID_PARAMETER, "invalid namespace id"); + } + if (nameSpace.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"); + } + + CKevaData data; + { + LOCK2(cs_main, mempool.cs); + if (!pcoinsTip->GetName(nameSpace, key, data)) { + std::vector> unconfirmedKeyValueList; + valtype val; + if (!mempool.getUnconfirmedKeyValue(nameSpace, key, val) || val.size() == 0) { + throw JSONRPCError (RPC_TRANSACTION_ERROR, "key not found"); + } + } + } + + EnsureWalletIsUnlocked(pwallet); + + COutput output; + std::string kevaNamespce = namespaceStr; + if (!pwallet->FindKevaCoin(output, kevaNamespce)) { + throw JSONRPCError (RPC_TRANSACTION_ERROR, "this namespace can not be updated"); + } + const COutPoint outp(output.tx->GetHash(), output.i); + const CTxIn txIn(outp); + + CReserveKey keyName(pwallet); + CPubKey pubKeyReserve; + const bool ok = keyName.GetReservedKey(pubKeyReserve, true); + assert(ok); + bool usedKey = false; + + CScript addrName; + usedKey = true; + addrName = GetScriptForDestination(pubKeyReserve.GetID()); + + const CScript kevaScript = CKevaScript::buildKevaDelete(addrName, nameSpace, key); + + CCoinControl coinControl; + CWalletTx wtx; + valtype empty; + SendMoneyToScript(pwallet, kevaScript, &txIn, empty, + KEVA_LOCKED_AMOUNT, false, wtx, coinControl); + + if (usedKey) { + keyName.KeepKey(); + } + + return wtx.GetHash().GetHex(); +} + UniValue keva_get(const JSONRPCRequest& request) { CWallet* const pwallet = GetWalletForJSONRPCRequest(request); @@ -392,6 +485,7 @@ UniValue keva_pending(const JSONRPCRequest& request) UniValue arr(UniValue::VARR); const std::string opKevaNamepsace = "keva_namespace"; const std::string opKevaPut = "keva_put"; + const std::string opKevaDelete = "keva_delete"; for (auto entry: unconfirmedNamespaces) { UniValue obj(UniValue::VOBJ); @@ -404,11 +498,19 @@ UniValue keva_pending(const JSONRPCRequest& request) for (auto entry: unconfirmedKeyValueList) { UniValue obj(UniValue::VOBJ); - obj.pushKV("op", opKevaPut); - obj.pushKV("namespace", EncodeBase58Check(std::get<0>(entry))); - obj.pushKV("key", ValtypeToString(std::get<1>(entry))); - obj.pushKV("value", ValtypeToString(std::get<2>(entry))); - obj.pushKV("txid", std::get<3>(entry).ToString()); + const valtype val = std::get<2>(entry); + if (val.size() > 0) { + obj.pushKV("op", opKevaPut); + obj.pushKV("namespace", EncodeBase58Check(std::get<0>(entry))); + obj.pushKV("key", ValtypeToString(std::get<1>(entry))); + obj.pushKV("value", ValtypeToString(std::get<2>(entry))); + obj.pushKV("txid", std::get<3>(entry).ToString()); + } else { + obj.pushKV("op", opKevaDelete); + obj.pushKV("namespace", EncodeBase58Check(std::get<0>(entry))); + obj.pushKV("key", ValtypeToString(std::get<1>(entry))); + obj.pushKV("txid", std::get<3>(entry).ToString()); + } arr.push_back(obj); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 07b1893e1..bf0f5b239 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3593,6 +3593,7 @@ extern UniValue rescanblockchain(const JSONRPCRequest& request); // in rpckeva.cpp extern UniValue keva_namespace(const JSONRPCRequest& request); extern UniValue keva_put(const JSONRPCRequest& request); +extern UniValue keva_delete(const JSONRPCRequest& request); extern UniValue keva_get(const JSONRPCRequest& request); extern UniValue keva_list_namespaces(const JSONRPCRequest& request); extern UniValue keva_pending(const JSONRPCRequest& request); @@ -3658,6 +3659,7 @@ static const CRPCCommand commands[] = { "kevacoin", "keva_namespace", &keva_namespace, {"display_name"} }, { "kevacoin", "keva_list_namespaces", &keva_list_namespaces, {} }, { "kevacoin", "keva_put", &keva_put, {"namespace", "key", "value"} }, + { "kevacoin", "keva_delete", &keva_delete, {"namespace", "key"} }, { "kevacoin", "keva_get", &keva_get, {"namespace", "key"} }, { "kevacoin", "keva_pending", &keva_pending, {"namespace"} } };