mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 18:15:50 +00:00
add account_namespace rpc call for iterating hook state objects
This commit is contained in:
@@ -573,6 +573,7 @@ target_sources (rippled PRIVATE
|
|||||||
src/ripple/rpc/handlers/AccountLines.cpp
|
src/ripple/rpc/handlers/AccountLines.cpp
|
||||||
src/ripple/rpc/handlers/AccountObjects.cpp
|
src/ripple/rpc/handlers/AccountObjects.cpp
|
||||||
src/ripple/rpc/handlers/AccountOffers.cpp
|
src/ripple/rpc/handlers/AccountOffers.cpp
|
||||||
|
src/ripple/rpc/handlers/AccountNamespace.cpp
|
||||||
src/ripple/rpc/handlers/AccountTx.cpp
|
src/ripple/rpc/handlers/AccountTx.cpp
|
||||||
src/ripple/rpc/handlers/AccountTxOld.cpp
|
src/ripple/rpc/handlers/AccountTxOld.cpp
|
||||||
src/ripple/rpc/handlers/BlackList.cpp
|
src/ripple/rpc/handlers/BlackList.cpp
|
||||||
|
|||||||
@@ -785,6 +785,13 @@ private:
|
|||||||
return parseAccountRaw2(jvParams, jss::peer);
|
return parseAccountRaw2(jvParams, jss::peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// account_namespace <account> <namespace hex> [<ledger>]
|
||||||
|
Json::Value
|
||||||
|
parseAccountNamespace(Json::Value const& jvParams)
|
||||||
|
{
|
||||||
|
return parseAccountNamespaceRaw(jvParams);
|
||||||
|
}
|
||||||
|
|
||||||
// account_channels <account> <account>|"" [<ledger>]
|
// account_channels <account> <account>|"" [<ledger>]
|
||||||
Json::Value
|
Json::Value
|
||||||
parseAccountChannels(Json::Value const& jvParams)
|
parseAccountChannels(Json::Value const& jvParams)
|
||||||
@@ -865,6 +872,61 @@ private:
|
|||||||
return jvRequest;
|
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<PublicKey>(
|
||||||
|
TokenType::AccountPublic, strParam) ||
|
||||||
|
parseBase58<AccountID>(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
|
Json::Value
|
||||||
parseAccountRaw2(Json::Value const& jvParams, char const* const acc2Field)
|
parseAccountRaw2(Json::Value const& jvParams, char const* const acc2Field)
|
||||||
{
|
{
|
||||||
@@ -1237,6 +1299,7 @@ public:
|
|||||||
{"account_currencies", &RPCParser::parseAccountCurrencies, 1, 3},
|
{"account_currencies", &RPCParser::parseAccountCurrencies, 1, 3},
|
||||||
{"account_info", &RPCParser::parseAccountItems, 1, 3},
|
{"account_info", &RPCParser::parseAccountItems, 1, 3},
|
||||||
{"account_lines", &RPCParser::parseAccountLines, 1, 5},
|
{"account_lines", &RPCParser::parseAccountLines, 1, 5},
|
||||||
|
{"account_namespace", &RPCParser::parseAccountNamespace, 2, 3},
|
||||||
{"account_channels", &RPCParser::parseAccountChannels, 1, 3},
|
{"account_channels", &RPCParser::parseAccountChannels, 1, 3},
|
||||||
{"account_objects", &RPCParser::parseAccountItems, 1, 5},
|
{"account_objects", &RPCParser::parseAccountItems, 1, 5},
|
||||||
{"account_offers", &RPCParser::parseAccountItems, 1, 4},
|
{"account_offers", &RPCParser::parseAccountItems, 1, 4},
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ enum error_code_i {
|
|||||||
|
|
||||||
// Ledger state
|
// Ledger state
|
||||||
rpcACT_NOT_FOUND = 19,
|
rpcACT_NOT_FOUND = 19,
|
||||||
// unused 20,
|
rpcNAMESPACE_NOT_FOUND = 20,
|
||||||
|
|
||||||
rpcLGR_NOT_FOUND = 21,
|
rpcLGR_NOT_FOUND = 21,
|
||||||
rpcLGR_NOT_VALIDATED = 22,
|
rpcLGR_NOT_VALIDATED = 22,
|
||||||
rpcMASTER_DISABLED = 23,
|
rpcMASTER_DISABLED = 23,
|
||||||
@@ -90,7 +91,7 @@ enum error_code_i {
|
|||||||
rpcACT_MALFORMED = 35,
|
rpcACT_MALFORMED = 35,
|
||||||
rpcALREADY_MULTISIG = 36,
|
rpcALREADY_MULTISIG = 36,
|
||||||
rpcALREADY_SINGLE_SIG = 37,
|
rpcALREADY_SINGLE_SIG = 37,
|
||||||
// unused 38,
|
rpcNAMESPACE_MALFORMED = 38,
|
||||||
// unused 39,
|
// unused 39,
|
||||||
rpcBAD_FEATURE = 40,
|
rpcBAD_FEATURE = 40,
|
||||||
rpcBAD_ISSUER = 41,
|
rpcBAD_ISSUER = 41,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace detail {
|
|||||||
constexpr static ErrorInfo unorderedErrorInfos[]{
|
constexpr static ErrorInfo unorderedErrorInfos[]{
|
||||||
{rpcACT_MALFORMED, "actMalformed", "Account malformed."},
|
{rpcACT_MALFORMED, "actMalformed", "Account malformed."},
|
||||||
{rpcACT_NOT_FOUND, "actNotFound", "Account not found."},
|
{rpcACT_NOT_FOUND, "actNotFound", "Account not found."},
|
||||||
|
{rpcNAMESPACE_NOT_FOUND, "nsNotFound", "Namespace not found."},
|
||||||
{rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."},
|
{rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."},
|
||||||
{rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."},
|
{rpcALREADY_SINGLE_SIG, "alreadySingleSig", "Already single-signed."},
|
||||||
{rpcAMENDMENT_BLOCKED,
|
{rpcAMENDMENT_BLOCKED,
|
||||||
@@ -87,6 +88,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
|
|||||||
{rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."},
|
{rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found."},
|
||||||
{rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."},
|
{rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated."},
|
||||||
{rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."},
|
{rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled."},
|
||||||
|
{rpcNAMESPACE_MALFORMED, "namespaceMalformed", "Namespace identifier is malformed."},
|
||||||
{rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."},
|
{rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration."},
|
||||||
{rpcNOT_IMPL, "notImpl", "Not implemented."},
|
{rpcNOT_IMPL, "notImpl", "Not implemented."},
|
||||||
{rpcNOT_READY, "notReady", "Not ready to handle this request."},
|
{rpcNOT_READY, "notReady", "Not ready to handle this request."},
|
||||||
|
|||||||
@@ -392,6 +392,8 @@ JSS(minimum_fee); // out: TxQ
|
|||||||
JSS(minimum_level); // out: TxQ
|
JSS(minimum_level); // out: TxQ
|
||||||
JSS(missingCommand); // error
|
JSS(missingCommand); // error
|
||||||
JSS(name); // out: AmendmentTableImpl, PeerImp
|
JSS(name); // out: AmendmentTableImpl, PeerImp
|
||||||
|
JSS(namespace_entries); // out: AccountNamespace
|
||||||
|
JSS(namespace_id); // in/out: AccountNamespace
|
||||||
JSS(needed_state_hashes); // out: InboundLedger
|
JSS(needed_state_hashes); // out: InboundLedger
|
||||||
JSS(needed_transaction_hashes); // out: InboundLedger
|
JSS(needed_transaction_hashes); // out: InboundLedger
|
||||||
JSS(network_id); // out: NetworkOPs
|
JSS(network_id); // out: NetworkOPs
|
||||||
|
|||||||
135
src/ripple/rpc/handlers/AccountNamespace.cpp
Normal file
135
src/ripple/rpc/handlers/AccountNamespace.cpp
Normal file
@@ -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 <ripple/app/main/Application.h>
|
||||||
|
#include <ripple/json/json_writer.h>
|
||||||
|
#include <ripple/ledger/ReadView.h>
|
||||||
|
#include <ripple/net/RPCErr.h>
|
||||||
|
#include <ripple/protocol/ErrorCodes.h>
|
||||||
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
#include <ripple/protocol/LedgerFormats.h>
|
||||||
|
#include <ripple/protocol/jss.h>
|
||||||
|
#include <ripple/resource/Fees.h>
|
||||||
|
#include <ripple/rpc/Context.h>
|
||||||
|
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||||
|
#include <ripple/rpc/impl/Tuning.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
/** RPC command that retreives hook state objects from a particular namespace in a particular account.
|
||||||
|
{
|
||||||
|
account: <account>|<account_public_key>
|
||||||
|
namespace_id: <namespace hex>
|
||||||
|
ledger_hash: <string> // optional
|
||||||
|
ledger_index: <string | unsigned integer> // optional
|
||||||
|
type: <string> // optional, defaults to all account objects types
|
||||||
|
limit: <integer> // optional
|
||||||
|
marker: <opaque> // 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<ReadView const> 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
|
||||||
@@ -37,6 +37,8 @@ doAccountObjects(RPC::JsonContext&);
|
|||||||
Json::Value
|
Json::Value
|
||||||
doAccountOffers(RPC::JsonContext&);
|
doAccountOffers(RPC::JsonContext&);
|
||||||
Json::Value
|
Json::Value
|
||||||
|
doAccountNamespace(RPC::JsonContext&);
|
||||||
|
Json::Value
|
||||||
doAccountTxJson(RPC::JsonContext&);
|
doAccountTxJson(RPC::JsonContext&);
|
||||||
Json::Value
|
Json::Value
|
||||||
doBookOffers(RPC::JsonContext&);
|
doBookOffers(RPC::JsonContext&);
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Handler const handlerArray[]{
|
|||||||
Role::USER,
|
Role::USER,
|
||||||
NO_CONDITION},
|
NO_CONDITION},
|
||||||
{"account_lines", byRef(&doAccountLines), 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_channels", byRef(&doAccountChannels), Role::USER, NO_CONDITION},
|
||||||
{"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION},
|
{"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION},
|
||||||
{"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION},
|
{"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION},
|
||||||
|
|||||||
@@ -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 {
|
namespace {
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -109,6 +109,25 @@ getAccountObjects(
|
|||||||
std::uint32_t const limit,
|
std::uint32_t const limit,
|
||||||
Json::Value& jvResult);
|
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
|
/** Get ledger by hash
|
||||||
If there is no error in the return value, the ledger pointer will have
|
If there is no error in the return value, the ledger pointer will have
|
||||||
been filled
|
been filled
|
||||||
|
|||||||
Reference in New Issue
Block a user