diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 03a760084..2fe7f1a51 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -573,6 +573,7 @@ target_sources (rippled PRIVATE src/ripple/rpc/handlers/AccountLines.cpp src/ripple/rpc/handlers/AccountObjects.cpp src/ripple/rpc/handlers/AccountOffers.cpp + src/ripple/rpc/handlers/AccountNamespace.cpp src/ripple/rpc/handlers/AccountTx.cpp src/ripple/rpc/handlers/AccountTxOld.cpp src/ripple/rpc/handlers/BlackList.cpp diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index a8d72eda2..a55422a4c 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -785,6 +785,13 @@ private: return parseAccountRaw2(jvParams, jss::peer); } + // account_namespace [] + Json::Value + parseAccountNamespace(Json::Value const& jvParams) + { + return parseAccountNamespaceRaw(jvParams); + } + // account_channels |"" [] Json::Value parseAccountChannels(Json::Value const& jvParams) @@ -865,6 +872,61 @@ private: return jvRequest; } + Json::Value + parseAccountNamespaceRaw(Json::Value const& jvParams) + { + auto const nParams = jvParams.size(); + Json::Value jvRequest(Json::objectValue); + + for (auto i = 0; i < nParams; ++i) + { + std::string strParam = jvParams[i].asString(); + + if (i == 0) + { + // account + if (parseBase58( + TokenType::AccountPublic, strParam) || + parseBase58(strParam) || + parseGenericSeed(strParam)) + { + jvRequest[jss::account] = std::move(strParam); + } + else + { + return rpcError(rpcACT_MALFORMED); + } + continue; + } + + if (i == 1) + { + // namespace hex + uint256 namespaceId; + if (!namespaceId.parseHex(strParam)) + return rpcError(rpcNAMESPACE_MALFORMED); + jvRequest[jss::namespace_id] = to_string(namespaceId); + continue; + } + + if (i == 2) + { + // ledger index (optional) + if (strParam.empty()) + break; + + if (jvParseLedger(jvRequest, strParam)) + break; + else + return rpcError(rpcLGR_IDX_MALFORMED); + + continue; + } + } + + return jvRequest; + } + Json::Value parseAccountRaw2(Json::Value const& jvParams, char const* const acc2Field) { @@ -1237,6 +1299,7 @@ public: {"account_currencies", &RPCParser::parseAccountCurrencies, 1, 3}, {"account_info", &RPCParser::parseAccountItems, 1, 3}, {"account_lines", &RPCParser::parseAccountLines, 1, 5}, + {"account_namespace", &RPCParser::parseAccountNamespace, 2, 3}, {"account_channels", &RPCParser::parseAccountChannels, 1, 3}, {"account_objects", &RPCParser::parseAccountItems, 1, 5}, {"account_offers", &RPCParser::parseAccountItems, 1, 4}, diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index 45fa7da29..bbdcdfd3b 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -68,7 +68,8 @@ enum error_code_i { // Ledger state rpcACT_NOT_FOUND = 19, - // unused 20, + rpcNAMESPACE_NOT_FOUND = 20, + rpcLGR_NOT_FOUND = 21, rpcLGR_NOT_VALIDATED = 22, rpcMASTER_DISABLED = 23, @@ -90,7 +91,7 @@ enum error_code_i { rpcACT_MALFORMED = 35, rpcALREADY_MULTISIG = 36, rpcALREADY_SINGLE_SIG = 37, - // unused 38, + rpcNAMESPACE_MALFORMED = 38, // unused 39, rpcBAD_FEATURE = 40, rpcBAD_ISSUER = 41, diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 87fb2da2a..b278b6604 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -34,6 +34,7 @@ namespace detail { constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcACT_MALFORMED, "actMalformed", "Account malformed."}, {rpcACT_NOT_FOUND, "actNotFound", "Account not found."}, + {rpcNAMESPACE_NOT_FOUND, "nsNotFound", "Namespace not found."}, {rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."}, {rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."}, {rpcAMENDMENT_BLOCKED, @@ -87,6 +88,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."}, {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."}, {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."}, + {rpcNAMESPACE_MALFORMED, "namespaceMalformed", "Namespace identifier is malformed."}, {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."}, {rpcNOT_IMPL, "notImpl", "Not implemented."}, {rpcNOT_READY, "notReady", "Not ready to handle this request."}, diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 12217688c..74571fb96 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -392,6 +392,8 @@ JSS(minimum_fee); // out: TxQ JSS(minimum_level); // out: TxQ JSS(missingCommand); // error JSS(name); // out: AmendmentTableImpl, PeerImp +JSS(namespace_entries); // out: AccountNamespace +JSS(namespace_id); // in/out: AccountNamespace JSS(needed_state_hashes); // out: InboundLedger JSS(needed_transaction_hashes); // out: InboundLedger JSS(network_id); // out: NetworkOPs diff --git a/src/ripple/rpc/handlers/AccountNamespace.cpp b/src/ripple/rpc/handlers/AccountNamespace.cpp new file mode 100644 index 000000000..e661d9e32 --- /dev/null +++ b/src/ripple/rpc/handlers/AccountNamespace.cpp @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { + +/** RPC command that retreives hook state objects from a particular namespace in a particular account. + { + account: | + namespace_id: + ledger_hash: // optional + ledger_index: // optional + type: // optional, defaults to all account objects types + limit: // optional + marker: // optional, resume previous query + } +*/ + +Json::Value +doAccountNamespace(RPC::JsonContext& context) +{ + auto const& params = context.params; + if (!params.isMember(jss::account)) + return RPC::missing_field_error(jss::account); + + if (!params.isMember(jss::namespace_id)) + return RPC::missing_field_error(jss::namespace_id); + + std::shared_ptr ledger; + auto result = RPC::lookupLedger(ledger, context); + if (ledger == nullptr) + return result; + + AccountID accountID; + { + auto const strIdent = params[jss::account].asString(); + if (auto jv = RPC::accountFromString(accountID, strIdent)) + { + for (auto it = jv.begin(); it != jv.end(); ++it) + result[it.memberName()] = *it; + + return result; + } + } + + auto const ns = params[jss::namespace_id].asString(); + + uint256 nsID = beast::zero; + + if (!nsID.parseHex(ns)) + return rpcError(rpcINVALID_PARAMS); + + if (!ledger->exists(keylet::account(accountID))) + return rpcError(rpcACT_NOT_FOUND); + + if (!ledger->exists(keylet::hookStateDir(accountID, nsID))) + return rpcError(rpcNAMESPACE_NOT_FOUND); + + unsigned int limit; + if (auto err = readLimitField(limit, RPC::Tuning::accountObjects, context)) + return *err; + + uint256 dirIndex; + uint256 entryIndex; + if (params.isMember(jss::marker)) + { + auto const& marker = params[jss::marker]; + if (!marker.isString()) + return RPC::expected_field_error(jss::marker, "string"); + + std::stringstream ss(marker.asString()); + std::string s; + if (!std::getline(ss, s, ',')) + return RPC::invalid_field_error(jss::marker); + + if (!dirIndex.parseHex(s)) + return RPC::invalid_field_error(jss::marker); + + if (!std::getline(ss, s, ',')) + return RPC::invalid_field_error(jss::marker); + + if (!entryIndex.parseHex(s)) + return RPC::invalid_field_error(jss::marker); + } + + if (!RPC::getAccountNamespace( + *ledger, + accountID, + nsID, + dirIndex, + entryIndex, + limit, + result)) + { + result[jss::account_objects] = Json::arrayValue; + } + + result[jss::account] = context.app.accountIDCache().toBase58(accountID); + result[jss::namespace_id] = ns; + context.loadType = Resource::feeMediumBurdenRPC; + return result; +} + +} // namespace ripple diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 264d0a3f1..687671cdc 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -37,6 +37,8 @@ doAccountObjects(RPC::JsonContext&); Json::Value doAccountOffers(RPC::JsonContext&); Json::Value +doAccountNamespace(RPC::JsonContext&); +Json::Value doAccountTxJson(RPC::JsonContext&); Json::Value doBookOffers(RPC::JsonContext&); diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 7c193f554..5b02a280c 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -66,6 +66,7 @@ Handler const handlerArray[]{ Role::USER, NO_CONDITION}, {"account_lines", byRef(&doAccountLines), Role::USER, NO_CONDITION}, + {"account_namespace", byRef(&doAccountNamespace), Role::USER, NO_CONDITION}, {"account_channels", byRef(&doAccountChannels), Role::USER, NO_CONDITION}, {"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION}, {"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION}, diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 5c42aae96..b3e0813dd 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -226,6 +226,89 @@ getAccountObjects( } } +bool +getAccountNamespace( + ReadView const& ledger, + AccountID const& account, + uint256 const& ns, + uint256 dirIndex, + uint256 const& entryIndex, + std::uint32_t const limit, + Json::Value& jvResult) +{ + auto const root = keylet::hookStateDir(account, ns); + auto found = false; + + if (dirIndex.isZero()) + { + dirIndex = root.key; + found = true; + } + + auto dir = ledger.read({ltDIR_NODE, dirIndex}); + if (!dir) + return false; + + std::uint32_t i = 0; + auto& jvObjects = (jvResult[jss::namespace_entries] = Json::arrayValue); + for (;;) + { + auto const& entries = dir->getFieldV256(sfIndexes); + auto iter = entries.begin(); + + if (!found) + { + iter = std::find(iter, entries.end(), entryIndex); + if (iter == entries.end()) + return false; + + found = true; + } + + for (; iter != entries.end(); ++iter) + { + auto const sleNode = ledger.read(keylet::child(*iter)); + + jvObjects.append(sleNode->getJson(JsonOptions::none)); + + if (++i == limit) + { + if (++iter != entries.end()) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = + to_string(dirIndex) + ',' + to_string(*iter); + return true; + } + + break; + } + } + + auto const nodeIndex = dir->getFieldU64(sfIndexNext); + if (nodeIndex == 0) + return true; + + dirIndex = keylet::page(root, nodeIndex).key; + dir = ledger.read({ltDIR_NODE, dirIndex}); + if (!dir) + return true; + + if (i == limit) + { + auto const& e = dir->getFieldV256(sfIndexes); + if (!e.empty()) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = + to_string(dirIndex) + ',' + to_string(*e.begin()); + } + + return true; + } + } +} + namespace { bool diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index e3d44c2e7..0e35dba2a 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -109,6 +109,25 @@ getAccountObjects( std::uint32_t const limit, Json::Value& jvResult); +/** Gathers all hook state objects for an account namespace in a ledger. + @param ledger Ledger to search account objects. + @param account AccountID to find objects for. + @param ns Namespace ID to find objects for. + @param dirIndex Begin gathering account objects from this directory. + @param entryIndex Begin gathering objects from this directory node. + @param limit Maximum number of objects to find. + @param jvResult A JSON result that holds the request objects. +*/ +bool +getAccountNamespace( + ReadView const& ledger, + AccountID const& account, + uint256 const& ns, + uint256 dirIndex, + uint256 const& entryIndex, + std::uint32_t const limit, + Json::Value& jvResult); + /** Get ledger by hash If there is no error in the return value, the ledger pointer will have been filled