mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add book_changes rpc (#4212)
This commit is contained in:
@@ -135,6 +135,7 @@ printHelp(const po::options_description& desc)
|
||||
"[strict]\n"
|
||||
" account_tx accountID [ledger_min [ledger_max [limit "
|
||||
"[offset]]]] [binary] [count] [descending]\n"
|
||||
" book_changes [<ledger hash|id>]\n"
|
||||
" book_offers <taker_pays> <taker_gets> [<taker [<ledger> "
|
||||
"[<limit> [<proof> [<marker>]]]]]\n"
|
||||
" can_delete [<ledgerid>|<ledgerhash>|now|always|never]\n"
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include <ripple/protocol/STParsedJSON.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/resource/ResourceManager.h>
|
||||
#include <ripple/rpc/BookChanges.h>
|
||||
#include <ripple/rpc/DeliveredAmount.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <boost/asio/ip/host_name.hpp>
|
||||
@@ -502,6 +503,11 @@ public:
|
||||
bool
|
||||
unsubLedger(std::uint64_t uListener) override;
|
||||
|
||||
bool
|
||||
subBookChanges(InfoSub::ref ispListener) override;
|
||||
bool
|
||||
unsubBookChanges(std::uint64_t uListener) override;
|
||||
|
||||
bool
|
||||
subServer(InfoSub::ref ispListener, Json::Value& jvResult, bool admin)
|
||||
override;
|
||||
@@ -743,9 +749,10 @@ private:
|
||||
sValidations, // Received validations.
|
||||
sPeerStatus, // Peer status changes.
|
||||
sConsensusPhase, // Consensus phase
|
||||
sBookChanges, // Per-ledger order book changes
|
||||
|
||||
sLastEntry = sConsensusPhase // as this name implies, any new entry
|
||||
// must be ADDED ABOVE this one
|
||||
sLastEntry = sBookChanges // as this name implies, any new entry
|
||||
// must be ADDED ABOVE this one
|
||||
};
|
||||
std::array<SubMapType, SubTypes::sLastEntry + 1> mStreamMaps;
|
||||
|
||||
@@ -2898,6 +2905,24 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mStreamMaps[sBookChanges].empty())
|
||||
{
|
||||
Json::Value jvObj = ripple::RPC::computeBookChanges(lpAccepted);
|
||||
|
||||
auto it = mStreamMaps[sBookChanges].begin();
|
||||
while (it != mStreamMaps[sBookChanges].end())
|
||||
{
|
||||
InfoSub::pointer p = it->second.lock();
|
||||
if (p)
|
||||
{
|
||||
p->send(jvObj, true);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
it = mStreamMaps[sBookChanges].erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
static bool firstTime = true;
|
||||
if (firstTime)
|
||||
@@ -3876,6 +3901,16 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
|
||||
.second;
|
||||
}
|
||||
|
||||
// <-- bool: true=added, false=already there
|
||||
bool
|
||||
NetworkOPsImp::subBookChanges(InfoSub::ref isrListener)
|
||||
{
|
||||
std::lock_guard sl(mSubLock);
|
||||
return mStreamMaps[sBookChanges]
|
||||
.emplace(isrListener->getSeq(), isrListener)
|
||||
.second;
|
||||
}
|
||||
|
||||
// <-- bool: true=erased, false=was not there
|
||||
bool
|
||||
NetworkOPsImp::unsubLedger(std::uint64_t uSeq)
|
||||
@@ -3884,6 +3919,14 @@ NetworkOPsImp::unsubLedger(std::uint64_t uSeq)
|
||||
return mStreamMaps[sLedger].erase(uSeq);
|
||||
}
|
||||
|
||||
// <-- bool: true=erased, false=was not there
|
||||
bool
|
||||
NetworkOPsImp::unsubBookChanges(std::uint64_t uSeq)
|
||||
{
|
||||
std::lock_guard sl(mSubLock);
|
||||
return mStreamMaps[sBookChanges].erase(uSeq);
|
||||
}
|
||||
|
||||
// <-- bool: true=added, false=already there
|
||||
bool
|
||||
NetworkOPsImp::subManifests(InfoSub::ref isrListener)
|
||||
|
||||
@@ -128,6 +128,11 @@ public:
|
||||
virtual bool
|
||||
unsubLedger(std::uint64_t uListener) = 0;
|
||||
|
||||
virtual bool
|
||||
subBookChanges(ref ispListener) = 0;
|
||||
virtual bool
|
||||
unsubBookChanges(std::uint64_t uListener) = 0;
|
||||
|
||||
virtual bool
|
||||
subManifests(ref ispListener) = 0;
|
||||
virtual bool
|
||||
|
||||
@@ -1242,6 +1242,7 @@ public:
|
||||
{"account_objects", &RPCParser::parseAccountItems, 1, 5},
|
||||
{"account_offers", &RPCParser::parseAccountItems, 1, 4},
|
||||
{"account_tx", &RPCParser::parseAccountTransactions, 1, 8},
|
||||
{"book_changes", &RPCParser::parseLedgerId, 1, 1},
|
||||
{"book_offers", &RPCParser::parseBookOffers, 2, 7},
|
||||
{"can_delete", &RPCParser::parseCanDelete, 0, 1},
|
||||
{"channel_authorize", &RPCParser::parseChannelAuthorize, 3, 4},
|
||||
|
||||
@@ -166,11 +166,13 @@ JSS(build_path); // in: TransactionSign
|
||||
JSS(build_version); // out: NetworkOPs
|
||||
JSS(cancel_after); // out: AccountChannels
|
||||
JSS(can_delete); // out: CanDelete
|
||||
JSS(changes); // out: BookChanges
|
||||
JSS(channel_id); // out: AccountChannels
|
||||
JSS(channels); // out: AccountChannels
|
||||
JSS(check); // in: AccountObjects
|
||||
JSS(check_nodes); // in: LedgerCleaner
|
||||
JSS(clear); // in/out: FetchInfo
|
||||
JSS(close); // out: BookChanges
|
||||
JSS(close_flags); // out: LedgerToJson
|
||||
JSS(close_time); // in: Application, out: NetworkOPs,
|
||||
// RCLCxPeerPos, LedgerToJson
|
||||
@@ -193,6 +195,8 @@ JSS(converge_time_s); // out: NetworkOPs
|
||||
JSS(cookie); // out: NetworkOPs
|
||||
JSS(count); // in: AccountTx*, ValidatorList
|
||||
JSS(counters); // in/out: retrieve counters
|
||||
JSS(currency_a); // out: BookChanges
|
||||
JSS(currency_b); // out: BookChanges
|
||||
JSS(currentShard); // out: NodeToShardStatus
|
||||
JSS(currentShardIndex); // out: NodeToShardStatus
|
||||
JSS(currency); // in: paths/PathRequest, STAmount
|
||||
@@ -282,6 +286,7 @@ JSS(hashes); // in: AccountObjects
|
||||
JSS(have_header); // out: InboundLedger
|
||||
JSS(have_state); // out: InboundLedger
|
||||
JSS(have_transactions); // out: InboundLedger
|
||||
JSS(high); // out: BookChanges
|
||||
JSS(highest_sequence); // out: AccountInfo
|
||||
JSS(highest_ticket); // out: AccountInfo
|
||||
JSS(historical_perminute); // historical_perminute.
|
||||
@@ -363,6 +368,7 @@ JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs
|
||||
JSS(local); // out: resource/Logic.h
|
||||
JSS(local_txs); // out: GetCounts
|
||||
JSS(local_static_keys); // out: ValidatorList
|
||||
JSS(low); // out: BookChanges
|
||||
JSS(lowest_sequence); // out: AccountInfo
|
||||
JSS(lowest_ticket); // out: AccountInfo
|
||||
JSS(majority); // out: RPC feature
|
||||
@@ -639,6 +645,8 @@ JSS(validator_sites); // out: ValidatorSites
|
||||
JSS(value); // out: STAmount
|
||||
JSS(version); // out: RPCVersion
|
||||
JSS(vetoed); // out: AmendmentTableImpl
|
||||
JSS(volume_a); // out: BookChanges
|
||||
JSS(volume_b); // out: BookChanges
|
||||
JSS(vote); // in: Feature
|
||||
JSS(warning); // rpc:
|
||||
JSS(warnings); // out: server_info, server_state
|
||||
|
||||
213
src/ripple/rpc/BookChanges.h
Normal file
213
src/ripple/rpc/BookChanges.h
Normal file
@@ -0,0 +1,213 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RPC_BOOKCHANGES_H_INCLUDED
|
||||
#define RIPPLE_RPC_BOOKCAHNGES_H_INCLUDED
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
}
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ReadView;
|
||||
class Transaction;
|
||||
class TxMeta;
|
||||
class STTx;
|
||||
|
||||
namespace RPC {
|
||||
|
||||
template <class L>
|
||||
Json::Value
|
||||
computeBookChanges(std::shared_ptr<L const> const& lpAccepted)
|
||||
{
|
||||
std::map<
|
||||
std::string,
|
||||
std::tuple<
|
||||
STAmount, // side A volume
|
||||
STAmount, // side B volume
|
||||
STAmount, // high rate
|
||||
STAmount, // low rate
|
||||
STAmount, // open rate
|
||||
STAmount // close rate
|
||||
>>
|
||||
tally;
|
||||
|
||||
for (auto& tx : lpAccepted->txs)
|
||||
{
|
||||
if (!tx.first || !tx.second ||
|
||||
!tx.first->isFieldPresent(sfTransactionType))
|
||||
continue;
|
||||
|
||||
std::optional<uint32_t> offerCancel;
|
||||
uint16_t tt = tx.first->getFieldU16(sfTransactionType);
|
||||
switch (tt)
|
||||
{
|
||||
case ttOFFER_CANCEL:
|
||||
case ttOFFER_CREATE: {
|
||||
if (tx.first->isFieldPresent(sfOfferSequence))
|
||||
offerCancel = tx.first->getFieldU32(sfOfferSequence);
|
||||
break;
|
||||
}
|
||||
// in future if any other ways emerge to cancel an offer
|
||||
// this switch makes them easy to add
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto const& node : tx.second->getFieldArray(sfAffectedNodes))
|
||||
{
|
||||
SField const& metaType = node.getFName();
|
||||
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
||||
|
||||
// we only care about ltOFFER objects being modified or
|
||||
// deleted
|
||||
if (nodeType != ltOFFER || metaType == sfCreatedNode)
|
||||
continue;
|
||||
|
||||
// if either FF or PF are missing we can't compute
|
||||
// but generally these are cancelled rather than crossed
|
||||
// so skipping them is consistent
|
||||
if (!node.isFieldPresent(sfFinalFields) ||
|
||||
!node.isFieldPresent(sfPreviousFields))
|
||||
continue;
|
||||
|
||||
STObject& finalFields = (const_cast<STObject&>(node))
|
||||
.getField(sfFinalFields)
|
||||
.downcast<STObject>();
|
||||
|
||||
STObject& previousFields = (const_cast<STObject&>(node))
|
||||
.getField(sfPreviousFields)
|
||||
.downcast<STObject>();
|
||||
|
||||
// defensive case that should never be hit
|
||||
if (!finalFields.isFieldPresent(sfTakerGets) ||
|
||||
!finalFields.isFieldPresent(sfTakerPays) ||
|
||||
!previousFields.isFieldPresent(sfTakerGets) ||
|
||||
!previousFields.isFieldPresent(sfTakerPays))
|
||||
continue;
|
||||
|
||||
// filter out any offers deleted by explicit offer cancels
|
||||
if (metaType == sfDeletedNode && offerCancel &&
|
||||
finalFields.getFieldU32(sfSequence) == *offerCancel)
|
||||
continue;
|
||||
|
||||
// compute the difference in gets and pays actually
|
||||
// affected onto the offer
|
||||
STAmount deltaGets = finalFields.getFieldAmount(sfTakerGets) -
|
||||
previousFields.getFieldAmount(sfTakerGets);
|
||||
STAmount deltaPays = finalFields.getFieldAmount(sfTakerPays) -
|
||||
previousFields.getFieldAmount(sfTakerPays);
|
||||
|
||||
std::string g{to_string(deltaGets.issue())};
|
||||
std::string p{to_string(deltaPays.issue())};
|
||||
|
||||
bool const noswap =
|
||||
isXRP(deltaGets) ? true : (isXRP(deltaPays) ? false : (g < p));
|
||||
|
||||
STAmount first = noswap ? deltaGets : deltaPays;
|
||||
STAmount second = noswap ? deltaPays : deltaGets;
|
||||
|
||||
// defensively programmed, should (probably) never happen
|
||||
if (second == beast::zero)
|
||||
continue;
|
||||
|
||||
STAmount rate = divide(first, second, noIssue());
|
||||
|
||||
if (first < beast::zero)
|
||||
first = -first;
|
||||
|
||||
if (second < beast::zero)
|
||||
second = -second;
|
||||
|
||||
std::stringstream ss;
|
||||
if (noswap)
|
||||
ss << g << "|" << p;
|
||||
else
|
||||
ss << p << "|" << g;
|
||||
|
||||
std::string key{ss.str()};
|
||||
|
||||
if (tally.find(key) == tally.end())
|
||||
tally[key] = {
|
||||
first, // side A vol
|
||||
second, // side B vol
|
||||
rate, // high
|
||||
rate, // low
|
||||
rate, // open
|
||||
rate // close
|
||||
};
|
||||
else
|
||||
{
|
||||
// increment volume
|
||||
auto& entry = tally[key];
|
||||
|
||||
std::get<0>(entry) += first; // side A vol
|
||||
std::get<1>(entry) += second; // side B vol
|
||||
|
||||
if (std::get<2>(entry) < rate) // high
|
||||
std::get<2>(entry) = rate;
|
||||
|
||||
if (std::get<3>(entry) > rate) // low
|
||||
std::get<3>(entry) = rate;
|
||||
|
||||
std::get<5>(entry) = rate; // close
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value jvObj(Json::objectValue);
|
||||
jvObj[jss::type] = "bookChanges";
|
||||
jvObj[jss::ledger_index] = lpAccepted->info().seq;
|
||||
jvObj[jss::ledger_hash] = to_string(lpAccepted->info().hash);
|
||||
jvObj[jss::ledger_time] = Json::Value::UInt(
|
||||
lpAccepted->info().closeTime.time_since_epoch().count());
|
||||
|
||||
jvObj[jss::changes] = Json::arrayValue;
|
||||
|
||||
for (auto const& entry : tally)
|
||||
{
|
||||
Json::Value& inner = jvObj[jss::changes].append(Json::objectValue);
|
||||
|
||||
STAmount volA = std::get<0>(entry.second);
|
||||
STAmount volB = std::get<1>(entry.second);
|
||||
|
||||
inner[jss::currency_a] =
|
||||
(isXRP(volA) ? "XRP_drops" : to_string(volA.issue()));
|
||||
inner[jss::currency_b] =
|
||||
(isXRP(volB) ? "XRP_drops" : to_string(volB.issue()));
|
||||
|
||||
inner[jss::volume_a] =
|
||||
(isXRP(volA) ? to_string(volA.xrp()) : to_string(volA.iou()));
|
||||
inner[jss::volume_b] =
|
||||
(isXRP(volB) ? to_string(volB.xrp()) : to_string(volB.iou()));
|
||||
|
||||
inner[jss::high] = to_string(std::get<2>(entry.second).iou());
|
||||
inner[jss::low] = to_string(std::get<3>(entry.second).iou());
|
||||
inner[jss::open] = to_string(std::get<4>(entry.second).iou());
|
||||
inner[jss::close] = to_string(std::get<5>(entry.second).iou());
|
||||
}
|
||||
|
||||
return jvObj;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <ripple/protocol/UintTypes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/BookChanges.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
|
||||
@@ -200,4 +201,16 @@ doBookOffers(RPC::JsonContext& context)
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
doBookChanges(RPC::JsonContext& context)
|
||||
{
|
||||
auto res = RPC::getLedgerByContext(context);
|
||||
|
||||
if (std::holds_alternative<Json::Value>(res))
|
||||
return std::get<Json::Value>(res);
|
||||
|
||||
return RPC::computeBookChanges(
|
||||
std::get<std::shared_ptr<Ledger const>>(res));
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -43,6 +43,8 @@ doAccountTxJson(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doBookOffers(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doBookChanges(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doBlackList(RPC::JsonContext&);
|
||||
Json::Value
|
||||
doCanDelete(RPC::JsonContext&);
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#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 <variant>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -37,122 +39,17 @@ namespace ripple {
|
||||
Json::Value
|
||||
doLedgerRequest(RPC::JsonContext& context)
|
||||
{
|
||||
if (context.app.config().reporting())
|
||||
return rpcError(rpcREPORTING_UNSUPPORTED);
|
||||
auto res = getLedgerByContext(context);
|
||||
|
||||
auto const hasHash = context.params.isMember(jss::ledger_hash);
|
||||
auto const hasIndex = context.params.isMember(jss::ledger_index);
|
||||
std::uint32_t ledgerIndex = 0;
|
||||
if (std::holds_alternative<Json::Value>(res))
|
||||
return std::get<Json::Value>(res);
|
||||
|
||||
auto& ledgerMaster = context.app.getLedgerMaster();
|
||||
LedgerHash ledgerHash;
|
||||
auto const& ledger = std::get<std::shared_ptr<Ledger const>>(res);
|
||||
|
||||
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
|
||||
{
|
||||
return RPC::make_param_error(
|
||||
"Exactly one of ledger_hash and ledger_index can be set.");
|
||||
}
|
||||
|
||||
context.loadType = Resource::feeHighBurdenRPC;
|
||||
|
||||
if (hasHash)
|
||||
{
|
||||
auto const& jsonHash = context.params[jss::ledger_hash];
|
||||
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
|
||||
return RPC::invalid_field_error(jss::ledger_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& jsonIndex = context.params[jss::ledger_index];
|
||||
if (!jsonIndex.isInt())
|
||||
return RPC::invalid_field_error(jss::ledger_index);
|
||||
|
||||
// We need a validated ledger to get the hash from the sequence
|
||||
if (ledgerMaster.getValidatedLedgerAge() >
|
||||
RPC::Tuning::maxValidatedLedgerAge)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return rpcError(rpcNO_CURRENT);
|
||||
return rpcError(rpcNOT_SYNCED);
|
||||
}
|
||||
|
||||
ledgerIndex = jsonIndex.asInt();
|
||||
auto ledger = ledgerMaster.getValidatedLedger();
|
||||
|
||||
if (ledgerIndex >= ledger->info().seq)
|
||||
return RPC::make_param_error("Ledger index too large");
|
||||
if (ledgerIndex <= 0)
|
||||
return RPC::make_param_error("Ledger index too small");
|
||||
|
||||
auto const j = context.app.journal("RPCHandler");
|
||||
// Try to get the hash of the desired ledger from the validated ledger
|
||||
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
if (!neededHash)
|
||||
{
|
||||
// Find a ledger more likely to have the hash of the desired ledger
|
||||
auto const refIndex = getCandidateLedger(ledgerIndex);
|
||||
auto refHash = hashOfSeq(*ledger, refIndex, j);
|
||||
assert(refHash);
|
||||
|
||||
ledger = ledgerMaster.getLedgerByHash(*refHash);
|
||||
if (!ledger)
|
||||
{
|
||||
// We don't have the ledger we need to figure out which ledger
|
||||
// they want. Try to get it.
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().acquire(
|
||||
*refHash, refIndex, InboundLedger::Reason::GENERIC))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] =
|
||||
getJson(LedgerFill(*il, &context));
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(*refHash))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] = il->getJson(0);
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
// Likely the app is shutting down
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
}
|
||||
assert(neededHash);
|
||||
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
|
||||
}
|
||||
|
||||
// Try to get the desired ledger
|
||||
// Verify all nodes even if we think we have it
|
||||
auto ledger = context.app.getInboundLedgers().acquire(
|
||||
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
|
||||
|
||||
// In standalone mode, accept the ledger from the ledger cache
|
||||
if (!ledger && context.app.config().standalone())
|
||||
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
|
||||
if (ledger)
|
||||
{
|
||||
// We already had the entire ledger verified/acquired
|
||||
Json::Value jvResult;
|
||||
jvResult[jss::ledger_index] = ledger->info().seq;
|
||||
addJson(jvResult, {*ledger, &context, 0});
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
|
||||
return il->getJson(0);
|
||||
|
||||
return RPC::make_error(
|
||||
rpcNOT_READY, "findCreate failed to return an inbound ledger");
|
||||
Json::Value jvResult;
|
||||
jvResult[jss::ledger_index] = ledger->info().seq;
|
||||
addJson(jvResult, {*ledger, &context, 0});
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -136,6 +136,10 @@ doSubscribe(RPC::JsonContext& context)
|
||||
{
|
||||
context.netOps.subLedger(ispSub, jvResult);
|
||||
}
|
||||
else if (streamName == "book_changes")
|
||||
{
|
||||
context.netOps.subBookChanges(ispSub);
|
||||
}
|
||||
else if (streamName == "manifests")
|
||||
{
|
||||
context.netOps.subManifests(ispSub);
|
||||
|
||||
@@ -72,6 +72,7 @@ Handler const handlerArray[]{
|
||||
{"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION},
|
||||
{"account_tx", byRef(&doAccountTxJson), Role::USER, NO_CONDITION},
|
||||
{"blacklist", byRef(&doBlackList), Role::ADMIN, NO_CONDITION},
|
||||
{"book_changes", byRef(&doBookChanges), Role::USER, NO_CONDITION},
|
||||
{"book_offers", byRef(&doBookOffers), Role::USER, NO_CONDITION},
|
||||
{"can_delete", byRef(&doCanDelete), Role::ADMIN, NO_CONDITION},
|
||||
{"channel_authorize", byRef(&doChannelAuthorize), Role::USER, NO_CONDITION},
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/ledger/InboundLedgers.h>
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/ledger/LedgerToJson.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/app/misc/NetworkOPs.h>
|
||||
#include <ripple/app/reporting/P2pProxy.h>
|
||||
@@ -30,14 +32,17 @@
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/net/InfoSub.h>
|
||||
#include <ripple/net/RPCErr.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/RPCHandler.h>
|
||||
#include <ripple/rpc/Role.h>
|
||||
#include <ripple/rpc/impl/Handler.h>
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <variant>
|
||||
|
||||
namespace ripple {
|
||||
namespace RPC {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/ledger/LedgerMaster.h>
|
||||
#include <ripple/app/ledger/LedgerToJson.h>
|
||||
#include <ripple/app/ledger/OpenLedger.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
#include <ripple/app/paths/TrustLine.h>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/impl/GRPCHelpers.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -956,5 +958,119 @@ getAPIVersionNumber(Json::Value const& jv, bool betaEnabled)
|
||||
return requestedVersion.asUInt();
|
||||
}
|
||||
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context)
|
||||
{
|
||||
if (context.app.config().reporting())
|
||||
return rpcError(rpcREPORTING_UNSUPPORTED);
|
||||
|
||||
auto const hasHash = context.params.isMember(jss::ledger_hash);
|
||||
auto const hasIndex = context.params.isMember(jss::ledger_index);
|
||||
std::uint32_t ledgerIndex = 0;
|
||||
|
||||
auto& ledgerMaster = context.app.getLedgerMaster();
|
||||
LedgerHash ledgerHash;
|
||||
|
||||
if ((hasHash && hasIndex) || !(hasHash || hasIndex))
|
||||
{
|
||||
return RPC::make_param_error(
|
||||
"Exactly one of ledger_hash and ledger_index can be set.");
|
||||
}
|
||||
|
||||
context.loadType = Resource::feeHighBurdenRPC;
|
||||
|
||||
if (hasHash)
|
||||
{
|
||||
auto const& jsonHash = context.params[jss::ledger_hash];
|
||||
if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString()))
|
||||
return RPC::invalid_field_error(jss::ledger_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& jsonIndex = context.params[jss::ledger_index];
|
||||
if (!jsonIndex.isInt())
|
||||
return RPC::invalid_field_error(jss::ledger_index);
|
||||
|
||||
// We need a validated ledger to get the hash from the sequence
|
||||
if (ledgerMaster.getValidatedLedgerAge() >
|
||||
RPC::Tuning::maxValidatedLedgerAge)
|
||||
{
|
||||
if (context.apiVersion == 1)
|
||||
return rpcError(rpcNO_CURRENT);
|
||||
return rpcError(rpcNOT_SYNCED);
|
||||
}
|
||||
|
||||
ledgerIndex = jsonIndex.asInt();
|
||||
auto ledger = ledgerMaster.getValidatedLedger();
|
||||
|
||||
if (ledgerIndex >= ledger->info().seq)
|
||||
return RPC::make_param_error("Ledger index too large");
|
||||
if (ledgerIndex <= 0)
|
||||
return RPC::make_param_error("Ledger index too small");
|
||||
|
||||
auto const j = context.app.journal("RPCHandler");
|
||||
// Try to get the hash of the desired ledger from the validated ledger
|
||||
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
if (!neededHash)
|
||||
{
|
||||
// Find a ledger more likely to have the hash of the desired ledger
|
||||
auto const refIndex = getCandidateLedger(ledgerIndex);
|
||||
auto refHash = hashOfSeq(*ledger, refIndex, j);
|
||||
assert(refHash);
|
||||
|
||||
ledger = ledgerMaster.getLedgerByHash(*refHash);
|
||||
if (!ledger)
|
||||
{
|
||||
// We don't have the ledger we need to figure out which ledger
|
||||
// they want. Try to get it.
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().acquire(
|
||||
*refHash, refIndex, InboundLedger::Reason::GENERIC))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] =
|
||||
getJson(LedgerFill(*il, &context));
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(*refHash))
|
||||
{
|
||||
Json::Value jvResult = RPC::make_error(
|
||||
rpcLGR_NOT_FOUND,
|
||||
"acquiring ledger containing requested index");
|
||||
jvResult[jss::acquiring] = il->getJson(0);
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
// Likely the app is shutting down
|
||||
return Json::Value();
|
||||
}
|
||||
|
||||
neededHash = hashOfSeq(*ledger, ledgerIndex, j);
|
||||
}
|
||||
assert(neededHash);
|
||||
ledgerHash = neededHash ? *neededHash : beast::zero; // kludge
|
||||
}
|
||||
|
||||
// Try to get the desired ledger
|
||||
// Verify all nodes even if we think we have it
|
||||
auto ledger = context.app.getInboundLedgers().acquire(
|
||||
ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
|
||||
|
||||
// In standalone mode, accept the ledger from the ledger cache
|
||||
if (!ledger && context.app.config().standalone())
|
||||
ledger = ledgerMaster.getLedgerByHash(ledgerHash);
|
||||
|
||||
if (ledger)
|
||||
return ledger;
|
||||
|
||||
if (auto il = context.app.getInboundLedgers().find(ledgerHash))
|
||||
return il->getJson(0);
|
||||
|
||||
return RPC::make_error(
|
||||
rpcNOT_READY, "findCreate failed to return an inbound ledger");
|
||||
}
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
#include <optional>
|
||||
#include <org/xrpl/rpc/v1/xrp_ledger.pb.h>
|
||||
#include <variant>
|
||||
|
||||
namespace Json {
|
||||
class Value;
|
||||
@@ -287,6 +288,11 @@ chooseLedgerEntryType(Json::Value const& params);
|
||||
unsigned int
|
||||
getAPIVersionNumber(const Json::Value& value, bool betaEnabled);
|
||||
|
||||
/** Return a ledger based on ledger_hash or ledger_index,
|
||||
or an RPC error */
|
||||
std::variant<std::shared_ptr<Ledger const>, Json::Value>
|
||||
getLedgerByContext(RPC::JsonContext& context);
|
||||
|
||||
} // namespace RPC
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
Reference in New Issue
Block a user