mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-19 19:25:53 +00:00
Achieve parity with rippled API
This commit is contained in:
@@ -36,29 +36,22 @@ add_executable (clio_server
|
||||
add_executable (clio_tests
|
||||
unittests/main.cpp
|
||||
)
|
||||
add_library(clio src/backend/BackendInterface.h)
|
||||
add_library(clio)
|
||||
include_directories(src)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps")
|
||||
include(ExternalProject)
|
||||
message(${CMAKE_CURRENT_BINARY_DIR})
|
||||
message(${CMAKE_MODULE_PATH})
|
||||
#include(rippled/Builds/CMake/RippledCore.cmake)
|
||||
add_subdirectory(rippled)
|
||||
target_link_libraries(clio PUBLIC xrpl_core grpc_pbufs)
|
||||
add_dependencies(clio xrpl_core)
|
||||
add_dependencies(clio grpc_pbufs)
|
||||
get_target_property(grpc_includes grpc_pbufs INCLUDE_DIRECTORIES)
|
||||
#get_target_property(xrpl_core_includes xrpl_core INCLUDE_DIRECTORIES)
|
||||
# get_target_property(proto_includes protobuf_src INCLUDE_DIRECTORIES)
|
||||
message("hi")
|
||||
message("${grpc_includes}")
|
||||
message("${proto_includes}")
|
||||
# ExternalProject_Get_Property(protobuf_src SOURCE_DIR)
|
||||
message("${SOURCE_DIR}")
|
||||
INCLUDE_DIRECTORIES(${grpc_includes})
|
||||
#INCLUDE_DIRECTORIES(${xrpl_core_includes})
|
||||
INCLUDE_DIRECTORIES(${SOURCE_DIR}/src)
|
||||
# ExternalProject_Get_Property(grpc_src SOURCE_DIR)
|
||||
INCLUDE_DIRECTORIES(${SOURCE_DIR}/include)
|
||||
get_target_property(xrpl_core_includes xrpl_core INCLUDE_DIRECTORIES)
|
||||
message("${xrpl_core_includes}")
|
||||
@@ -68,34 +61,46 @@ include(Postgres)
|
||||
|
||||
|
||||
target_sources(clio PRIVATE
|
||||
src/backend/CassandraBackend.cpp
|
||||
src/backend/PostgresBackend.cpp
|
||||
src/backend/BackendIndexer.cpp
|
||||
src/backend/BackendInterface.cpp
|
||||
src/backend/Pg.cpp
|
||||
src/backend/DBHelpers.cpp
|
||||
src/etl/ETLSource.cpp
|
||||
src/etl/ReportingETL.cpp
|
||||
src/server/Handlers.cpp
|
||||
src/server/SubscriptionManager.cpp
|
||||
src/handlers/AccountInfo.cpp
|
||||
src/handlers/Tx.cpp
|
||||
src/handlers/RPCHelpers.cpp
|
||||
src/handlers/AccountTx.cpp
|
||||
src/handlers/LedgerData.cpp
|
||||
src/handlers/BookOffers.cpp
|
||||
src/handlers/LedgerRange.cpp
|
||||
src/handlers/Ledger.cpp
|
||||
src/handlers/LedgerEntry.cpp
|
||||
src/handlers/AccountChannels.cpp
|
||||
src/handlers/AccountLines.cpp
|
||||
src/handlers/AccountCurrencies.cpp
|
||||
src/handlers/AccountOffers.cpp
|
||||
src/handlers/AccountObjects.cpp
|
||||
src/handlers/ChannelAuthorize.cpp
|
||||
src/handlers/ChannelVerify.cpp
|
||||
src/handlers/Subscribe.cpp
|
||||
src/handlers/ServerInfo.cpp)
|
||||
## Backend
|
||||
src/backend/CassandraBackend.cpp
|
||||
src/backend/PostgresBackend.cpp
|
||||
src/backend/BackendIndexer.cpp
|
||||
src/backend/BackendInterface.cpp
|
||||
src/backend/Pg.cpp
|
||||
src/backend/DBHelpers.cpp
|
||||
## Reporting
|
||||
src/etl/ETLSource.cpp
|
||||
src/etl/ReportingETL.cpp
|
||||
## Server
|
||||
src/webserver/SubscriptionManager.cpp
|
||||
## Handlers
|
||||
src/handlers/Status.cpp
|
||||
src/handlers/RPCHelpers.cpp
|
||||
src/handlers/Handlers.cpp
|
||||
src/handlers/Context.cpp
|
||||
## Methods
|
||||
# Account
|
||||
src/handlers/methods/impl/AccountChannels.cpp
|
||||
src/handlers/methods/impl/AccountCurrencies.cpp
|
||||
src/handlers/methods/impl/AccountInfo.cpp
|
||||
src/handlers/methods/impl/AccountLines.cpp
|
||||
src/handlers/methods/impl/AccountOffers.cpp
|
||||
src/handlers/methods/impl/AccountObjects.cpp
|
||||
# Ledger
|
||||
src/handlers/methods/impl/Ledger.cpp
|
||||
src/handlers/methods/impl/LedgerData.cpp
|
||||
src/handlers/methods/impl/LedgerEntry.cpp
|
||||
src/handlers/methods/impl/LedgerRange.cpp
|
||||
# Transaction
|
||||
src/handlers/methods/impl/Tx.cpp
|
||||
src/handlers/methods/impl/AccountTx.cpp
|
||||
# Dex
|
||||
src/handlers/methods/impl/BookOffers.cpp
|
||||
# Payment Channel
|
||||
src/handlers/methods/impl/ChannelAuthorize.cpp
|
||||
src/handlers/methods/impl/ChannelVerify.cpp
|
||||
# Subscribe
|
||||
src/handlers/methods/impl/Subscribe.cpp)
|
||||
|
||||
|
||||
message(${Boost_LIBRARIES})
|
||||
|
||||
@@ -224,6 +224,7 @@ BackendInterface::fetchLedgerPage(
|
||||
page.objects.end(), partial.objects.begin(), partial.objects.end());
|
||||
page.cursor = partial.cursor;
|
||||
} while (page.objects.size() < limit && page.cursor);
|
||||
|
||||
if (incomplete)
|
||||
{
|
||||
auto rng = fetchLedgerRange();
|
||||
@@ -332,4 +333,37 @@ BackendInterface::checkFlagLedgers() const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ripple::Fees>
|
||||
BackendInterface::fetchFees(std::uint32_t seq) const
|
||||
{
|
||||
ripple::Fees fees;
|
||||
|
||||
auto key = ripple::keylet::fees().key;
|
||||
auto bytes = fetchLedgerObject(key, seq);
|
||||
|
||||
if (!bytes)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __func__ << " - could not find fees";
|
||||
return {};
|
||||
}
|
||||
|
||||
ripple::SerialIter it(bytes->data(), bytes->size());
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfBaseFee) != -1)
|
||||
fees.base = sle.getFieldU64(ripple::sfBaseFee);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReferenceFeeUnits) != -1)
|
||||
fees.units = sle.getFieldU32(ripple::sfReferenceFeeUnits);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReserveBase) != -1)
|
||||
fees.reserve = sle.getFieldU32(ripple::sfReserveBase);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReserveIncrement) != -1)
|
||||
fees.increment = sle.getFieldU32(ripple::sfReserveIncrement);
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
@@ -91,18 +91,23 @@ public:
|
||||
virtual std::optional<ripple::LedgerInfo>
|
||||
fetchLedgerBySequence(uint32_t sequence) const = 0;
|
||||
|
||||
virtual std::optional<ripple::LedgerInfo>
|
||||
fetchLedgerByHash(ripple::uint256 const& hash) const = 0;
|
||||
|
||||
virtual std::optional<uint32_t>
|
||||
fetchLatestLedgerSequence() const = 0;
|
||||
|
||||
virtual std::optional<LedgerRange>
|
||||
fetchLedgerRange() const = 0;
|
||||
|
||||
std::optional<ripple::Fees>
|
||||
fetchFees(std::uint32_t seq) const;
|
||||
|
||||
// Doesn't throw DatabaseTimeout. Should be used with care.
|
||||
std::optional<LedgerRange>
|
||||
fetchLedgerRangeNoThrow() const;
|
||||
|
||||
// *** transaction methods
|
||||
|
||||
virtual std::optional<TransactionAndMetadata>
|
||||
fetchTransaction(ripple::uint256 const& hash) const = 0;
|
||||
|
||||
|
||||
@@ -286,6 +286,7 @@ struct ReadDiffCallbackData
|
||||
CassandraBackend const& backend;
|
||||
uint32_t sequence;
|
||||
std::vector<LedgerObject>& result;
|
||||
std::mutex& mtx;
|
||||
std::condition_variable& cv;
|
||||
|
||||
std::atomic_uint32_t& numFinished;
|
||||
@@ -295,12 +296,14 @@ struct ReadDiffCallbackData
|
||||
CassandraBackend const& backend,
|
||||
uint32_t sequence,
|
||||
std::vector<LedgerObject>& result,
|
||||
std::mutex& mtx,
|
||||
std::condition_variable& cv,
|
||||
std::atomic_uint32_t& numFinished,
|
||||
size_t batchSize)
|
||||
: backend(backend)
|
||||
, sequence(sequence)
|
||||
, result(result)
|
||||
, mtx(mtx)
|
||||
, cv(cv)
|
||||
, numFinished(numFinished)
|
||||
, batchSize(batchSize)
|
||||
@@ -355,6 +358,7 @@ CassandraBackend::fetchLedgerDiffs(std::vector<uint32_t> const& sequences) const
|
||||
*this,
|
||||
sequences[i],
|
||||
results[sequences[i]],
|
||||
mtx,
|
||||
cv,
|
||||
numFinished,
|
||||
sequences.size()));
|
||||
@@ -414,7 +418,10 @@ CassandraBackend::doFetchLedgerPage(
|
||||
CassandraStatement statement{selectKeys_};
|
||||
statement.bindInt(index->keyIndex);
|
||||
if (cursor)
|
||||
statement.bindBytes(*cursor);
|
||||
{
|
||||
auto thisCursor = *cursor;
|
||||
statement.bindBytes(++thisCursor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ripple::uint256 zero;
|
||||
@@ -426,12 +433,13 @@ CassandraBackend::doFetchLedgerPage(
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__ << " - got keys - size = " << result.numRows();
|
||||
std::vector<ripple::uint256> keys;
|
||||
|
||||
std::vector<ripple::uint256> keys;
|
||||
do
|
||||
{
|
||||
keys.push_back(result.getUInt256());
|
||||
} while (result.nextRow());
|
||||
|
||||
if (keys.size() && keys.size() >= limit)
|
||||
{
|
||||
page.cursor = keys.back();
|
||||
@@ -454,12 +462,17 @@ CassandraBackend::doFetchLedgerPage(
|
||||
page.objects.push_back({std::move(key), std::move(obj)});
|
||||
}
|
||||
}
|
||||
|
||||
if (!cursor && (!keys.size() || !keys[0].isZero()))
|
||||
{
|
||||
page.warning = "Data may be incomplete";
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
if (!cursor)
|
||||
return {{}, {}, "Data may be incomplete"};
|
||||
|
||||
return {};
|
||||
}
|
||||
std::vector<Blob>
|
||||
@@ -479,7 +492,7 @@ CassandraBackend::fetchLedgerObjects(
|
||||
for (std::size_t i = 0; i < keys.size(); ++i)
|
||||
{
|
||||
cbs.push_back(std::make_shared<ReadObjectCallbackData>(
|
||||
*this, keys[i], sequence, results[i], cv, numFinished, numKeys));
|
||||
*this, keys[i], sequence, results[i], mtx, cv, numFinished, numKeys));
|
||||
readObject(*cbs[i]);
|
||||
}
|
||||
assert(results.size() == cbs.size());
|
||||
@@ -1574,6 +1587,12 @@ CassandraBackend::open(bool readOnly)
|
||||
if (!insertLedgerHash_.prepareStatement(query, session_.get()))
|
||||
continue;
|
||||
|
||||
query.str("");
|
||||
query << "SELECT sequence FROM " << tablePrefix << "ledger_hashes "
|
||||
<< "WHERE hash = ? LIMIT 1";
|
||||
if (!selectLedgerByHash_.prepareStatement(query, session_.get()))
|
||||
continue;
|
||||
|
||||
query.str("");
|
||||
query << " update " << tablePrefix << "ledger_range"
|
||||
<< " set sequence = ? where is_latest = ? if sequence in "
|
||||
|
||||
@@ -575,6 +575,8 @@ public:
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "finished a request";
|
||||
size_t batchSize = requestParams_.batchSize;
|
||||
|
||||
std::unique_lock lk(requestParams_.mtx);
|
||||
if (++(requestParams_.numFinished) == batchSize)
|
||||
requestParams_.cv.notify_all();
|
||||
}
|
||||
@@ -657,6 +659,7 @@ private:
|
||||
CassandraPreparedStatement deleteLedgerRange_;
|
||||
CassandraPreparedStatement updateLedgerHeader_;
|
||||
CassandraPreparedStatement selectLedgerBySeq_;
|
||||
CassandraPreparedStatement selectLedgerByHash_;
|
||||
CassandraPreparedStatement selectLatestLedger_;
|
||||
CassandraPreparedStatement selectLedgerRange_;
|
||||
CassandraPreparedStatement selectLedgerDiff_;
|
||||
@@ -906,9 +909,7 @@ public:
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << __func__;
|
||||
CassandraStatement statement{selectLatestLedger_};
|
||||
std::cout << "READ" << std::endl;
|
||||
CassandraResult result = executeSyncRead(statement);
|
||||
std::cout << "ITS THE READ" << std::endl;
|
||||
if (!result.hasResult())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
@@ -934,6 +935,26 @@ public:
|
||||
std::vector<unsigned char> header = result.getBytes();
|
||||
return deserializeHeader(ripple::makeSlice(header));
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerInfo>
|
||||
fetchLedgerByHash(ripple::uint256 const& hash) const override
|
||||
{
|
||||
CassandraStatement statement{selectLedgerByHash_};
|
||||
|
||||
statement.bindBytes(hash);
|
||||
|
||||
CassandraResult result = executeSyncRead(statement);
|
||||
if (!result.hasResult())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << __func__ << " - no rows returned";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::uint32_t sequence = result.getInt64();
|
||||
|
||||
return fetchLedgerBySequence(sequence);
|
||||
}
|
||||
|
||||
std::optional<LedgerRange>
|
||||
fetchLedgerRange() const override;
|
||||
|
||||
@@ -1033,6 +1054,7 @@ public:
|
||||
CassandraBackend const& backend;
|
||||
ripple::uint256 const& hash;
|
||||
TransactionAndMetadata& result;
|
||||
std::mutex& mtx;
|
||||
std::condition_variable& cv;
|
||||
|
||||
std::atomic_uint32_t& numFinished;
|
||||
@@ -1042,12 +1064,14 @@ public:
|
||||
CassandraBackend const& backend,
|
||||
ripple::uint256 const& hash,
|
||||
TransactionAndMetadata& result,
|
||||
std::mutex& mtx,
|
||||
std::condition_variable& cv,
|
||||
std::atomic_uint32_t& numFinished,
|
||||
size_t batchSize)
|
||||
: backend(backend)
|
||||
, hash(hash)
|
||||
, result(result)
|
||||
, mtx(mtx)
|
||||
, cv(cv)
|
||||
, numFinished(numFinished)
|
||||
, batchSize(batchSize)
|
||||
@@ -1072,7 +1096,7 @@ public:
|
||||
for (std::size_t i = 0; i < hashes.size(); ++i)
|
||||
{
|
||||
cbs.push_back(std::make_shared<ReadCallbackData>(
|
||||
*this, hashes[i], results[i], cv, numFinished, numHashes));
|
||||
*this, hashes[i], results[i], mtx, cv, numFinished, numHashes));
|
||||
read(*cbs[i]);
|
||||
}
|
||||
assert(results.size() == cbs.size());
|
||||
@@ -1106,6 +1130,7 @@ public:
|
||||
ripple::uint256 const& key;
|
||||
uint32_t sequence;
|
||||
Blob& result;
|
||||
std::mutex& mtx;
|
||||
std::condition_variable& cv;
|
||||
|
||||
std::atomic_uint32_t& numFinished;
|
||||
@@ -1116,6 +1141,7 @@ public:
|
||||
ripple::uint256 const& key,
|
||||
uint32_t sequence,
|
||||
Blob& result,
|
||||
std::mutex& mtx,
|
||||
std::condition_variable& cv,
|
||||
std::atomic_uint32_t& numFinished,
|
||||
size_t batchSize)
|
||||
@@ -1123,6 +1149,7 @@ public:
|
||||
, key(key)
|
||||
, sequence(sequence)
|
||||
, result(result)
|
||||
, mtx(mtx)
|
||||
, cv(cv)
|
||||
, numFinished(numFinished)
|
||||
, batchSize(batchSize)
|
||||
|
||||
@@ -199,6 +199,20 @@ PostgresBackend::fetchLedgerBySequence(uint32_t sequence) const
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerInfo>
|
||||
PostgresBackend::fetchLedgerByHash(ripple::uint256 const& hash) const
|
||||
{
|
||||
PgQuery pgQuery(pgPool_);
|
||||
pgQuery("SET statement_timeout TO 10000");
|
||||
std::stringstream sql;
|
||||
sql << "SELECT * FROM ledgers WHERE ledger_hash = "
|
||||
<< ripple::to_string(hash);
|
||||
auto res = pgQuery(sql.str().data());
|
||||
if (checkResult(res, 10))
|
||||
return parseLedgerInfo(res);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<LedgerRange>
|
||||
PostgresBackend::fetchLedgerRange() const
|
||||
{
|
||||
|
||||
@@ -27,6 +27,9 @@ public:
|
||||
std::optional<ripple::LedgerInfo>
|
||||
fetchLedgerBySequence(uint32_t sequence) const override;
|
||||
|
||||
std::optional<ripple::LedgerInfo>
|
||||
fetchLedgerByHash(ripple::uint256 const& hash) const override;
|
||||
|
||||
std::optional<LedgerRange>
|
||||
fetchLedgerRange() const override;
|
||||
|
||||
|
||||
@@ -1,26 +1,108 @@
|
||||
The backend is clio's view into the database. The database could be either PostgreSQL or Cassandra.
|
||||
Multiple clio servers can share access to the same database.
|
||||
Reporting mode is a special operating mode of rippled, designed to handle RPCs
|
||||
for validated data. A server running in reporting mode does not connect to the
|
||||
p2p network, but rather extracts validated data from a node that is connected
|
||||
to the p2p network. To run rippled in reporting mode, you must also run a
|
||||
separate rippled node in p2p mode, to use as an ETL source. Multiple reporting
|
||||
nodes can share access to the same network accessible databases (Postgres and
|
||||
Cassandra); at any given time, only one reporting node will be performing ETL
|
||||
and writing to the databases, while the others simply read from the databases.
|
||||
A server running in reporting mode will forward any requests that require access
|
||||
to the p2p network to a p2p node.
|
||||
|
||||
`BackendInterface`, and it's derived classes, store very little state. The read methods go directly to the database,
|
||||
and generally don't access any internal data structures. Nearly all of the methods are const.
|
||||
# Reporting ETL
|
||||
A single reporting node has one or more ETL sources, specified in the config
|
||||
file. A reporting node will subscribe to the "ledgers" stream of each of the ETL
|
||||
sources. This stream sends a message whenever a new ledger is validated. Upon
|
||||
receiving a message on the stream, reporting will then fetch the data associated
|
||||
with the newly validated ledger from one of the ETL sources. The fetch is
|
||||
performed via a gRPC request ("GetLedger"). This request returns the ledger
|
||||
header, transactions+metadata blobs, and every ledger object
|
||||
added/modified/deleted as part of this ledger. ETL then writes all of this data
|
||||
to the databases, and moves on to the next ledger. ETL does not apply
|
||||
transactions, but rather extracts the already computed results of those
|
||||
transactions (all of the added/modified/deleted SHAMap leaf nodes of the state
|
||||
tree). The new SHAMap inner nodes are computed by the ETL writer; this computation mainly
|
||||
involves manipulating child pointers and recomputing hashes, logic which is
|
||||
buried inside of SHAMap.
|
||||
|
||||
The data model used by clio is called the flat map data model. The flat map data model does not store any
|
||||
SHAMap inner nodes, and instead only stores the raw ledger objects contained in the leaf node. Ledger objects
|
||||
are stored in the database with a compound key of `(object_id, ledger_sequence)`, where `ledger_sequence` is the
|
||||
ledger in which the object was created or modified. Objects are then fetched using an inequality operation,
|
||||
such as `SELECT * FROM objects WHERE object_id = id AND ledger_sequence <= seq order by ledger_sequence limit 1`, where `seq` is the ledger
|
||||
in which we are trying to look up the object. When an object is deleted, we write an empty blob.
|
||||
If the database is entirely empty, ETL must download an entire ledger in full
|
||||
(as opposed to just the diff, as described above). This download is done via the
|
||||
"GetLedgerData" gRPC request. "GetLedgerData" allows clients to page through an
|
||||
entire ledger over several RPC calls. ETL will page through an entire ledger,
|
||||
and write each object to the database.
|
||||
|
||||
Transactions are stored in a separate table, where the key is the hash.
|
||||
If the database is not empty, the reporting node will first come up in a "soft"
|
||||
read-only mode. In read-only mode, the server does not perform ETL and simply
|
||||
publishes new ledgers as they are written to the database.
|
||||
If the database is not updated within a certain time period
|
||||
(currently hard coded at 20 seconds), the reporting node will begin the ETL
|
||||
process and start writing to the database. Postgres will report an error when
|
||||
trying to write a record with a key that already exists. ETL uses this error to
|
||||
determine that another process is writing to the database, and subsequently
|
||||
falls back to a soft read-only mode. Reporting nodes can also operate in strict
|
||||
read-only mode, in which case they will never write to the database.
|
||||
|
||||
Ledger headers are stored in their own table.
|
||||
# Database Nuances
|
||||
The database schema for reporting mode does not allow any history gaps.
|
||||
Attempting to write a ledger to a non-empty database where the previous ledger
|
||||
does not exist will return an error.
|
||||
|
||||
The account_tx table maps accounts to a list of transactions that affect them.
|
||||
The databases must be set up prior to running reporting mode. This requires
|
||||
creating the Postgres database, and setting up the Cassandra keyspace. Reporting
|
||||
mode will create the objects table in Cassandra if the table does not yet exist.
|
||||
|
||||
Creating the Postgres database:
|
||||
```
|
||||
$ psql -h [host] -U [user]
|
||||
postgres=# create database [database];
|
||||
```
|
||||
Creating the keyspace:
|
||||
```
|
||||
$ cqlsh [host] [port]
|
||||
> CREATE KEYSPACE rippled WITH REPLICATION =
|
||||
{'class' : 'SimpleStrategy', 'replication_factor' : 3 };
|
||||
```
|
||||
A replication factor of 3 is recommended. However, when running locally, only a
|
||||
replication factor of 1 is supported.
|
||||
|
||||
### Backend Indexer
|
||||
Online delete is not supported by reporting mode and must be done manually. The
|
||||
easiest way to do this would be to setup a second Cassandra keyspace and
|
||||
Postgres database, bring up a single reporting mode instance that uses those
|
||||
databases, and start ETL at a ledger of your choosing (via --startReporting on
|
||||
the command line). Once this node is caught up, the other databases can be
|
||||
deleted.
|
||||
|
||||
To delete:
|
||||
```
|
||||
$ psql -h [host] -U [user] -d [database]
|
||||
reporting=$ truncate table ledgers cascade;
|
||||
```
|
||||
```
|
||||
$ cqlsh [host] [port]
|
||||
> truncate table objects;
|
||||
```
|
||||
# Proxy
|
||||
RPCs that require access to the p2p network and/or the open ledger are forwarded
|
||||
from the reporting node to one of the ETL sources. The request is not processed
|
||||
prior to forwarding, and the response is delivered as-is to the client.
|
||||
Reporting will forward any requests that always require p2p/open ledger access
|
||||
(fee and submit, for instance). In addition, any request that explicitly
|
||||
requests data from the open or closed ledger (via setting
|
||||
"ledger_index":"current" or "ledger_index":"closed"), will be forwarded to a
|
||||
p2p node.
|
||||
|
||||
For the stream "transactions_proposed" (AKA "rt_transactions"), reporting
|
||||
subscribes to the "transactions_proposed" streams of each ETL source, and then
|
||||
forwards those messages to any clients subscribed to the same stream on the
|
||||
reporting node. A reporting node will subscribe to the stream on each ETL
|
||||
source, but will only forward the messages from one of the streams at any given
|
||||
time (to avoid sending the same message more than once to the same client).
|
||||
|
||||
# API changes
|
||||
A reporting node defaults to only returning validated data. If a ledger is not
|
||||
specified, the most recently validated ledger is used. This is in contrast to
|
||||
the normal rippled behavior, where the open ledger is used by default.
|
||||
|
||||
Reporting will reject all subscribe requests for streams "server", "manifests",
|
||||
"validations", "peer_status" and "consensus".
|
||||
|
||||
With the elimination of SHAMap inner nodes, iterating across a ledger becomes difficult. In order to iterate,
|
||||
a keys table is maintained, which keeps a collection of all keys in a ledger. This table has one record for every
|
||||
million ledgers, where each record has all of the keys in that ledger, as well as all of the keys that were deleted
|
||||
between that ledger and the prior ledger written to the keys table. Most of this logic is contained in `BackendIndexer`.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
|
||||
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
|
||||
#include <grpcpp/grpcpp.h>
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <server/SubscriptionManager.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
@@ -138,49 +139,18 @@ ReportingETL::loadInitialLedger(uint32_t startingSequence)
|
||||
return lgrInfo;
|
||||
}
|
||||
|
||||
std::optional<ripple::Fees>
|
||||
ReportingETL::getFees(std::uint32_t seq)
|
||||
{
|
||||
ripple::Fees fees;
|
||||
|
||||
auto key = ripple::keylet::fees().key;
|
||||
auto bytes = backend_->fetchLedgerObject(key, seq);
|
||||
|
||||
if (!bytes)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __func__ << " - could not find fees";
|
||||
return {};
|
||||
}
|
||||
|
||||
ripple::SerialIter it(bytes->data(), bytes->size());
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfBaseFee) != -1)
|
||||
fees.base = sle.getFieldU64(ripple::sfBaseFee);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReferenceFeeUnits) != -1)
|
||||
fees.units = sle.getFieldU32(ripple::sfReferenceFeeUnits);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReserveBase) != -1)
|
||||
fees.reserve = sle.getFieldU32(ripple::sfReserveBase);
|
||||
|
||||
if (sle.getFieldIndex(ripple::sfReserveIncrement) != -1)
|
||||
fees.increment = sle.getFieldU32(ripple::sfReserveIncrement);
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
void
|
||||
ReportingETL::publishLedger(ripple::LedgerInfo const& lgrInfo)
|
||||
{
|
||||
{
|
||||
auto ledgerRange = backend_->fetchLedgerRangeNoThrow();
|
||||
|
||||
auto fees = getFees(lgrInfo.seq);
|
||||
std::vector<Backend::TransactionAndMetadata> transactions;
|
||||
while (true)
|
||||
std::optional<ripple::Fees> fees;
|
||||
std::vector<Backend::TransactionAndMetadata> transactions;
|
||||
for(;;)
|
||||
{
|
||||
try
|
||||
{
|
||||
fees = backend_->fetchFees(lgrInfo.seq);
|
||||
transactions = backend_->fetchAllTransactionsInLedger(lgrInfo.seq);
|
||||
break;
|
||||
}
|
||||
@@ -448,7 +418,6 @@ ReportingETL::runETLPipeline(uint32_t startSequence, int numExtractors)
|
||||
|
||||
auto getNext = [&queues, &startSequence, &numExtractors](
|
||||
uint32_t sequence) -> std::shared_ptr<QueueType> {
|
||||
std::cout << std::to_string((sequence - startSequence) % numExtractors);
|
||||
return queues[(sequence - startSequence) % numExtractors];
|
||||
};
|
||||
std::vector<std::thread> extractors;
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <etl/ETLSource.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
|
||||
#include "org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h"
|
||||
#include <grpcpp/grpcpp.h>
|
||||
@@ -248,12 +248,6 @@ private:
|
||||
void
|
||||
publishLedger(ripple::LedgerInfo const& lgrInfo);
|
||||
|
||||
/// Get fees at a current ledger_index
|
||||
/// @param seq the ledger index
|
||||
/// @return nullopt if not found, fees if found.
|
||||
std::optional<ripple::Fees>
|
||||
getFees(std::uint32_t seq);
|
||||
|
||||
bool
|
||||
isStopping()
|
||||
{
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
|
||||
void
|
||||
addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
|
||||
{
|
||||
boost::json::object jDst;
|
||||
jDst["channel_id"] = ripple::to_string(line.key());
|
||||
jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount));
|
||||
jDst["destination_account"] =
|
||||
ripple::to_string(line.getAccountID(ripple::sfDestination));
|
||||
jDst["amount"] = line[ripple::sfAmount].getText();
|
||||
jDst["balance"] = line[ripple::sfBalance].getText();
|
||||
if (publicKeyType(line[ripple::sfPublicKey]))
|
||||
{
|
||||
ripple::PublicKey const pk(line[ripple::sfPublicKey]);
|
||||
jDst["public_key"] = toBase58(ripple::TokenType::AccountPublic, pk);
|
||||
jDst["public_key_hex"] = strHex(pk);
|
||||
}
|
||||
jDst["settle_delay"] = line[ripple::sfSettleDelay];
|
||||
if (auto const& v = line[~ripple::sfExpiration])
|
||||
jDst["expiration"] = *v;
|
||||
if (auto const& v = line[~ripple::sfCancelAfter])
|
||||
jDst["cancel_after"] = *v;
|
||||
if (auto const& v = line[~ripple::sfSourceTag])
|
||||
jDst["source_tag"] = *v;
|
||||
if (auto const& v = line[~ripple::sfDestinationTag])
|
||||
jDst["destination_tag"] = *v;
|
||||
|
||||
jsonLines.push_back(jDst);
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doAccountChannels(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
boost::optional<ripple::AccountID> destAccount;
|
||||
if (request.contains("destination_account"))
|
||||
{
|
||||
if (!request.at("destination_account").is_string())
|
||||
{
|
||||
response["error"] = "destination_account should be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
destAccount = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("destination_account").as_string().c_str());
|
||||
if (!destAccount)
|
||||
{
|
||||
response["error"] = "Invalid destination account";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if (!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if (!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() == 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
response["channels"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonChannels = response.at("channels").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltPAYCHAN &&
|
||||
sle.getAccountID(ripple::sfAccount) == accountID &&
|
||||
(!destAccount ||
|
||||
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addChannel(jsonChannels, sle);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
backend, accountID, *ledgerSequence, cursor, addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> types{
|
||||
{"state", ripple::ltRIPPLE_STATE},
|
||||
{"ticket", ripple::ltTICKET},
|
||||
{"signer_list", ripple::ltSIGNER_LIST},
|
||||
{"payment_channel", ripple::ltPAYCHAN},
|
||||
{"offer", ripple::ltOFFER},
|
||||
{"escrow", ripple::ltESCROW},
|
||||
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
|
||||
{"check", ripple::ltCHECK},
|
||||
};
|
||||
|
||||
boost::json::object
|
||||
doAccountObjects(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if (!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerEntryType> objectType = {};
|
||||
if (request.contains("type"))
|
||||
{
|
||||
if (!request.at("type").is_string())
|
||||
{
|
||||
response["error"] = "type must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
std::string typeAsString = request.at("type").as_string().c_str();
|
||||
if (types.find(typeAsString) == types.end())
|
||||
{
|
||||
response["error"] = "invalid object type";
|
||||
return response;
|
||||
}
|
||||
|
||||
objectType = types[typeAsString];
|
||||
}
|
||||
|
||||
response["objects"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonObjects = response.at("objects").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (!objectType || objectType == sle.getType())
|
||||
{
|
||||
jsonObjects.push_back(toJson(sle));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
backend, accountID, *ledgerSequence, cursor, addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
// {
|
||||
// account: account,
|
||||
// ledger_index_min: ledger_index // optional, defaults to earliest
|
||||
// ledger_index_max: ledger_index, // optional, defaults to latest
|
||||
// binary: boolean, // optional, defaults to false
|
||||
// forward: boolean, // optional, defaults to false
|
||||
// limit: integer, // optional
|
||||
// marker: object {ledger: ledger_index, seq: txn_sequence} // optional,
|
||||
// resume previous query
|
||||
// }
|
||||
boost::json::object
|
||||
doAccountTx(boost::json::object const& request, BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Please specify an account";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto account = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
if (!account)
|
||||
{
|
||||
account = ripple::AccountID();
|
||||
if (!account->parseHex(request.at("account").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "account malformed";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
|
||||
std::optional<Backend::AccountTransactionsCursor> cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
auto const& obj = request.at("cursor").as_object();
|
||||
std::optional<uint32_t> ledgerSequence;
|
||||
if (obj.contains("ledger_sequence"))
|
||||
{
|
||||
ledgerSequence = (uint32_t)obj.at("ledger_sequence").as_int64();
|
||||
}
|
||||
std::optional<uint32_t> transactionIndex;
|
||||
if (obj.contains("transaction_index"))
|
||||
{
|
||||
transactionIndex = (uint32_t)obj.at("transaction_index").as_int64();
|
||||
}
|
||||
if (!ledgerSequence || !transactionIndex)
|
||||
{
|
||||
response["error"] =
|
||||
"malformed cursor. include transaction_index and "
|
||||
"ledger_sequence in an object named \"cursor\"";
|
||||
return response;
|
||||
}
|
||||
cursor = {*ledgerSequence, *transactionIndex};
|
||||
}
|
||||
|
||||
uint32_t limit = 200;
|
||||
if (request.contains("limit") and
|
||||
request.at("limit").kind() == boost::json::kind::int64)
|
||||
limit = request.at("limit").as_int64();
|
||||
boost::json::array txns;
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto [blobs, retCursor] =
|
||||
backend.fetchAccountTransactions(*account, limit, cursor);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took "
|
||||
<< ((end - start).count() / 1000000000.0)
|
||||
<< " num blobs = " << blobs.size();
|
||||
for (auto const& txnPlusMeta : blobs)
|
||||
{
|
||||
if (txnPlusMeta.ledgerSequence > ledgerSequence)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__
|
||||
<< " skipping over transactions from incomplete ledger";
|
||||
continue;
|
||||
}
|
||||
boost::json::object obj;
|
||||
if (!binary)
|
||||
{
|
||||
auto [txn, meta] = deserializeTxPlusMeta(txnPlusMeta);
|
||||
obj["transaction"] = toJson(*txn);
|
||||
obj["metadata"] = toJson(*meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj["transaction"] = ripple::strHex(txnPlusMeta.transaction);
|
||||
obj["metadata"] = ripple::strHex(txnPlusMeta.metadata);
|
||||
}
|
||||
obj["ledger_sequence"] = txnPlusMeta.ledgerSequence;
|
||||
txns.push_back(obj);
|
||||
}
|
||||
response["transactions"] = txns;
|
||||
if (retCursor)
|
||||
{
|
||||
boost::json::object cursorJson;
|
||||
cursorJson["ledger_sequence"] = retCursor->ledgerSequence;
|
||||
cursorJson["transaction_index"] = retCursor->transactionIndex;
|
||||
response["cursor"] = cursorJson;
|
||||
}
|
||||
auto end2 = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took "
|
||||
<< ((end2 - end).count() / 1000000000.0);
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
|
||||
boost::json::object
|
||||
doBookOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
ripple::uint256 bookBase;
|
||||
if (request.contains("book"))
|
||||
{
|
||||
if (!bookBase.parseHex(request.at("book").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "Error parsing book";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!request.contains("taker_pays"))
|
||||
{
|
||||
response["error"] = "Missing field taker_pays";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.contains("taker_gets"))
|
||||
{
|
||||
response["error"] = "Missing field taker_gets";
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::json::object taker_pays;
|
||||
if (request.at("taker_pays").kind() == boost::json::kind::object)
|
||||
{
|
||||
taker_pays = request.at("taker_pays").as_object();
|
||||
}
|
||||
else
|
||||
{
|
||||
response["error"] = "Invalid field taker_pays";
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::json::object taker_gets;
|
||||
if (request.at("taker_gets").kind() == boost::json::kind::object)
|
||||
{
|
||||
taker_gets = request.at("taker_gets").as_object();
|
||||
}
|
||||
else
|
||||
{
|
||||
response["error"] = "Invalid field taker_gets";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!taker_pays.contains("currency"))
|
||||
{
|
||||
response["error"] = "Missing field taker_pays.currency";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!taker_pays.at("currency").is_string())
|
||||
{
|
||||
response["error"] = "taker_pays.currency should be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!taker_gets.contains("currency"))
|
||||
{
|
||||
response["error"] = "Missing field taker_gets.currency";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!taker_gets.at("currency").is_string())
|
||||
{
|
||||
response["error"] = "taker_gets.currency should be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::Currency pay_currency;
|
||||
|
||||
if (!ripple::to_currency(
|
||||
pay_currency, taker_pays.at("currency").as_string().c_str()))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_pays.currency', bad currency.";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::Currency get_currency;
|
||||
|
||||
if (!ripple::to_currency(
|
||||
get_currency, taker_gets["currency"].as_string().c_str()))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_gets.currency', bad currency.";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID pay_issuer;
|
||||
|
||||
if (taker_pays.contains("issuer"))
|
||||
{
|
||||
if (!taker_pays.at("issuer").is_string())
|
||||
{
|
||||
response["error"] = "taker_pays.issuer should be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
pay_issuer, taker_pays.at("issuer").as_string().c_str()))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_pays.issuer', bad issuer.";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (pay_issuer == ripple::noAccount())
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_pays.issuer', bad issuer account "
|
||||
"one.";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pay_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (isXRP(pay_currency) && !isXRP(pay_issuer))
|
||||
{
|
||||
response["error"] =
|
||||
"Unneeded field 'taker_pays.issuer' for XRP currency "
|
||||
"specification.";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!isXRP(pay_currency) && isXRP(pay_issuer))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_pays.issuer', expected non-XRP issuer.";
|
||||
return response;
|
||||
}
|
||||
|
||||
ripple::AccountID get_issuer;
|
||||
|
||||
if (taker_gets.contains("issuer"))
|
||||
{
|
||||
if (!taker_gets["issuer"].is_string())
|
||||
{
|
||||
response["error"] = "taker_gets.issuer should be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
get_issuer, taker_gets.at("issuer").as_string().c_str()))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_gets.issuer', bad issuer.";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (get_issuer == ripple::noAccount())
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_gets.issuer', bad issuer account "
|
||||
"one.";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
get_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
|
||||
{
|
||||
response["error"] =
|
||||
"Unneeded field 'taker_gets.issuer' for XRP currency "
|
||||
"specification.";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
|
||||
{
|
||||
response["error"] =
|
||||
"Invalid field 'taker_gets.issuer', expected non-XRP issuer.";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (pay_currency == get_currency && pay_issuer == get_issuer)
|
||||
{
|
||||
response["error"] = "Bad market";
|
||||
return response;
|
||||
}
|
||||
ripple::Book book = {
|
||||
{pay_currency, pay_issuer}, {get_currency, get_issuer}};
|
||||
|
||||
bookBase = getBookBase(book);
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit") and
|
||||
request.at("limit").kind() == boost::json::kind::int64)
|
||||
limit = request.at("limit").as_int64();
|
||||
|
||||
std::optional<ripple::AccountID> takerID = {};
|
||||
if (request.contains("taker"))
|
||||
{
|
||||
if (!request.at("taker").is_string())
|
||||
{
|
||||
response["error"] = "Taker account must be string";
|
||||
return response;
|
||||
}
|
||||
|
||||
takerID =
|
||||
accountFromStringStrict(request.at("taker").as_string().c_str());
|
||||
if (!takerID)
|
||||
{
|
||||
response["error"] = "Invalid taker account";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ripple::uint256> cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
cursor = ripple::uint256{};
|
||||
if (!cursor->parseHex(request.at("cursor").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "Bad cursor";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto [offers, retCursor, warning] =
|
||||
backend.fetchBookOffers(bookBase, *ledgerSequence, limit, cursor);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Time loading books: " << ((end - start).count() / 1000000000.0);
|
||||
|
||||
if (warning)
|
||||
response["warning"] = *warning;
|
||||
|
||||
response["offers"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonOffers = response.at("offers").as_array();
|
||||
|
||||
start = std::chrono::system_clock::now();
|
||||
for (auto const& obj : offers)
|
||||
{
|
||||
if (jsonOffers.size() == limit)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
ripple::SerialIter it{obj.blob.data(), obj.blob.size()};
|
||||
ripple::SLE offer{it, obj.key};
|
||||
ripple::uint256 bookDir =
|
||||
offer.getFieldH256(ripple::sfBookDirectory);
|
||||
|
||||
boost::json::object offerJson = toJson(offer);
|
||||
offerJson["quality"] =
|
||||
ripple::amountFromQuality(getQuality(bookDir)).getText();
|
||||
jsonOffers.push_back(offerJson);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Time transforming to json: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
if (retCursor)
|
||||
response["cursor"] = ripple::strHex(*retCursor);
|
||||
if (warning)
|
||||
response["warning"] =
|
||||
"Periodic database update in progress. Data for this book as of "
|
||||
"this ledger "
|
||||
"may be incomplete. Data should be complete within one minute";
|
||||
|
||||
return response;
|
||||
}
|
||||
71
src/handlers/Context.cpp
Normal file
71
src/handlers/Context.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <handlers/Context.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
std::optional<Context>
|
||||
make_WsContext(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<WsBase> const& session,
|
||||
Backend::LedgerRange const& range)
|
||||
{
|
||||
if (!request.contains("command"))
|
||||
return {};
|
||||
|
||||
std::string command = request.at("command").as_string().c_str();
|
||||
|
||||
return Context{
|
||||
command,
|
||||
1,
|
||||
request,
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
session,
|
||||
range
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<Context>
|
||||
make_HttpContext(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
Backend::LedgerRange const& range)
|
||||
{
|
||||
if (!request.contains("method") || !request.at("method").is_string())
|
||||
return {};
|
||||
|
||||
std::string const& command = request.at("method").as_string().c_str();
|
||||
|
||||
if (command == "subscribe" || command == "unsubscribe")
|
||||
return {};
|
||||
|
||||
if (!request.contains("params") || !request.at("params").is_array())
|
||||
return {};
|
||||
|
||||
boost::json::array const& array = request.at("params").as_array();
|
||||
|
||||
if (array.size() != 1)
|
||||
return {};
|
||||
|
||||
if (!array.at(0).is_object())
|
||||
return {};
|
||||
|
||||
return Context{
|
||||
command,
|
||||
1,
|
||||
array.at(0).as_object(),
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
nullptr,
|
||||
range
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
85
src/handlers/Context.h
Normal file
85
src/handlers/Context.h
Normal file
@@ -0,0 +1,85 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_CONTEXT_H_INCLUDED
|
||||
#define REPORTING_CONTEXT_H_INCLUDED
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
class WsBase;
|
||||
class SubscriptionManager;
|
||||
class ETLLoadBalancer;
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
struct Context
|
||||
{
|
||||
std::string method;
|
||||
std::uint32_t version;
|
||||
boost::json::object const& params;
|
||||
std::shared_ptr<BackendInterface> const& backend;
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions;
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer;
|
||||
std::shared_ptr<WsBase> session;
|
||||
Backend::LedgerRange const& range;
|
||||
|
||||
Context(
|
||||
std::string const& command_,
|
||||
std::uint32_t version_,
|
||||
boost::json::object const& params_,
|
||||
std::shared_ptr<BackendInterface> const& backend_,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions_,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer_,
|
||||
std::shared_ptr<WsBase> const& session_,
|
||||
Backend::LedgerRange const& range_)
|
||||
: method(command_)
|
||||
, version(version_)
|
||||
, params(params_)
|
||||
, backend(backend_)
|
||||
, subscriptions(subscriptions_)
|
||||
, balancer(balancer_)
|
||||
, session(session_)
|
||||
, range(range_)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
std::optional<Context>
|
||||
make_WsContext(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
std::shared_ptr<WsBase> const& session,
|
||||
Backend::LedgerRange const& range);
|
||||
|
||||
std::optional<Context>
|
||||
make_HttpContext(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> const& backend,
|
||||
std::shared_ptr<SubscriptionManager> const& subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> const& balancer,
|
||||
Backend::LedgerRange const& range);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif //REPORTING_CONTEXT_H_INCLUDED
|
||||
90
src/handlers/Handlers.cpp
Normal file
90
src/handlers/Handlers.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <handlers/Handlers.h>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <handlers/methods/Channel.h>
|
||||
#include <handlers/methods/Exchange.h>
|
||||
#include <handlers/methods/Ledger.h>
|
||||
#include <handlers/methods/Subscribe.h>
|
||||
#include <handlers/methods/Transaction.h>
|
||||
#include <etl/ETLSource.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
static std::unordered_map<std::string, std::function<Result(Context const&)>>
|
||||
handlerTable
|
||||
{
|
||||
{ "account_channels", &doAccountChannels },
|
||||
{ "account_currencies", &doAccountCurrencies },
|
||||
{ "account_info", &doAccountInfo },
|
||||
{ "account_lines", &doAccountLines },
|
||||
{ "account_objects", &doAccountObjects },
|
||||
{ "account_offers", &doAccountOffers },
|
||||
{ "account_tx", &doAccountTx },
|
||||
{ "book_offers", &doBookOffers },
|
||||
{ "channel_authorize", &doChannelAuthorize },
|
||||
{ "channel_verify", &doChannelVerify },
|
||||
{ "ledger", &doLedger },
|
||||
{ "ledger_data", &doLedgerData },
|
||||
{ "ledger_entry", &doLedgerEntry },
|
||||
{ "ledger_range", &doLedgerRange },
|
||||
{ "ledger_data", &doLedgerData },
|
||||
{ "subscribe", &doSubscribe },
|
||||
{ "unsubscribe", &doUnsubscribe },
|
||||
{ "tx", &doTx },
|
||||
};
|
||||
|
||||
static std::unordered_set<std::string> forwardCommands {
|
||||
"submit",
|
||||
"submit_multisigned",
|
||||
"fee",
|
||||
"path_find",
|
||||
"ripple_path_find",
|
||||
"manifest"
|
||||
};
|
||||
|
||||
bool
|
||||
shouldForwardToRippled(Context const& ctx)
|
||||
{
|
||||
auto request = ctx.params;
|
||||
|
||||
if (request.contains("forward") && request.at("forward").is_bool())
|
||||
return request.at("forward").as_bool();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "checked forward";
|
||||
|
||||
if (forwardCommands.find(ctx.method) != forwardCommands.end())
|
||||
return true;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "checked command";
|
||||
|
||||
if (request.contains("ledger_index"))
|
||||
{
|
||||
auto indexValue = request.at("ledger_index");
|
||||
if (indexValue.is_string())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "checking ledger as string";
|
||||
std::string index = indexValue.as_string().c_str();
|
||||
return index == "current" || index == "closed";
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "checked ledger";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Result
|
||||
buildResponse(Context const& ctx)
|
||||
{
|
||||
if (shouldForwardToRippled(ctx))
|
||||
return ctx.balancer->forwardToRippled(ctx.params);
|
||||
|
||||
if (handlerTable.find(ctx.method) == handlerTable.end())
|
||||
return Status{Error::rpcUNKNOWN_COMMAND};
|
||||
|
||||
auto method = handlerTable[ctx.method];
|
||||
|
||||
return method(ctx);
|
||||
}
|
||||
|
||||
}
|
||||
29
src/handlers/Handlers.h
Normal file
29
src/handlers/Handlers.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef RIPPLE_REPORTING_HANDLERS_H
|
||||
#define RIPPLE_REPORTING_HANDLERS_H
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
// Maximum and minimum supported API versions
|
||||
// Defaults to APIVersionIfUnspecified
|
||||
constexpr unsigned int APIVersionIfUnspecified = 1;
|
||||
constexpr unsigned int ApiMinimumSupportedVersion = 1;
|
||||
constexpr unsigned int ApiMaximumSupportedVersion = 1;
|
||||
constexpr unsigned int APINumberVersionSupported =
|
||||
ApiMaximumSupportedVersion - ApiMinimumSupportedVersion + 1;
|
||||
|
||||
static_assert(ApiMinimumSupportedVersion >= APIVersionIfUnspecified);
|
||||
static_assert(ApiMaximumSupportedVersion >= ApiMinimumSupportedVersion);
|
||||
|
||||
Result
|
||||
buildResponse(Context const& ctx);
|
||||
|
||||
} // namespace RPC
|
||||
#endif // RIPPLE_REPORTING_HANDLERS_H
|
||||
@@ -1,89 +0,0 @@
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
boost::json::object
|
||||
doLedger(boost::json::object const& request, BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
if (!request.contains("ledger_index"))
|
||||
{
|
||||
response["error"] = "Please specify a ledger index";
|
||||
return response;
|
||||
}
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
bool getTransactions = request.contains("transactions")
|
||||
? request.at("transactions").as_bool()
|
||||
: false;
|
||||
bool expand =
|
||||
request.contains("expand") ? request.at("expand").as_bool() : false;
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto lgrInfo = backend.fetchLedgerBySequence(*ledgerSequence);
|
||||
if (!lgrInfo)
|
||||
{
|
||||
response["error"] = "ledger not found";
|
||||
return response;
|
||||
}
|
||||
boost::json::object header;
|
||||
if (binary)
|
||||
{
|
||||
header["blob"] = ripple::strHex(ledgerInfoToBlob(*lgrInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
header = toJson(*lgrInfo);
|
||||
}
|
||||
response["header"] = header;
|
||||
if (getTransactions)
|
||||
{
|
||||
response["transactions"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonTransactions =
|
||||
response.at("transactions").as_array();
|
||||
if (expand)
|
||||
{
|
||||
auto txns = backend.fetchAllTransactionsInLedger(*ledgerSequence);
|
||||
|
||||
std::transform(
|
||||
std::move_iterator(txns.begin()),
|
||||
std::move_iterator(txns.end()),
|
||||
std::back_inserter(jsonTransactions),
|
||||
[binary](auto obj) {
|
||||
boost::json::object entry;
|
||||
if (!binary)
|
||||
{
|
||||
auto [sttx, meta] = deserializeTxPlusMeta(obj);
|
||||
entry["transaction"] = toJson(*sttx);
|
||||
entry["metadata"] = toJson(*meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry["transaction"] = ripple::strHex(obj.transaction);
|
||||
entry["metadata"] = ripple::strHex(obj.metadata);
|
||||
}
|
||||
entry["ledger_sequence"] = obj.ledgerSequence;
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hashes =
|
||||
backend.fetchAllTransactionHashesInLedger(*ledgerSequence);
|
||||
std::transform(
|
||||
std::move_iterator(hashes.begin()),
|
||||
std::move_iterator(hashes.end()),
|
||||
std::back_inserter(jsonTransactions),
|
||||
[](auto hash) {
|
||||
boost::json::object entry;
|
||||
entry["hash"] = ripple::strHex(hash);
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/ledger/LedgerToJson.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
// Get state nodes from a ledger
|
||||
// Inputs:
|
||||
// limit: integer, maximum number of entries
|
||||
// marker: opaque, resume point
|
||||
// binary: boolean, format
|
||||
// type: string // optional, defaults to all ledger node types
|
||||
// Outputs:
|
||||
// ledger_hash: chosen ledger's hash
|
||||
// ledger_index: chosen ledger's index
|
||||
// state: array of state nodes
|
||||
// marker: resume point, if any
|
||||
//
|
||||
//
|
||||
boost::json::object
|
||||
doLedgerData(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
auto ledger = backend.fetchLedgerBySequence(*ledgerSequence);
|
||||
if (!ledger)
|
||||
{
|
||||
response["error"] = "Ledger not found";
|
||||
return response;
|
||||
}
|
||||
|
||||
std::optional<ripple::uint256> cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing cursor";
|
||||
cursor = ripple::uint256{};
|
||||
if (!cursor->parseHex(request.at("cursor").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "Invalid cursor";
|
||||
response["request"] = request;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
size_t limit = request.contains("limit") ? request.at("limit").as_int64()
|
||||
: (binary ? 2048 : 256);
|
||||
Backend::LedgerPage page;
|
||||
auto start = std::chrono::system_clock::now();
|
||||
page = backend.fetchLedgerPage(cursor, *ledgerSequence, limit);
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
auto time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
boost::json::array objects;
|
||||
std::vector<Backend::LedgerObject>& results = page.objects;
|
||||
std::optional<ripple::uint256> const& returnedCursor = page.cursor;
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__ << " number of results = " << results.size();
|
||||
for (auto const& [key, object] : results)
|
||||
{
|
||||
ripple::STLedgerEntry sle{
|
||||
ripple::SerialIter{object.data(), object.size()}, key};
|
||||
if (binary)
|
||||
{
|
||||
boost::json::object entry;
|
||||
entry["data"] = ripple::serializeHex(sle);
|
||||
entry["index"] = ripple::to_string(sle.key());
|
||||
objects.push_back(entry);
|
||||
}
|
||||
else
|
||||
objects.push_back(toJson(sle));
|
||||
}
|
||||
response["objects"] = objects;
|
||||
if (returnedCursor)
|
||||
response["cursor"] = ripple::strHex(*returnedCursor);
|
||||
|
||||
response["num_results"] = results.size();
|
||||
response["db_time"] = time;
|
||||
response["time_per_result"] = time / (results.size() ? results.size() : 1);
|
||||
if (page.warning)
|
||||
{
|
||||
response["warning"] =
|
||||
"Periodic database update in progress. Data for this ledger may be "
|
||||
"incomplete. Data should be complete "
|
||||
"within a few minutes. Other RPC calls are not affected, "
|
||||
"regardless of ledger. This "
|
||||
"warning is only present on the first "
|
||||
"page of the ledger";
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
std::pair<org::xrpl::rpc::v1::GetLedgerDataResponse, grpc::Status>
|
||||
doLedgerDataGrpc(
|
||||
RPC::GRPCContext<org::xrpl::rpc::v1::GetLedgerDataRequest>& context)
|
||||
{
|
||||
org::xrpl::rpc::v1::GetLedgerDataRequest& request = context.params;
|
||||
org::xrpl::rpc::v1::GetLedgerDataResponse response;
|
||||
grpc::Status status = grpc::Status::OK;
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
if (RPC::ledgerFromRequest(ledger, context))
|
||||
{
|
||||
grpc::Status errorStatus{
|
||||
grpc::StatusCode::NOT_FOUND, "ledger not found"};
|
||||
return {response, errorStatus};
|
||||
}
|
||||
|
||||
ReadView::key_type key = ReadView::key_type();
|
||||
if (request.marker().size() != 0)
|
||||
{
|
||||
key = uint256::fromVoid(request.marker().data());
|
||||
if (key.size() != request.marker().size())
|
||||
{
|
||||
grpc::Status errorStatus{
|
||||
grpc::StatusCode::INVALID_ARGUMENT, "marker malformed"};
|
||||
return {response, errorStatus};
|
||||
}
|
||||
}
|
||||
|
||||
auto e = ledger->sles.end();
|
||||
ReadView::key_type stopKey = ReadView::key_type();
|
||||
if (request.end_marker().size() != 0)
|
||||
{
|
||||
stopKey = uint256::fromVoid(request.end_marker().data());
|
||||
if (stopKey.size() != request.marker().size())
|
||||
{
|
||||
grpc::Status errorStatus{
|
||||
grpc::StatusCode::INVALID_ARGUMENT, "end marker malformed"};
|
||||
return {response, errorStatus};
|
||||
}
|
||||
e = ledger->sles.upper_bound(stopKey);
|
||||
}
|
||||
|
||||
int maxLimit = RPC::Tuning::pageLength(true);
|
||||
|
||||
for (auto i = ledger->sles.upper_bound(key); i != e; ++i)
|
||||
{
|
||||
auto sle = ledger->read(keylet::unchecked((*i)->key()));
|
||||
if (maxLimit-- <= 0)
|
||||
{
|
||||
// Stop processing before the current key.
|
||||
auto k = sle->key();
|
||||
--k;
|
||||
response.set_marker(k.data(), k.size());
|
||||
break;
|
||||
}
|
||||
auto stateObject = response.mutable_ledger_objects()->add_objects();
|
||||
Serializer s;
|
||||
sle->add(s);
|
||||
stateObject->set_data(s.peekData().data(), s.getLength());
|
||||
stateObject->set_key(sle->key().data(), sle->key().size());
|
||||
}
|
||||
return {response, status};
|
||||
}
|
||||
*/
|
||||
@@ -1,55 +0,0 @@
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
// {
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// ...
|
||||
// }
|
||||
boost::json::object
|
||||
doLedgerEntry(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
ripple::uint256 key;
|
||||
if (!key.parseHex(request.at("index").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "Error parsing index";
|
||||
return response;
|
||||
}
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto dbResponse = backend.fetchLedgerObject(key, *ledgerSequence);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
auto time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
if (!dbResponse or dbResponse->size() == 0)
|
||||
{
|
||||
response["error"] = "Object not found";
|
||||
return response;
|
||||
}
|
||||
if (binary)
|
||||
{
|
||||
response["object"] = ripple::strHex(*dbResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
ripple::STLedgerEntry sle{
|
||||
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key};
|
||||
response["object"] = toJson(sle);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromStringStrict(std::string const& account)
|
||||
@@ -69,54 +70,38 @@ deserializeTxPlusMeta(
|
||||
boost::json::object
|
||||
toJson(ripple::STBase const& obj)
|
||||
{
|
||||
auto start = std::chrono::system_clock::now();
|
||||
boost::json::value value = boost::json::parse(
|
||||
obj.getJson(ripple::JsonOptions::none).toStyledString());
|
||||
auto end = std::chrono::system_clock::now();
|
||||
value.as_object()["deserialization_time_microseconds"] =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
return value.as_object();
|
||||
}
|
||||
|
||||
boost::json::value
|
||||
getJson(Json::Value const& value)
|
||||
{
|
||||
boost::json::value boostValue = boost::json::parse(value.toStyledString());
|
||||
|
||||
return boostValue;
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
toJson(ripple::TxMeta const& meta)
|
||||
{
|
||||
auto start = std::chrono::system_clock::now();
|
||||
boost::json::value value = boost::json::parse(
|
||||
meta.getJson(ripple::JsonOptions::none).toStyledString());
|
||||
auto end = std::chrono::system_clock::now();
|
||||
value.as_object()["deserialization_time_microseconds"] =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
return value.as_object();
|
||||
}
|
||||
|
||||
boost::json::value
|
||||
toBoostJson(Json::Value const& value)
|
||||
{
|
||||
boost::json::value boostValue =
|
||||
boost::json::parse(value.toStyledString());
|
||||
|
||||
return boostValue;
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
toJson(ripple::SLE const& sle)
|
||||
{
|
||||
auto start = std::chrono::system_clock::now();
|
||||
boost::json::value value = boost::json::parse(
|
||||
sle.getJson(ripple::JsonOptions::none).toStyledString());
|
||||
auto end = std::chrono::system_clock::now();
|
||||
value.as_object()["deserialization_time_microseconds"] =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
return value.as_object();
|
||||
}
|
||||
boost::json::value
|
||||
toBoostJson(RippledJson const& value)
|
||||
{
|
||||
return boost::json::parse(value.toStyledString());
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
toJson(ripple::LedgerInfo const& lgrInfo)
|
||||
@@ -138,20 +123,70 @@ toJson(ripple::LedgerInfo const& lgrInfo)
|
||||
return header;
|
||||
}
|
||||
|
||||
std::optional<uint32_t>
|
||||
ledgerSequenceFromRequest(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
std::variant<RPC::Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(RPC::Context const& ctx)
|
||||
{
|
||||
if (!request.contains("ledger_index") ||
|
||||
request.at("ledger_index").is_string())
|
||||
auto indexValue = ctx.params.contains("ledger_index")
|
||||
? ctx.params.at("ledger_index")
|
||||
: nullptr;
|
||||
|
||||
auto hashValue = ctx.params.contains("ledger_hash")
|
||||
? ctx.params.at("ledger_hash")
|
||||
: nullptr;
|
||||
|
||||
std::optional<ripple::LedgerInfo> lgrInfo;
|
||||
if (!hashValue.is_null())
|
||||
{
|
||||
return backend.fetchLatestLedgerSequence();
|
||||
if (!hashValue.is_string())
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerHashNotString"};
|
||||
|
||||
ripple::uint256 ledgerHash;
|
||||
if (!ledgerHash.parseHex(hashValue.as_string().c_str()))
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerHashMalformed"};
|
||||
|
||||
lgrInfo = ctx.backend->fetchLedgerByHash(ledgerHash);
|
||||
|
||||
}
|
||||
else if (!indexValue.is_null())
|
||||
{
|
||||
std::uint32_t ledgerSequence;
|
||||
if (indexValue.is_string() && indexValue.as_string() == "validated")
|
||||
ledgerSequence = ctx.range.maxSequence;
|
||||
else if (!indexValue.is_string() && indexValue.is_int64())
|
||||
ledgerSequence = indexValue.as_int64();
|
||||
else
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "ledgerIndexMalformed"};
|
||||
|
||||
lgrInfo =
|
||||
ctx.backend->fetchLedgerBySequence(ledgerSequence);
|
||||
}
|
||||
else
|
||||
{
|
||||
return request.at("ledger_index").as_int64();
|
||||
lgrInfo =
|
||||
ctx.backend->fetchLedgerBySequence(ctx.range.maxSequence);
|
||||
}
|
||||
|
||||
if (!lgrInfo)
|
||||
return RPC::Status{RPC::Error::rpcLGR_NOT_FOUND, "ledgerNotFound"};
|
||||
|
||||
return *lgrInfo;
|
||||
}
|
||||
|
||||
std::vector<unsigned char>
|
||||
ledgerInfoToBlob(ripple::LedgerInfo const& info)
|
||||
{
|
||||
ripple::Serializer s;
|
||||
s.add32(info.seq);
|
||||
s.add64(info.drops.drops());
|
||||
s.addBitString(info.parentHash);
|
||||
s.addBitString(info.txHash);
|
||||
s.addBitString(info.accountHash);
|
||||
s.add32(info.parentCloseTime.time_since_epoch().count());
|
||||
s.add32(info.closeTime.time_since_epoch().count());
|
||||
s.add8(info.closeTimeResolution.count());
|
||||
s.add8(info.closeFlags);
|
||||
// s.addBitString(info.hash);
|
||||
return s.peekData();
|
||||
}
|
||||
|
||||
std::optional<ripple::uint256>
|
||||
@@ -239,8 +274,8 @@ parseRippleLibSeed(boost::json::value const& value)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<ripple::PublicKey, ripple::SecretKey>
|
||||
keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
std::variant<RPC::Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
keypairFromRequst(boost::json::object const& request)
|
||||
{
|
||||
bool const has_key_type = request.contains("key_type");
|
||||
|
||||
@@ -262,17 +297,13 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
error = "missing field secret";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "missing field secret"};
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
error =
|
||||
"Exactly one of the following must be specified: "
|
||||
" passphrase, secret, seed, or seed_hex";
|
||||
return {};
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"Exactly one of the following must be specified: "
|
||||
" passphrase, secret, seed, or seed_hex"};
|
||||
}
|
||||
|
||||
boost::optional<ripple::KeyType> keyType;
|
||||
@@ -281,25 +312,18 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at("key_type").is_string())
|
||||
{
|
||||
error = "key_type must be string";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "keyTypeNotString"};
|
||||
|
||||
std::string key_type = request.at("key_type").as_string().c_str();
|
||||
keyType = ripple::keyTypeFromString(key_type);
|
||||
|
||||
if (!keyType)
|
||||
{
|
||||
error = "Invalid field key_type";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS, "invalidFieldKeyType"};
|
||||
|
||||
if (secretType == "secret")
|
||||
{
|
||||
error = "The secret field is not allowed if key_type is used.";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"The secret field is not allowed if key_type is used."};
|
||||
|
||||
}
|
||||
|
||||
// ripple-lib encodes seed used to generate an Ed25519 wallet in a
|
||||
@@ -313,12 +337,9 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
{
|
||||
// If the user passed in an Ed25519 seed but *explicitly*
|
||||
// requested another key type, return an error.
|
||||
if (keyType.value_or(ripple::KeyType::ed25519) !=
|
||||
ripple::KeyType::ed25519)
|
||||
{
|
||||
error = "Specified seed is for an Ed25519 wallet.";
|
||||
return {};
|
||||
}
|
||||
if (keyType.value_or(ripple::KeyType::ed25519) != ripple::KeyType::ed25519)
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"Specified seed is for an Ed25519 wallet."};
|
||||
|
||||
keyType = ripple::KeyType::ed25519;
|
||||
}
|
||||
@@ -332,10 +353,8 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
if (has_key_type)
|
||||
{
|
||||
if (!request.at(secretType).is_string())
|
||||
{
|
||||
error = "secret value must be string";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"secret value must be string"};
|
||||
|
||||
std::string key = request.at(secretType).as_string().c_str();
|
||||
|
||||
@@ -353,10 +372,8 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
else
|
||||
{
|
||||
if (!request.at("secret").is_string())
|
||||
{
|
||||
error = "field secret should be a string";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"field secret should be a string"};
|
||||
|
||||
std::string secret = request.at("secret").as_string().c_str();
|
||||
seed = ripple::parseGenericSeed(secret);
|
||||
@@ -364,17 +381,13 @@ keypairFromRequst(boost::json::object const& request, boost::json::value& error)
|
||||
}
|
||||
|
||||
if (!seed)
|
||||
{
|
||||
error = "Bad Seed: invalid field message secretType";
|
||||
return {};
|
||||
}
|
||||
return RPC::Status{RPC::Error::rpcBAD_SEED,
|
||||
"Bad Seed: invalid field message secretType"};
|
||||
|
||||
if (keyType != ripple::KeyType::secp256k1 &&
|
||||
keyType != ripple::KeyType::ed25519)
|
||||
{
|
||||
error = "keypairForSignature: invalid key type";
|
||||
return {};
|
||||
}
|
||||
if (keyType != ripple::KeyType::secp256k1
|
||||
&& keyType != ripple::KeyType::ed25519)
|
||||
return RPC::Status{RPC::Error::rpcINVALID_PARAMS,
|
||||
"keypairForSignature: invalid key type"};
|
||||
|
||||
return generateKeyPair(*keyType, *seed);
|
||||
}
|
||||
@@ -402,19 +415,161 @@ getAccountsFromTransaction(boost::json::object const& transaction)
|
||||
|
||||
return accounts;
|
||||
}
|
||||
std::vector<unsigned char>
|
||||
ledgerInfoToBlob(ripple::LedgerInfo const& info)
|
||||
|
||||
bool
|
||||
isGlobalFrozen(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& issuer)
|
||||
{
|
||||
ripple::Serializer s;
|
||||
s.add32(info.seq);
|
||||
s.add64(info.drops.drops());
|
||||
s.addBitString(info.parentHash);
|
||||
s.addBitString(info.txHash);
|
||||
s.addBitString(info.accountHash);
|
||||
s.add32(info.parentCloseTime.time_since_epoch().count());
|
||||
s.add32(info.closeTime.time_since_epoch().count());
|
||||
s.add8(info.closeTimeResolution.count());
|
||||
s.add8(info.closeFlags);
|
||||
s.addBitString(info.hash);
|
||||
return s.peekData();
|
||||
if (ripple::isXRP(issuer))
|
||||
return false;
|
||||
|
||||
auto key = ripple::keylet::account(issuer).key;
|
||||
auto blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (!blob)
|
||||
return false;
|
||||
|
||||
ripple::SerialIter it{blob->data(), blob->size()};
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
return sle.isFlag(ripple::lsfGlobalFreeze);
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& account,
|
||||
ripple::Currency const& currency,
|
||||
ripple::AccountID const& issuer)
|
||||
{
|
||||
if (ripple::isXRP(currency))
|
||||
return false;
|
||||
|
||||
auto key = ripple::keylet::account(issuer).key;
|
||||
auto blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (!blob)
|
||||
return false;
|
||||
|
||||
ripple::SerialIter it{blob->data(), blob->size()};
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
if (sle.isFlag(ripple::lsfGlobalFreeze))
|
||||
return true;
|
||||
|
||||
if (issuer != account)
|
||||
{
|
||||
key = ripple::keylet::line(account, issuer, currency).key;
|
||||
blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (!blob)
|
||||
return false;
|
||||
|
||||
ripple::SerialIter issuerIt{blob->data(), blob->size()};
|
||||
ripple::SLE issuerLine{it, key};
|
||||
|
||||
auto frozen =
|
||||
(issuer > account) ? ripple::lsfHighFreeze : ripple::lsfLowFreeze;
|
||||
|
||||
if (issuerLine.isFlag(frozen))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ripple::XRPAmount
|
||||
xrpLiquid(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& id)
|
||||
{
|
||||
auto key = ripple::keylet::account(id).key;
|
||||
auto blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (!blob)
|
||||
return beast::zero;
|
||||
|
||||
ripple::SerialIter it{blob->data(), blob->size()};
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
std::uint32_t const ownerCount = sle.getFieldU32(ripple::sfOwnerCount);
|
||||
|
||||
auto const reserve = backend.fetchFees(sequence)->accountReserve(ownerCount);
|
||||
|
||||
auto const balance = sle.getFieldAmount(ripple::sfBalance);
|
||||
|
||||
ripple::STAmount amount = balance - reserve;
|
||||
if (balance < reserve)
|
||||
amount.clear();
|
||||
|
||||
return amount.xrp();
|
||||
}
|
||||
|
||||
ripple::STAmount
|
||||
accountHolds(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& account,
|
||||
ripple::Currency const& currency,
|
||||
ripple::AccountID const& issuer)
|
||||
{
|
||||
ripple::STAmount amount;
|
||||
if (ripple::isXRP(currency))
|
||||
{
|
||||
return {xrpLiquid(backend, sequence, account)};
|
||||
}
|
||||
|
||||
auto key = ripple::keylet::line(account, issuer, currency).key;
|
||||
auto const blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (!blob)
|
||||
{
|
||||
amount.clear({currency, issuer});
|
||||
return amount;
|
||||
}
|
||||
|
||||
ripple::SerialIter it{blob->data(), blob->size()};
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
if (isFrozen(backend, sequence, account, currency, issuer))
|
||||
{
|
||||
amount.clear(ripple::Issue(currency, issuer));
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = sle.getFieldAmount(ripple::sfBalance);
|
||||
if (account > issuer)
|
||||
{
|
||||
// Put balance in account terms.
|
||||
amount.negate();
|
||||
}
|
||||
amount.setIssuer(issuer);
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
ripple::Rate
|
||||
transferRate(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& issuer)
|
||||
{
|
||||
auto key = ripple::keylet::account(issuer).key;
|
||||
auto blob = backend.fetchLedgerObject(key, sequence);
|
||||
|
||||
if (blob)
|
||||
{
|
||||
ripple::SerialIter it{blob->data(), blob->size()};
|
||||
ripple::SLE sle{it, key};
|
||||
|
||||
if (sle.isFieldPresent(ripple::sfTransferRate))
|
||||
return ripple::Rate{sle.getFieldU32(ripple::sfTransferRate)};
|
||||
}
|
||||
|
||||
return ripple::parityRate;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
#define XRPL_REPORTING_RPCHELPERS_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Context.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
@@ -38,10 +43,8 @@ using RippledJson = Json::Value;
|
||||
boost::json::value
|
||||
toBoostJson(RippledJson const& value);
|
||||
|
||||
std::optional<uint32_t>
|
||||
ledgerSequenceFromRequest(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
std::variant<RPC::Status, ripple::LedgerInfo>
|
||||
ledgerInfoFromRequest(RPC::Context const& ctx);
|
||||
|
||||
std::optional<ripple::uint256>
|
||||
traverseOwnedNodes(
|
||||
@@ -51,10 +54,8 @@ traverseOwnedNodes(
|
||||
ripple::uint256 const& cursor,
|
||||
std::function<bool(ripple::SLE)> atOwnedNode);
|
||||
|
||||
std::pair<ripple::PublicKey, ripple::SecretKey>
|
||||
keypairFromRequst(
|
||||
boost::json::object const& request,
|
||||
boost::json::value& error);
|
||||
std::variant<RPC::Status, std::pair<ripple::PublicKey, ripple::SecretKey>>
|
||||
keypairFromRequst(boost::json::object const& request);
|
||||
|
||||
std::vector<ripple::AccountID>
|
||||
getAccountsFromTransaction(boost::json::object const& transaction);
|
||||
@@ -62,4 +63,38 @@ getAccountsFromTransaction(boost::json::object const& transaction);
|
||||
std::vector<unsigned char>
|
||||
ledgerInfoToBlob(ripple::LedgerInfo const& info);
|
||||
|
||||
bool
|
||||
isGlobalFrozen(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t seq,
|
||||
ripple::AccountID const& issuer);
|
||||
|
||||
bool
|
||||
isFrozen(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& account,
|
||||
ripple::Currency const& currency,
|
||||
ripple::AccountID const& issuer);
|
||||
|
||||
ripple::STAmount
|
||||
accountHolds(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& account,
|
||||
ripple::Currency const& currency,
|
||||
ripple::AccountID const& issuer);
|
||||
|
||||
ripple::Rate
|
||||
transferRate(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& issuer);
|
||||
|
||||
ripple::XRPAmount
|
||||
xrpLiquid(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& id);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
boost::json::object
|
||||
doServerInfo(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto rng = backend.fetchLedgerRange();
|
||||
if (!rng)
|
||||
{
|
||||
response["complete_ledgers"] = "empty";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string completeLedgers = std::to_string(rng->minSequence);
|
||||
if (rng->maxSequence != rng->minSequence)
|
||||
completeLedgers += "-" + std::to_string(rng->maxSequence);
|
||||
response["complete_ledgers"] = completeLedgers;
|
||||
}
|
||||
if (rng)
|
||||
{
|
||||
auto lgrInfo = backend.fetchLedgerBySequence(rng->maxSequence);
|
||||
response["validated_ledger"] = toJson(*lgrInfo);
|
||||
}
|
||||
|
||||
boost::json::array indexes;
|
||||
|
||||
if (rng)
|
||||
{
|
||||
uint32_t cur = rng->minSequence;
|
||||
while (cur <= rng->maxSequence + 1)
|
||||
{
|
||||
auto keyIndex = backend.getKeyIndexOfSeq(cur);
|
||||
assert(keyIndex.has_value());
|
||||
cur = keyIndex->keyIndex;
|
||||
boost::json::object entry;
|
||||
entry["complete"] = backend.isLedgerIndexed(cur);
|
||||
entry["sequence"] = cur;
|
||||
indexes.emplace_back(entry);
|
||||
cur = cur + 1;
|
||||
}
|
||||
}
|
||||
response["indexes"] = indexes;
|
||||
auto indexing = backend.getIndexer().getCurrentlyIndexing();
|
||||
if (indexing)
|
||||
response["indexing"] = *indexing;
|
||||
else
|
||||
response["indexing"] = "none";
|
||||
|
||||
return response;
|
||||
}
|
||||
62
src/handlers/Status.cpp
Normal file
62
src/handlers/Status.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 <handlers/Status.h>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
void
|
||||
inject_error(Error err, boost::json::object& json)
|
||||
{
|
||||
ripple::RPC::ErrorInfo const& info(ripple::RPC::get_error_info(err));
|
||||
json["error"] = info.token;
|
||||
json["error_code"] = static_cast<std::uint32_t>(err);
|
||||
json["error_message"] = info.message;
|
||||
json["status"] = "error";
|
||||
json["type"] = "response";
|
||||
}
|
||||
|
||||
void
|
||||
inject_error(Error err, std::string const& message, boost::json::object& json)
|
||||
{
|
||||
ripple::RPC::ErrorInfo const& info(ripple::RPC::get_error_info(err));
|
||||
json["error"] = info.token;
|
||||
json["error_code"] = static_cast<std::uint32_t>(err);
|
||||
json["error_message"] = message;
|
||||
json["status"] = "error";
|
||||
json["type"] = "response";
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
make_error(Error err)
|
||||
{
|
||||
boost::json::object json{};
|
||||
inject_error(err, json);
|
||||
return json;
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
make_error(Error err, std::string const& message)
|
||||
{
|
||||
boost::json::object json{};
|
||||
inject_error(err, message, json);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
72
src/handlers/Status.h
Normal file
72
src/handlers/Status.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 RPC_ERRORCODES_H_INCLUDED
|
||||
#define RPC_ERRORCODES_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <boost/json.hpp>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
using Error = ripple::error_code_i;
|
||||
|
||||
struct Status
|
||||
{
|
||||
Error error = Error::rpcSUCCESS;
|
||||
std::string message = "";
|
||||
|
||||
Status() {};
|
||||
|
||||
Status(Error error_) : error(error_) {};
|
||||
|
||||
Status(Error error_, std::string message_)
|
||||
: error(error_)
|
||||
, message(message_)
|
||||
{}
|
||||
|
||||
/** Returns true if the Status is *not* OK. */
|
||||
operator bool() const
|
||||
{
|
||||
return error != Error::rpcSUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
static Status OK;
|
||||
|
||||
using Result = std::variant<Status, boost::json::object>;
|
||||
|
||||
void
|
||||
inject_error(Error err, boost::json::object& json);
|
||||
|
||||
void
|
||||
inject_error(Error err, std::string const& message, boost::json::object& json);
|
||||
|
||||
boost::json::object
|
||||
make_error(Error err);
|
||||
|
||||
boost::json::object
|
||||
make_error(Error err, std::string const& message);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // RPC_ERRORCODES_H_INCLUDED
|
||||
51
src/handlers/methods/Account.h
Normal file
51
src/handlers/methods/Account.h
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_ACCOUNT_HANDLER_H_INCLUDED
|
||||
#define REPORTING_ACCOUNT_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doAccountInfo(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountChannels(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountCurrencies(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountLines(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountObjects(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountOffers(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_ACCOUNT_HANDLER_H_INCLUDED
|
||||
38
src/handlers/methods/Channel.h
Normal file
38
src/handlers/methods/Channel.h
Normal file
@@ -0,0 +1,38 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_CHANNEL_HANDLER_H_INCLUDED
|
||||
#define REPORTING_CHANNEL_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doChannelAuthorize(Context const& context);
|
||||
|
||||
Result
|
||||
doChannelVerify(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_CHANNEL_HANDLER_H_INCLUDED
|
||||
36
src/handlers/methods/Exchange.h
Normal file
36
src/handlers/methods/Exchange.h
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_EXCHANGE_HANDLER_H_INCLUDED
|
||||
#define REPORTING_EXCHANGE_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doBookOffers(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_CHANNEL_HANDLER_H_INCLUDED
|
||||
45
src/handlers/methods/Ledger.h
Normal file
45
src/handlers/methods/Ledger.h
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_LEDGER_HANDLER_H_INCLUDED
|
||||
#define REPORTING_LEDGER_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doLedger(Context const& context);
|
||||
|
||||
Result
|
||||
doLedgerEntry(Context const& context);
|
||||
|
||||
Result
|
||||
doLedgerData(Context const& context);
|
||||
|
||||
Result
|
||||
doLedgerRange(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_LEDGER_HANDLER_H_INCLUDED
|
||||
39
src/handlers/methods/Subscribe.h
Normal file
39
src/handlers/methods/Subscribe.h
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_SUBSCRIBE_HANDLER_H_INCLUDED
|
||||
#define REPORTING_SUBSCRIBE_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doSubscribe(Context const& context);
|
||||
|
||||
Result
|
||||
doUnsubscribe(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_SUBSCRIBE_HANDLER_H_INCLUDED
|
||||
39
src/handlers/methods/Transaction.h
Normal file
39
src/handlers/methods/Transaction.h
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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 REPORTING_TRANSACTION_HANDLER_H_INCLUDED
|
||||
#define REPORTING_TRANSACTION_HANDLER_H_INCLUDED
|
||||
|
||||
#include <handlers/Context.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doTx(Context const& context);
|
||||
|
||||
Result
|
||||
doAccountTx(Context const& context);
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
#endif // REPORTING_TRANSACTION_HANDLER_H_INCLUDED
|
||||
141
src/handlers/methods/impl/AccountChannels.cpp
Normal file
141
src/handlers/methods/impl/AccountChannels.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
void
|
||||
addChannel(boost::json::array& jsonLines, ripple::SLE const& line)
|
||||
{
|
||||
boost::json::object jDst;
|
||||
jDst["channel_id"] = ripple::to_string(line.key());
|
||||
jDst["account"] = ripple::to_string(line.getAccountID(ripple::sfAccount));
|
||||
jDst["destination_account"] = ripple::to_string(line.getAccountID(ripple::sfDestination));
|
||||
jDst["amount"] = line[ripple::sfAmount].getText();
|
||||
jDst["balance"] = line[ripple::sfBalance].getText();
|
||||
if (publicKeyType(line[ripple::sfPublicKey]))
|
||||
{
|
||||
ripple::PublicKey const pk(line[ripple::sfPublicKey]);
|
||||
jDst["public_key"] = toBase58(ripple::TokenType::AccountPublic, pk);
|
||||
jDst["public_key_hex"] = strHex(pk);
|
||||
}
|
||||
jDst["settle_delay"] = line[ripple::sfSettleDelay];
|
||||
if (auto const& v = line[~ripple::sfExpiration])
|
||||
jDst["expiration"] = *v;
|
||||
if (auto const& v = line[~ripple::sfCancelAfter])
|
||||
jDst["cancel_after"] = *v;
|
||||
if (auto const& v = line[~ripple::sfSourceTag])
|
||||
jDst["source_tag"] = *v;
|
||||
if (auto const& v = line[~ripple::sfDestinationTag])
|
||||
jDst["destination_tag"] = *v;
|
||||
|
||||
jsonLines.push_back(jDst);
|
||||
}
|
||||
|
||||
Result
|
||||
doAccountChannels(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
std::optional<ripple::AccountID> destAccount = {};
|
||||
if (request.contains("destination_account"))
|
||||
{
|
||||
if (!request.at("destination_account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "destinationNotString"};
|
||||
|
||||
destAccount = accountFromStringStrict(
|
||||
request.at("destination_account").as_string().c_str());
|
||||
|
||||
if (!destAccount)
|
||||
return Status{Error::rpcINVALID_PARAMS, "destinationMalformed"};
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["channels"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonChannels = response.at("channels").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltPAYCHAN &&
|
||||
sle.getAccountID(ripple::sfAccount) == *accountID &&
|
||||
(!destAccount ||
|
||||
*destAccount == sle.getAccountID(ripple::sfDestination)))
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addChannel(jsonChannels, sle);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
if (nextCursor)
|
||||
response["marker"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -6,53 +6,45 @@
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
boost::json::object
|
||||
doAccountCurrencies(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
namespace RPC
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
Result
|
||||
doAccountCurrencies(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
accountID = *parsed;
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
std::set<std::string> send, receive;
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (sle.getType() == ripple::ltRIPPLE_STATE)
|
||||
{
|
||||
ripple::STAmount const& balance =
|
||||
sle.getFieldAmount(ripple::sfBalance);
|
||||
sle.getFieldAmount(ripple::sfBalance);
|
||||
|
||||
auto lowLimit = sle.getFieldAmount(ripple::sfLowLimit);
|
||||
auto highLimit = sle.getFieldAmount(ripple::sfHighLimit);
|
||||
@@ -65,12 +57,25 @@ doAccountCurrencies(
|
||||
if ((-balance) < lineLimitPeer)
|
||||
send.insert(ripple::to_string(balance.getCurrency()));
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
traverseOwnedNodes(
|
||||
backend, accountID, *ledgerSequence, beast::zero, addToResponse);
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
beast::zero,
|
||||
addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
response["receive_currencies"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonReceive = response.at("receive_currencies").as_array();
|
||||
|
||||
for (auto const& currency : receive)
|
||||
jsonReceive.push_back(currency.c_str());
|
||||
|
||||
response["send_currencies"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonSend = response.at("send_currencies").as_array();
|
||||
@@ -78,13 +83,7 @@ doAccountCurrencies(
|
||||
for (auto const& currency : send)
|
||||
jsonSend.push_back(currency.c_str());
|
||||
|
||||
response["receive_currencies"] =
|
||||
boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonReceive =
|
||||
response.at("receive_currencies").as_array();
|
||||
|
||||
for (auto const& currency : receive)
|
||||
jsonReceive.push_back(currency.c_str());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
@@ -38,78 +39,63 @@
|
||||
// // error.
|
||||
// }
|
||||
|
||||
// TODO(tom): what is that "default"?
|
||||
boost::json::object
|
||||
doAccountInfo(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
namespace RPC
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
Result
|
||||
doAccountInfo(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
std::string strIdent;
|
||||
if (request.contains("account"))
|
||||
strIdent = request.at("account").as_string().c_str();
|
||||
else if (request.contains("ident"))
|
||||
strIdent = request.at("ident").as_string().c_str();
|
||||
else
|
||||
{
|
||||
response["error"] = "missing account field";
|
||||
return response;
|
||||
}
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcACT_MALFORMED};
|
||||
|
||||
// bool bStrict = request.contains("strict") &&
|
||||
// params.at("strict").as_bool();
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
// Get info on account.
|
||||
std::optional<ripple::AccountID> accountID =
|
||||
accountFromStringStrict(strIdent);
|
||||
auto accountID = accountFromStringStrict(strIdent);
|
||||
|
||||
if (!accountID)
|
||||
{
|
||||
accountID = ripple::AccountID();
|
||||
if (!accountID->parseHex(request.at("account").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "account malformed";
|
||||
return response;
|
||||
}
|
||||
}
|
||||
auto key = ripple::keylet::account(accountID.value());
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
std::optional<std::vector<unsigned char>> dbResponse =
|
||||
backend.fetchLedgerObject(key.key, *ledgerSequence);
|
||||
context.backend->fetchLedgerObject(key.key, lgrInfo.seq);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
auto time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
if (!dbResponse)
|
||||
{
|
||||
response["error"] = "no response from db";
|
||||
return Status{Error::rpcACT_NOT_FOUND};
|
||||
}
|
||||
|
||||
ripple::STLedgerEntry sle{
|
||||
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key.key};
|
||||
|
||||
if (!key.check(sle))
|
||||
{
|
||||
response["error"] = "error fetching record from db";
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
response["success"] = "fetched successfully!";
|
||||
if (!binary)
|
||||
response["object"] = toJson(sle);
|
||||
else
|
||||
response["object"] = ripple::strHex(*dbResponse);
|
||||
response["db_time"] = time;
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcDB_DESERIALIZATION};
|
||||
|
||||
// if (!binary)
|
||||
// response["account_data"] = getJson(sle);
|
||||
// else
|
||||
// response["account_data"] = ripple::strHex(*dbResponse);
|
||||
// response["db_time"] = time;
|
||||
|
||||
response["account_data"] = toJson(sle);
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
// Return SignerList(s) if that is requested.
|
||||
/*
|
||||
@@ -124,7 +110,7 @@ doAccountInfo(
|
||||
// support multiple SignerLists on one account.
|
||||
auto const sleSigners = ledger->read(keylet::signers(accountID));
|
||||
if (sleSigners)
|
||||
jvSignerList.append(sleSigners->toJson(JsonOptions::none));
|
||||
jvSignerList.append(sleSigners->getJson(JsonOptions::none));
|
||||
|
||||
result[jss::account_data][jss::signer_lists] =
|
||||
std::move(jvSignerList);
|
||||
@@ -134,3 +120,4 @@ doAccountInfo(
|
||||
return response;
|
||||
}
|
||||
|
||||
} //namespace RPC
|
||||
@@ -1,21 +1,27 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
void
|
||||
addLine(
|
||||
boost::json::array& jsonLines,
|
||||
ripple::SLE const& line,
|
||||
ripple::AccountID const& account,
|
||||
boost::optional<ripple::AccountID> const& peerAccount)
|
||||
std::optional<ripple::AccountID> const& peerAccount)
|
||||
{
|
||||
auto flags = line.getFieldU32(ripple::sfFlags);
|
||||
auto lowLimit = line.getFieldAmount(ripple::sfLowLimit);
|
||||
@@ -38,19 +44,16 @@ addLine(
|
||||
if (peerAccount and peerAccount != lineAccountIDPeer)
|
||||
return;
|
||||
|
||||
bool lineAuth =
|
||||
flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||
bool lineAuthPeer =
|
||||
flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||
bool lineNoRipple =
|
||||
flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||
if (!viewLowest)
|
||||
balance.negate();
|
||||
|
||||
bool lineAuth = flags & (viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||
bool lineAuthPeer = flags & (!viewLowest ? ripple::lsfLowAuth : ripple::lsfHighAuth);
|
||||
bool lineNoRipple = flags & (viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||
bool lineDefaultRipple = flags & ripple::lsfDefaultRipple;
|
||||
bool lineNoRipplePeer = flags &
|
||||
(!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||
bool lineFreeze =
|
||||
flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||
bool lineFreezePeer =
|
||||
flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||
bool lineNoRipplePeer = flags & (!viewLowest ? ripple::lsfLowNoRipple : ripple::lsfHighNoRipple);
|
||||
bool lineFreeze = flags & (viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||
bool lineFreezePeer = flags & (!viewLowest ? ripple::lsfLowFreeze : ripple::lsfHighFreeze);
|
||||
|
||||
ripple::STAmount const& saBalance(balance);
|
||||
ripple::STAmount const& saLimit(lineLimit);
|
||||
@@ -80,98 +83,67 @@ addLine(
|
||||
jsonLines.push_back(jPeer);
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doAccountLines(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
Result
|
||||
doAccountLines(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
boost::optional<ripple::AccountID> peerAccount;
|
||||
std::optional<ripple::AccountID> peerAccount;
|
||||
if (request.contains("peer"))
|
||||
{
|
||||
if (!request.at("peer").is_string())
|
||||
{
|
||||
response["error"] = "peer should be a string";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "peerNotString"};
|
||||
|
||||
peerAccount = ripple::parseBase58<ripple::AccountID>(
|
||||
peerAccount = accountFromStringStrict(
|
||||
request.at("peer").as_string().c_str());
|
||||
|
||||
if (!peerAccount)
|
||||
{
|
||||
response["error"] = "Invalid peer account";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "peerMalformed"};
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if (!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if (!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
response["lines"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonLines = response.at("lines").as_array();
|
||||
|
||||
@@ -182,18 +154,25 @@ doAccountLines(
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
addLine(jsonLines, sle, accountID, peerAccount);
|
||||
|
||||
addLine(jsonLines, sle, *accountID, peerAccount);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
backend, accountID, *ledgerSequence, cursor, addToResponse);
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
response["marker"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
123
src/handlers/methods/impl/AccountObjects.cpp
Normal file
123
src/handlers/methods/impl/AccountObjects.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> types {
|
||||
{"state", ripple::ltRIPPLE_STATE},
|
||||
{"ticket", ripple::ltTICKET},
|
||||
{"signer_list", ripple::ltSIGNER_LIST},
|
||||
{"payment_channel", ripple::ltPAYCHAN},
|
||||
{"offer", ripple::ltOFFER},
|
||||
{"escrow", ripple::ltESCROW},
|
||||
{"deposit_preauth", ripple::ltDEPOSIT_PREAUTH},
|
||||
{"check", ripple::ltCHECK},
|
||||
};
|
||||
|
||||
Result
|
||||
doAccountObjects(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
std::optional<ripple::LedgerEntryType> objectType = {};
|
||||
if (request.contains("type"))
|
||||
{
|
||||
if(!request.at("type").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "typeNotString"};
|
||||
|
||||
std::string typeAsString = request.at("type").as_string().c_str();
|
||||
if(types.find(typeAsString) == types.end())
|
||||
return Status{Error::rpcINVALID_PARAMS, "typeInvalid"};
|
||||
|
||||
objectType = types[typeAsString];
|
||||
}
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["account_objects"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonObjects = response.at("objects").as_array();
|
||||
|
||||
auto const addToResponse = [&](ripple::SLE const& sle) {
|
||||
if (!objectType || objectType == sle.getType())
|
||||
{
|
||||
if (limit-- == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonObjects.push_back(toJson(sle));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
if (nextCursor)
|
||||
response["marker"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/paths/RippleState.h>
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
@@ -7,8 +7,14 @@
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <handlers/methods/Account.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
void
|
||||
addOffer(boost::json::array& offersJson, ripple::SLE const& offer)
|
||||
@@ -18,7 +24,7 @@ addOffer(boost::json::array& offersJson, ripple::SLE const& offer)
|
||||
|
||||
ripple::STAmount takerPays = offer.getFieldAmount(ripple::sfTakerPays);
|
||||
ripple::STAmount takerGets = offer.getFieldAmount(ripple::sfTakerGets);
|
||||
|
||||
|
||||
boost::json::object obj;
|
||||
|
||||
if (!takerPays.native())
|
||||
@@ -54,83 +60,59 @@ addOffer(boost::json::array& offersJson, ripple::SLE const& offer)
|
||||
obj["quality"] = rate.getText();
|
||||
if (offer.isFieldPresent(ripple::sfExpiration))
|
||||
obj["expiration"] = offer.getFieldU32(ripple::sfExpiration);
|
||||
|
||||
|
||||
offersJson.push_back(obj);
|
||||
};
|
||||
boost::json::object
|
||||
doAccountOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
|
||||
Result
|
||||
doAccountOffers(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
auto ledgerSequence = ledgerSequenceFromRequest(request, backend);
|
||||
if (!ledgerSequence)
|
||||
{
|
||||
response["error"] = "Empty database";
|
||||
return response;
|
||||
}
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
if (!request.contains("account"))
|
||||
{
|
||||
response["error"] = "Must contain account";
|
||||
return response;
|
||||
}
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
if (!request.at("account").is_string())
|
||||
{
|
||||
response["error"] = "Account must be a string";
|
||||
return response;
|
||||
}
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
ripple::AccountID accountID;
|
||||
auto parsed = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account").as_string().c_str());
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!parsed)
|
||||
{
|
||||
response["error"] = "Invalid account";
|
||||
return response;
|
||||
}
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
accountID = *parsed;
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if (!request.at("limit").is_int64())
|
||||
{
|
||||
response["error"] = "limit must be integer";
|
||||
return response;
|
||||
}
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
{
|
||||
response["error"] = "limit must be positive";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
ripple::uint256 cursor;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if (!request.at("cursor").is_string())
|
||||
{
|
||||
response["error"] = "limit must be string";
|
||||
return response;
|
||||
}
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
auto bytes = ripple::strUnHex(request.at("cursor").as_string().c_str());
|
||||
if (bytes and bytes->size() != 32)
|
||||
{
|
||||
response["error"] = "invalid cursor";
|
||||
return response;
|
||||
}
|
||||
|
||||
cursor = ripple::uint256::fromVoid(bytes->data());
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
response["offers"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonLines = response.at("offers").as_array();
|
||||
|
||||
@@ -141,18 +123,25 @@ doAccountOffers(
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
addOffer(jsonLines, sle);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto nextCursor = traverseOwnedNodes(
|
||||
backend, accountID, *ledgerSequence, cursor, addToResponse);
|
||||
|
||||
auto nextCursor =
|
||||
traverseOwnedNodes(
|
||||
*context.backend,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
cursor,
|
||||
addToResponse);
|
||||
|
||||
if (nextCursor)
|
||||
response["next_cursor"] = ripple::strHex(*nextCursor);
|
||||
response["marker"] = ripple::strHex(*nextCursor);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
182
src/handlers/methods/impl/AccountTx.cpp
Normal file
182
src/handlers/methods/impl/AccountTx.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <handlers/RPCHelpers.h>
|
||||
#include <handlers/methods/Transaction.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doAccountTx(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if(!request.contains("account"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAccount"};
|
||||
|
||||
if(!request.at("account").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
auto accountID =
|
||||
accountFromStringStrict(request.at("account").as_string().c_str());
|
||||
|
||||
if (!accountID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
|
||||
bool binary = false;
|
||||
if(request.contains("binary"))
|
||||
{
|
||||
if(!request.at("binary").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
|
||||
|
||||
binary = request.at("binary").as_bool();
|
||||
}
|
||||
|
||||
auto minIndex = context.range.minSequence;
|
||||
if (request.contains("ledger_index_min"))
|
||||
{
|
||||
if (!request.at("ledger_index_min").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
|
||||
|
||||
minIndex = value_to<std::uint32_t>(request.at("ledger_index_min"));
|
||||
}
|
||||
|
||||
std::optional<Backend::AccountTransactionsCursor> cursor;
|
||||
cursor = {context.range.maxSequence, 0};
|
||||
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
auto const& obj = request.at("cursor").as_object();
|
||||
|
||||
std::optional<std::uint32_t> transactionIndex = {};
|
||||
if (obj.contains("seq"))
|
||||
{
|
||||
if (!obj.at("seq").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
|
||||
|
||||
transactionIndex = value_to<std::uint32_t>(obj.at("seq"));
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> ledgerIndex = {};
|
||||
if (obj.contains("ledger"))
|
||||
{
|
||||
if (!obj.at("ledger").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
|
||||
|
||||
transactionIndex = value_to<std::uint32_t>(obj.at("ledger"));
|
||||
}
|
||||
|
||||
if (!transactionIndex || !ledgerIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
|
||||
|
||||
cursor = {*ledgerIndex, *transactionIndex};
|
||||
}
|
||||
else if (request.contains("ledger_index_max"))
|
||||
{
|
||||
if (!request.at("ledger_index_max").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
|
||||
|
||||
auto maxIndex = value_to<std::uint32_t>(request.at("ledger_index_max"));
|
||||
|
||||
if (minIndex > maxIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidIndex"};
|
||||
|
||||
cursor = {maxIndex, 0};
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
|
||||
response["limit"] = limit;
|
||||
}
|
||||
|
||||
|
||||
boost::json::array txns;
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto [blobs, retCursor] =
|
||||
context.backend->fetchAccountTransactions(*accountID, limit, cursor);
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took " << ((end - start).count() / 1000000000.0) << " num blobs = " << blobs.size();
|
||||
|
||||
response["account"] = ripple::to_string(*accountID);
|
||||
response["ledger_index_min"] = minIndex;
|
||||
response["ledger_index_max"] = cursor->ledgerSequence;
|
||||
|
||||
if (retCursor)
|
||||
{
|
||||
boost::json::object cursorJson;
|
||||
cursorJson["ledger"] = retCursor->ledgerSequence;
|
||||
cursorJson["seq"] = retCursor->transactionIndex;
|
||||
response["marker"] = cursorJson;
|
||||
}
|
||||
|
||||
for (auto const& txnPlusMeta : blobs)
|
||||
{
|
||||
if (txnPlusMeta.ledgerSequence < minIndex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__
|
||||
<< " skipping over transactions from incomplete ledger";
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::json::object obj;
|
||||
|
||||
if (!binary)
|
||||
{
|
||||
auto [txn, meta] = deserializeTxPlusMeta(txnPlusMeta);
|
||||
obj["meta"] = toJson(*meta);
|
||||
obj["tx"] = toJson(*txn);
|
||||
obj["tx"].as_object()["ledger_index"] = txnPlusMeta.ledgerSequence;
|
||||
obj["tx"].as_object()["inLedger"] = txnPlusMeta.ledgerSequence;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
obj["meta"] = ripple::strHex(txnPlusMeta.metadata);
|
||||
obj["tx_blob"] = ripple::strHex(txnPlusMeta.transaction);
|
||||
obj["ledger_index"] = txnPlusMeta.ledgerSequence;
|
||||
}
|
||||
|
||||
obj["validated"] = true;
|
||||
|
||||
txns.push_back(obj);
|
||||
}
|
||||
|
||||
response["transactions"] = txns;
|
||||
|
||||
auto end2 = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took " << ((end2 - end).count() / 1000000000.0);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
325
src/handlers/methods/impl/BookOffers.cpp
Normal file
325
src/handlers/methods/impl/BookOffers.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <handlers/methods/Exchange.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doBookOffers(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
ripple::Book book;
|
||||
ripple::uint256 bookBase;
|
||||
if (request.contains("book"))
|
||||
{
|
||||
if (!request.at("book").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "bookNotString"};
|
||||
|
||||
if (!bookBase.parseHex(request.at("book").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidBook"};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!request.contains("taker_pays"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPays"};
|
||||
|
||||
if (!request.contains("taker_gets"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGets"};
|
||||
|
||||
if (!request.at("taker_pays").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysNotObject"};
|
||||
|
||||
if (!request.at("taker_gets").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsNotObject"};
|
||||
|
||||
auto taker_pays = request.at("taker_pays").as_object();
|
||||
if (!taker_pays.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerPaysCurrency"};
|
||||
|
||||
if (!taker_pays.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysCurrencyNotString"};
|
||||
|
||||
auto taker_gets = request.at("taker_gets").as_object();
|
||||
if (!taker_gets.contains("currency"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTakerGetsCurrency"};
|
||||
|
||||
if (!taker_gets.at("currency").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerGetsCurrencyNotString"};
|
||||
|
||||
ripple::Currency pay_currency;
|
||||
if (!ripple::to_currency(
|
||||
pay_currency, taker_pays.at("currency").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysCurrency"};
|
||||
|
||||
ripple::Currency get_currency;
|
||||
if (!ripple::to_currency(
|
||||
get_currency, taker_gets["currency"].as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerGetsCurrency"};
|
||||
|
||||
ripple::AccountID pay_issuer;
|
||||
if (taker_pays.contains("issuer"))
|
||||
{
|
||||
if (!taker_pays.at("issuer").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerPaysIssuerNotString"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
pay_issuer, taker_pays.at("issuer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysIssuer"};
|
||||
|
||||
if (pay_issuer == ripple::noAccount())
|
||||
return Status{Error::rpcINVALID_PARAMS, "badTakerPaysIssuerAccountOne"};
|
||||
}
|
||||
else
|
||||
{
|
||||
pay_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (isXRP(pay_currency) && !isXRP(pay_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_pays.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!isXRP(pay_currency) && isXRP(pay_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_pays.issuer', expected non-XRP "
|
||||
"issuer."};
|
||||
|
||||
ripple::AccountID get_issuer;
|
||||
|
||||
if (taker_gets.contains("issuer"))
|
||||
{
|
||||
if (!taker_gets["issuer"].is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"taker_gets.issuer should be string"};
|
||||
|
||||
if (!ripple::to_issuer(
|
||||
get_issuer, taker_gets.at("issuer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer."};
|
||||
|
||||
if (get_issuer == ripple::noAccount())
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', bad issuer account "
|
||||
"one."};
|
||||
}
|
||||
else
|
||||
{
|
||||
get_issuer = ripple::xrpAccount();
|
||||
}
|
||||
|
||||
if (ripple::isXRP(get_currency) && !ripple::isXRP(get_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Unneeded field 'taker_gets.issuer' for XRP currency "
|
||||
"specification."};
|
||||
|
||||
if (!ripple::isXRP(get_currency) && ripple::isXRP(get_issuer))
|
||||
return Status{Error::rpcINVALID_PARAMS,
|
||||
"Invalid field 'taker_gets.issuer', expected non-XRP issuer."};
|
||||
|
||||
if (pay_currency == get_currency && pay_issuer == get_issuer)
|
||||
return Status{Error::rpcINVALID_PARAMS, "badMarket"};
|
||||
|
||||
book = {{pay_currency, pay_issuer}, {get_currency, get_issuer}};
|
||||
bookBase = getBookBase(book);
|
||||
}
|
||||
|
||||
std::uint32_t limit = 200;
|
||||
if (request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInt"};
|
||||
|
||||
limit = request.at("limit").as_int64();
|
||||
if (limit <= 0)
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotPositive"};
|
||||
}
|
||||
|
||||
|
||||
std::optional<ripple::AccountID> takerID = {};
|
||||
if (request.contains("taker"))
|
||||
{
|
||||
if (!request.at("taker").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "takerNotString"};
|
||||
|
||||
takerID =
|
||||
accountFromStringStrict(request.at("taker").as_string().c_str());
|
||||
|
||||
if (!takerID)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidTakerAccount"};
|
||||
}
|
||||
|
||||
ripple::uint256 cursor = beast::zero;
|
||||
if (request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
if (!cursor.parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
}
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto [offers, retCursor, warning] =
|
||||
context.backend->fetchBookOffers(bookBase, lgrInfo.seq, limit, cursor);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Time loading books: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
response["offers"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonOffers = response.at("offers").as_array();
|
||||
|
||||
std::map<ripple::AccountID, ripple::STAmount> umBalance;
|
||||
|
||||
bool globalFreeze =
|
||||
isGlobalFrozen(*context.backend, lgrInfo.seq, book.out.account) ||
|
||||
isGlobalFrozen(*context.backend, lgrInfo.seq, book.out.account);
|
||||
|
||||
auto rate = transferRate(*context.backend, lgrInfo.seq, book.out.account);
|
||||
|
||||
start = std::chrono::system_clock::now();
|
||||
for (auto const& obj : offers)
|
||||
{
|
||||
if (jsonOffers.size() == limit)
|
||||
break;
|
||||
|
||||
try
|
||||
{
|
||||
ripple::SerialIter it{obj.blob.data(), obj.blob.size()};
|
||||
ripple::SLE offer{it, obj.key};
|
||||
ripple::uint256 bookDir = offer.getFieldH256(ripple::sfBookDirectory);
|
||||
|
||||
auto const uOfferOwnerID = offer.getAccountID(ripple::sfAccount);
|
||||
auto const& saTakerGets = offer.getFieldAmount(ripple::sfTakerGets);
|
||||
auto const& saTakerPays = offer.getFieldAmount(ripple::sfTakerPays);
|
||||
ripple::STAmount saOwnerFunds;
|
||||
bool firstOwnerOffer = true;
|
||||
|
||||
if (book.out.account == uOfferOwnerID)
|
||||
{
|
||||
// If an offer is selling issuer's own IOUs, it is fully
|
||||
// funded.
|
||||
saOwnerFunds = saTakerGets;
|
||||
}
|
||||
else if (globalFreeze)
|
||||
{
|
||||
// If either asset is globally frozen, consider all offers
|
||||
// that aren't ours to be totally unfunded
|
||||
saOwnerFunds.clear(book.out);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto umBalanceEntry = umBalance.find(uOfferOwnerID);
|
||||
if (umBalanceEntry != umBalance.end())
|
||||
{
|
||||
// Found in running balance table.
|
||||
|
||||
saOwnerFunds = umBalanceEntry->second;
|
||||
firstOwnerOffer = false;
|
||||
}
|
||||
else {
|
||||
saOwnerFunds = accountHolds(
|
||||
*context.backend,
|
||||
lgrInfo.seq,
|
||||
uOfferOwnerID,
|
||||
book.out.currency,
|
||||
book.out.account);
|
||||
|
||||
if (saOwnerFunds < beast::zero)
|
||||
saOwnerFunds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
boost::json::object offerJson = toJson(offer);
|
||||
|
||||
ripple::STAmount saTakerGetsFunded;
|
||||
ripple::STAmount saOwnerFundsLimit = saOwnerFunds;
|
||||
ripple::Rate offerRate = ripple::parityRate;
|
||||
ripple::STAmount dirRate =
|
||||
ripple::amountFromQuality(getQuality(bookDir));
|
||||
|
||||
if (rate != ripple::parityRate
|
||||
// Have a tranfer fee.
|
||||
&& takerID != book.out.account
|
||||
// Not taking offers of own IOUs.
|
||||
&& book.out.account != uOfferOwnerID)
|
||||
// Offer owner not issuing ownfunds
|
||||
{
|
||||
// Need to charge a transfer fee to offer owner.
|
||||
offerRate = rate;
|
||||
saOwnerFundsLimit = ripple::divide(saOwnerFunds, offerRate);
|
||||
}
|
||||
|
||||
if (saOwnerFundsLimit >= saTakerGets)
|
||||
{
|
||||
// Sufficient funds no shenanigans.
|
||||
saTakerGetsFunded = saTakerGets;
|
||||
}
|
||||
else
|
||||
{
|
||||
saTakerGetsFunded = saOwnerFundsLimit;
|
||||
offerJson["taker_gets_funded"] = saTakerGetsFunded.getText();
|
||||
offerJson["taker_pays_funded"] = toBoostJson(std::min(
|
||||
saTakerPays,
|
||||
ripple::multiply(
|
||||
saTakerGetsFunded, dirRate, saTakerPays.issue()))
|
||||
.getJson(ripple::JsonOptions::none));
|
||||
}
|
||||
|
||||
ripple::STAmount saOwnerPays = (ripple::parityRate == offerRate)
|
||||
? saTakerGetsFunded
|
||||
: std::min(
|
||||
saOwnerFunds,
|
||||
ripple::multiply(saTakerGetsFunded, offerRate));
|
||||
|
||||
umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays;
|
||||
|
||||
if (firstOwnerOffer)
|
||||
offerJson["owner_funds"] = saOwnerFunds.getText();
|
||||
|
||||
offerJson["quality"] = dirRate.getText();
|
||||
|
||||
jsonOffers.push_back(offerJson);
|
||||
}
|
||||
catch (std::exception const& e) {}
|
||||
}
|
||||
|
||||
end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Time transforming to json: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
|
||||
if (retCursor)
|
||||
response["marker"] = ripple::strHex(*retCursor);
|
||||
if (warning)
|
||||
response["warning"] =
|
||||
"Periodic database update in progress. Data for this book as of "
|
||||
"this ledger "
|
||||
"may be incomplete. Data should be complete within one minute";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -23,9 +23,13 @@
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <handlers/methods/Channel.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <optional>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
void
|
||||
serializePayChanAuthorization(
|
||||
ripple::Serializer& msg,
|
||||
@@ -37,57 +41,42 @@ serializePayChanAuthorization(
|
||||
msg.add64(amt.drops());
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doChannelAuthorize(boost::json::object const& request)
|
||||
Result
|
||||
doChannelAuthorize(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if(!request.contains("channel_id"))
|
||||
{
|
||||
response["error"] = "missing field channel_id";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"};
|
||||
|
||||
if(!request.at("channel_id").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
|
||||
|
||||
if(!request.contains("amount"))
|
||||
{
|
||||
response["error"] = "missing field amount";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAmount"};
|
||||
|
||||
if(!request.at("amount").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "amountNotString"};
|
||||
|
||||
if (!request.contains("key_type") && !request.contains("secret"))
|
||||
{
|
||||
response["error"] = "missing field secret";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingKeyTypeOrSecret"};
|
||||
|
||||
boost::json::value error = nullptr;
|
||||
auto const [pk, sk] = keypairFromRequst(request, error);
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
auto v = keypairFromRequst(request);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto const [pk, sk] = std::get<std::pair<ripple::PublicKey, ripple::SecretKey>>(v);
|
||||
|
||||
ripple::uint256 channelId;
|
||||
if (!channelId.parseHex(request.at("channel_id").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "channel id malformed";
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!request.at("amount").is_string())
|
||||
{
|
||||
response["error"] = "channel amount malformed";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"};
|
||||
|
||||
auto optDrops =
|
||||
ripple::to_uint64(request.at("amount").as_string().c_str());
|
||||
|
||||
if (!optDrops)
|
||||
{
|
||||
response["error"] = "could not parse channel amount";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
|
||||
|
||||
std::uint64_t drops = *optDrops;
|
||||
|
||||
@@ -101,9 +90,10 @@ doChannelAuthorize(boost::json::object const& request)
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
response["error"] = "Exception occurred during signing.";
|
||||
return response;
|
||||
return Status{Error::rpcINTERNAL};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
} // namesace RPC
|
||||
@@ -23,36 +23,42 @@
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <handlers/methods/Channel.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <optional>
|
||||
|
||||
boost::json::object
|
||||
doChannelVerify(boost::json::object const& request)
|
||||
namespace RPC
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
Result
|
||||
doChannelVerify(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if(!request.contains("channel_id"))
|
||||
{
|
||||
response["error"] = "missing field channel_id";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingChannelID"};
|
||||
|
||||
if(!request.at("channel_id").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "channelIDNotString"};
|
||||
|
||||
if(!request.contains("amount"))
|
||||
{
|
||||
response["error"] = "missing field amount";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingAmount"};
|
||||
|
||||
if(!request.at("amount").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "amountNotString"};
|
||||
|
||||
if (!request.contains("signature"))
|
||||
{
|
||||
response["error"] = "missing field signature";
|
||||
return response;
|
||||
}
|
||||
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingSignature"};
|
||||
|
||||
if(!request.at("signature").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "signatureNotString"};
|
||||
|
||||
if (!request.contains("public_key"))
|
||||
{
|
||||
response["error"] = "missing field public_key";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingPublicKey"};
|
||||
|
||||
if(!request.at("public_key").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "publicKeyNotString"};
|
||||
|
||||
boost::optional<ripple::PublicKey> pk;
|
||||
{
|
||||
@@ -63,15 +69,11 @@ doChannelVerify(boost::json::object const& request)
|
||||
{
|
||||
auto pkHex = ripple::strUnHex(strPk);
|
||||
if (!pkHex)
|
||||
{
|
||||
response["error"] = "malformed public key";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcPUBLIC_MALFORMED, "malformedPublicKey"};
|
||||
|
||||
auto const pkType = ripple::publicKeyType(ripple::makeSlice(*pkHex));
|
||||
if (!pkType)
|
||||
{
|
||||
response["error"] = "invalid key type";
|
||||
}
|
||||
return Status{Error::rpcPUBLIC_MALFORMED, "invalidKeyType"};
|
||||
|
||||
pk.emplace(ripple::makeSlice(*pkHex));
|
||||
}
|
||||
@@ -79,35 +81,20 @@ doChannelVerify(boost::json::object const& request)
|
||||
|
||||
ripple::uint256 channelId;
|
||||
if (!channelId.parseHex(request.at("channel_id").as_string().c_str()))
|
||||
{
|
||||
response["error"] = "channel id malformed";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcCHANNEL_MALFORMED, "malformedChannelID"};
|
||||
|
||||
auto optDrops =
|
||||
ripple::to_uint64(request.at("amount").as_string().c_str());
|
||||
|
||||
if (!optDrops)
|
||||
{
|
||||
response["error"] = "could not parse channel amount";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcCHANNEL_AMT_MALFORMED, "couldNotParseAmount"};
|
||||
|
||||
std::uint64_t drops = *optDrops;
|
||||
|
||||
if (!request.at("signature").is_string())
|
||||
{
|
||||
response["error"] = "signature must be type string";
|
||||
return response;
|
||||
}
|
||||
|
||||
auto sig = ripple::strUnHex(request.at("signature").as_string().c_str());
|
||||
|
||||
if (!sig || !sig->size())
|
||||
{
|
||||
response["error"] = "Invalid signature";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidSignature"};
|
||||
|
||||
ripple::Serializer msg;
|
||||
ripple::serializePayChanAuthorization(msg, channelId, ripple::XRPAmount(drops));
|
||||
@@ -116,4 +103,6 @@ doChannelVerify(boost::json::object const& request)
|
||||
ripple::verify(*pk, msg.slice(), ripple::makeSlice(*sig), true);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
123
src/handlers/methods/impl/Ledger.cpp
Normal file
123
src/handlers/methods/impl/Ledger.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <handlers/methods/Ledger.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doLedger(Context const& context)
|
||||
{
|
||||
auto params = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
bool binary = false;
|
||||
if(params.contains("binary"))
|
||||
{
|
||||
if(!params.at("binary").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
|
||||
|
||||
binary = params.at("binary").as_bool();
|
||||
}
|
||||
|
||||
bool transactions = false;
|
||||
if(params.contains("transactions"))
|
||||
{
|
||||
if(!params.at("transactions").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "transactionsFlagNotBool"};
|
||||
|
||||
transactions = params.at("transactions").as_bool();
|
||||
}
|
||||
|
||||
bool expand = false;
|
||||
if(params.contains("expand"))
|
||||
{
|
||||
if(!params.at("expand").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "expandFlagNotBool"};
|
||||
|
||||
expand = params.at("expand").as_bool();
|
||||
}
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
boost::json::object header;
|
||||
if (binary)
|
||||
{
|
||||
header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
header["accepted"] = true;
|
||||
header["account_hash"] = ripple::strHex(lgrInfo.accountHash);
|
||||
header["close_flags"] = lgrInfo.closeFlags;
|
||||
header["close_time"] = lgrInfo.closeTime.time_since_epoch().count();
|
||||
header["close_time_human"] = ripple::to_string(lgrInfo.closeTime);;
|
||||
header["close_time_resolution"] = lgrInfo.closeTimeResolution.count();
|
||||
header["closed"] = true;
|
||||
header["hash"] = ripple::strHex(lgrInfo.hash);
|
||||
header["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
header["ledger_index"] = std::to_string(lgrInfo.seq);
|
||||
header["parent_close_time"] =
|
||||
lgrInfo.parentCloseTime.time_since_epoch().count();
|
||||
header["parent_hash"] = ripple::strHex(lgrInfo.parentHash);
|
||||
header["seqNum"] = std::to_string(lgrInfo.seq);
|
||||
header["totalCoins"] = ripple::to_string(lgrInfo.drops);
|
||||
header["total_coins"] = ripple::to_string(lgrInfo.drops);
|
||||
header["transaction_hash"] = ripple::strHex(lgrInfo.txHash);
|
||||
}
|
||||
|
||||
if (transactions)
|
||||
{
|
||||
header["transactions"] = boost::json::value(boost::json::array_kind);
|
||||
boost::json::array& jsonTxs = header.at("transactions").as_array();
|
||||
if (expand)
|
||||
{
|
||||
auto txns = context.backend->fetchAllTransactionsInLedger(lgrInfo.seq);
|
||||
|
||||
std::transform(
|
||||
std::move_iterator(txns.begin()),
|
||||
std::move_iterator(txns.end()),
|
||||
std::back_inserter(jsonTxs),
|
||||
[binary](auto obj) {
|
||||
boost::json::object entry;
|
||||
if (!binary)
|
||||
{
|
||||
auto [sttx, meta] = deserializeTxPlusMeta(obj);
|
||||
entry = toJson(*sttx);
|
||||
entry["metaData"] = toJson(*meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry["tx_blob"] = ripple::strHex(obj.transaction);
|
||||
entry["meta"] = ripple::strHex(obj.metadata);
|
||||
}
|
||||
entry["ledger_sequence"] = obj.ledgerSequence;
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hashes =
|
||||
context.backend->fetchAllTransactionHashesInLedger(lgrInfo.seq);
|
||||
std::transform(
|
||||
std::move_iterator(hashes.begin()),
|
||||
std::move_iterator(hashes.end()),
|
||||
std::back_inserter(jsonTxs),
|
||||
[](auto hash) {
|
||||
boost::json::object entry;
|
||||
return boost::json::string(ripple::strHex(hash));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
response["ledger"] = header;
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
170
src/handlers/methods/impl/LedgerData.cpp
Normal file
170
src/handlers/methods/impl/LedgerData.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/ledger/LedgerToJson.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/methods/Ledger.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
// Get state nodes from a ledger
|
||||
// Inputs:
|
||||
// limit: integer, maximum number of entries
|
||||
// marker: opaque, resume point
|
||||
// binary: boolean, format
|
||||
// type: string // optional, defaults to all ledger node types
|
||||
// Outputs:
|
||||
// ledger_hash: chosen ledger's hash
|
||||
// ledger_index: chosen ledger's index
|
||||
// state: array of state nodes
|
||||
// marker: resume point, if any
|
||||
//
|
||||
//
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doLedgerData(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
bool binary = false;
|
||||
if(request.contains("binary"))
|
||||
{
|
||||
if(!request.at("binary").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
|
||||
|
||||
binary = request.at("binary").as_bool();
|
||||
}
|
||||
|
||||
std::size_t limit = binary ? 2048 : 256;
|
||||
if(request.contains("limit"))
|
||||
{
|
||||
if(!request.at("limit").is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "limitNotInteger"};
|
||||
|
||||
limit = value_to<int>(request.at("limit"));
|
||||
}
|
||||
|
||||
std::optional<ripple::uint256> cursor;
|
||||
if(request.contains("cursor"))
|
||||
{
|
||||
if(!request.at("cursor").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorNotString"};
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << __func__ << " : parsing cursor";
|
||||
|
||||
cursor = ripple::uint256{};
|
||||
if(!cursor->parseHex(request.at("cursor").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "cursorMalformed"};
|
||||
}
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
Backend::LedgerPage page;
|
||||
auto start = std::chrono::system_clock::now();
|
||||
page = context.backend->fetchLedgerPage(cursor, lgrInfo.seq, limit);
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
auto time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
boost::json::object header;
|
||||
if(!cursor)
|
||||
{
|
||||
if (binary)
|
||||
{
|
||||
header["ledger_data"] = ripple::strHex(ledgerInfoToBlob(lgrInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
header["accepted"] = true;
|
||||
header["account_hash"] = ripple::strHex(lgrInfo.accountHash);
|
||||
header["close_flags"] = lgrInfo.closeFlags;
|
||||
header["close_time"] = lgrInfo.closeTime.time_since_epoch().count();
|
||||
header["close_time_human"] = ripple::to_string(lgrInfo.closeTime);;
|
||||
header["close_time_resolution"] = lgrInfo.closeTimeResolution.count();
|
||||
header["closed"] = true;
|
||||
header["hash"] = ripple::strHex(lgrInfo.hash);
|
||||
header["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
header["ledger_index"] = std::to_string(lgrInfo.seq);
|
||||
header["parent_close_time"] =
|
||||
lgrInfo.parentCloseTime.time_since_epoch().count();
|
||||
header["parent_hash"] = ripple::strHex(lgrInfo.parentHash);
|
||||
header["seqNum"] = std::to_string(lgrInfo.seq);
|
||||
header["totalCoins"] = ripple::to_string(lgrInfo.drops);
|
||||
header["total_coins"] = ripple::to_string(lgrInfo.drops);
|
||||
header["transaction_hash"] = ripple::strHex(lgrInfo.txHash);
|
||||
|
||||
response["ledger"] = header;
|
||||
}
|
||||
}
|
||||
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
boost::json::array objects;
|
||||
std::vector<Backend::LedgerObject>& results = page.objects;
|
||||
std::optional<ripple::uint256> const& returnedCursor = page.cursor;
|
||||
|
||||
if(returnedCursor)
|
||||
response["marker"] = ripple::strHex(*returnedCursor);
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__ << " number of results = " << results.size();
|
||||
for (auto const& [key, object] : results)
|
||||
{
|
||||
ripple::STLedgerEntry sle{
|
||||
ripple::SerialIter{object.data(), object.size()}, key};
|
||||
if (binary)
|
||||
{
|
||||
boost::json::object entry;
|
||||
entry["data"] = ripple::serializeHex(sle);
|
||||
entry["index"] = ripple::to_string(sle.key());
|
||||
objects.push_back(entry);
|
||||
}
|
||||
else
|
||||
objects.push_back(toJson(sle));
|
||||
}
|
||||
response["state"] = objects;
|
||||
|
||||
|
||||
if (cursor && page.warning)
|
||||
{
|
||||
response["warning"] =
|
||||
"Periodic database update in progress. Data for this ledger may be "
|
||||
"incomplete. Data should be complete "
|
||||
"within a few minutes. Other RPC calls are not affected, "
|
||||
"regardless of ledger. This "
|
||||
"warning is only present on the first "
|
||||
"page of the ledger";
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
360
src/handlers/methods/impl/LedgerEntry.cpp
Normal file
360
src/handlers/methods/impl/LedgerEntry.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/STLedgerEntry.h>
|
||||
#include <boost/json.hpp>
|
||||
#include <handlers/methods/Ledger.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
// {
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// ...
|
||||
// }
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
Result
|
||||
doLedgerEntry(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
auto lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
|
||||
ripple::uint256 key;
|
||||
if (request.contains("index"))
|
||||
{
|
||||
if(!request.at("index").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "indexNotString"};
|
||||
|
||||
if (!key.parseHex(request.at("index").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedIndex"};
|
||||
}
|
||||
else if (request.contains("account_root"))
|
||||
{
|
||||
if(!request.at("account_root").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "account_rootNotString"};
|
||||
|
||||
auto const account = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("account_root").as_string().c_str());
|
||||
if (!account || account->isZero())
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"};
|
||||
else
|
||||
key = ripple::keylet::account(*account).key;
|
||||
}
|
||||
else if (request.contains("check"))
|
||||
{
|
||||
if(!request.at("check").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "checkNotString"};
|
||||
|
||||
if (!key.parseHex(request.at("check").as_string().c_str()))
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "checkMalformed"};
|
||||
}
|
||||
}
|
||||
else if (request.contains("deposit_preauth"))
|
||||
{
|
||||
if (!request.at("deposit_preauth").is_object())
|
||||
{
|
||||
if (!request.at("deposit_preauth").is_string() ||
|
||||
!key.parseHex(
|
||||
request.at("deposit_preauth").as_string().c_str()))
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "deposit_preauthMalformed"};
|
||||
}
|
||||
}
|
||||
else if (
|
||||
!request.at("deposit_preauth").as_object().contains("owner") ||
|
||||
!request.at("deposit_preauth").as_object().at("owner").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "ownerNotString"};
|
||||
}
|
||||
else if(
|
||||
!request.at("deposit_preauth").as_object().contains("authorized") ||
|
||||
!request.at("deposit_preauth").as_object().at("authorized").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "authorizedNotString"};
|
||||
}
|
||||
else
|
||||
{
|
||||
boost::json::object const& deposit_preauth =
|
||||
request.at("deposit_preauth").as_object();
|
||||
|
||||
auto const owner = ripple::parseBase58<ripple::AccountID>(
|
||||
deposit_preauth.at("owner").as_string().c_str());
|
||||
|
||||
auto const authorized = ripple::parseBase58<ripple::AccountID>(
|
||||
deposit_preauth.at("authorized").as_string().c_str());
|
||||
|
||||
if (!owner)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"};
|
||||
else if (!authorized)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAuthorized"};
|
||||
else
|
||||
key = ripple::keylet::depositPreauth(*owner, *authorized).key;
|
||||
}
|
||||
}
|
||||
else if (request.contains("directory"))
|
||||
{
|
||||
if (!request.at("directory").is_object())
|
||||
{
|
||||
if(!request.at("directory").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "directoryNotString"};
|
||||
|
||||
if (!key.parseHex(request.at("directory").as_string().c_str()))
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedDirectory"};
|
||||
}
|
||||
}
|
||||
else if (
|
||||
request.at("directory").as_object().contains("sub_index") &&
|
||||
!request.at("directory").as_object().at("sub_index").is_int64())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "sub_indexNotInt"};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto directory = request.at("directory").as_object();
|
||||
std::uint64_t subIndex = directory.contains("sub_index")
|
||||
? value_to<std::uint64_t>(directory.at("sub_index"))
|
||||
: 0;
|
||||
|
||||
if (directory.contains("dir_root"))
|
||||
{
|
||||
ripple::uint256 uDirRoot;
|
||||
|
||||
if (directory.contains("owner"))
|
||||
{
|
||||
// May not specify both dir_root and owner.
|
||||
return Status{Error::rpcINVALID_PARAMS, "mayNotSpecifyBothDirRootAndOwner"};
|
||||
}
|
||||
else if (!uDirRoot.parseHex(
|
||||
directory.at("dir_root").as_string().c_str()))
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedDirRoot"};
|
||||
}
|
||||
else
|
||||
{
|
||||
key = ripple::keylet::page(uDirRoot, subIndex).key;
|
||||
}
|
||||
}
|
||||
else if (directory.contains("owner"))
|
||||
{
|
||||
auto const ownerID = ripple::parseBase58<ripple::AccountID>(
|
||||
directory.at("owner").as_string().c_str());
|
||||
|
||||
if (!ownerID)
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAddress"};
|
||||
}
|
||||
else
|
||||
{
|
||||
key =
|
||||
ripple::keylet::page(
|
||||
ripple::keylet::ownerDir(*ownerID), subIndex).key;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingOwnerOrDirRoot"};
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.contains("escrow"))
|
||||
{
|
||||
if (!request.at("escrow").is_object())
|
||||
{
|
||||
if (!key.parseHex(request.at("escrow").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedEscrow"};
|
||||
}
|
||||
else if (
|
||||
!request.at("escrow").as_object().contains("owner") ||
|
||||
!request.at("escrow").as_object().at("owner").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"};
|
||||
}
|
||||
else if(
|
||||
!request.at("escrow").as_object().contains("seq") ||
|
||||
!request.at("escrow").as_object().at("seq").is_int64())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("escrow").as_object().at("owner").as_string().c_str());
|
||||
|
||||
if (!id)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedOwner"};
|
||||
else
|
||||
{
|
||||
std::uint32_t seq =
|
||||
request.at("escrow").as_object().at("seq").as_int64();
|
||||
key = ripple::keylet::escrow(*id, seq).key;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.contains("offer"))
|
||||
{
|
||||
if (!request.at("offer").is_object())
|
||||
{
|
||||
if (!key.parseHex(request.at("offer").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedOffer"};
|
||||
}
|
||||
else if (
|
||||
!request.at("offer").as_object().contains("account") ||
|
||||
!request.at("offer").as_object().at("account").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
}
|
||||
else if (
|
||||
!request.at("offer").as_object().contains("seq") ||
|
||||
!request.at("offer").as_object().at("seq").is_int64())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedSeq"};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto offer = request.at("offer").as_object();
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
offer.at("account").as_string().c_str());
|
||||
|
||||
if (!id)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccount"};
|
||||
else
|
||||
{
|
||||
std::uint32_t seq = value_to<std::uint32_t>(offer.at("seq"));
|
||||
key = ripple::keylet::offer(*id, seq).key;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (request.contains("payment_channel"))
|
||||
{
|
||||
if (!request.at("payment_channel").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "paymentChannelNotString"};
|
||||
|
||||
if (!key.parseHex(request.at("payment_channel").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedPaymentChannel"};
|
||||
}
|
||||
else if (request.contains("ripple_state"))
|
||||
{
|
||||
if (!request.at("ripple_state").is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "rippleStateNotObject"};
|
||||
|
||||
ripple::Currency currency;
|
||||
boost::json::object const& state = request.at("ripple_state").as_object();
|
||||
|
||||
if (
|
||||
!state.contains("currency") ||
|
||||
!state.at("currency").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"};
|
||||
}
|
||||
|
||||
if (
|
||||
!state.contains("accounts") ||
|
||||
!state.at("accounts").is_array() ||
|
||||
2 != state.at("accounts").as_array().size() ||
|
||||
!state.at("accounts").as_array().at(0).is_string() ||
|
||||
!state.at("accounts").as_array().at(1).is_string() ||
|
||||
(state.at("accounts").as_array().at(0).as_string() ==
|
||||
state.at("accounts").as_array().at(1).as_string()))
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"};
|
||||
}
|
||||
|
||||
auto const id1 = ripple::parseBase58<ripple::AccountID>(
|
||||
state.at("accounts").as_array().at(0).as_string().c_str());
|
||||
auto const id2 = ripple::parseBase58<ripple::AccountID>(
|
||||
state.at("accounts").as_array().at(1).as_string().c_str());
|
||||
|
||||
if (!id1 || !id2)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedAccounts"};
|
||||
|
||||
else if (!ripple::to_currency(
|
||||
currency, state.at("currency").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCurrency"};
|
||||
|
||||
key = ripple::keylet::line(*id1, *id2, currency).key;
|
||||
|
||||
}
|
||||
else if (request.contains("ticket"))
|
||||
{
|
||||
if (!request.at("ticket").is_object())
|
||||
{
|
||||
if (!request.at("ticket").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ticketNotString"};
|
||||
|
||||
if (!key.parseHex(request.at("ticket").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTicket"};
|
||||
|
||||
}
|
||||
else if (
|
||||
!request.at("ticket").as_object().contains("account") ||
|
||||
!request.at("ticket").as_object().at("account").is_string())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTicketAccount"};
|
||||
}
|
||||
else if (
|
||||
!request.at("ticket").as_object().contains("ticket_seq") ||
|
||||
!request.at("ticket").as_object().at("ticket_seq").is_int64())
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTicketSeq"};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const id = ripple::parseBase58<ripple::AccountID>(
|
||||
request.at("ticket").as_object().at("account").as_string().c_str());
|
||||
|
||||
if (!id)
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTicketAccount"};
|
||||
else
|
||||
{
|
||||
std::uint32_t seq =
|
||||
request.at("offer").as_object().at("ticket_seq").as_int64();
|
||||
|
||||
key = ripple::getTicketIndex(*id, seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return Status{Error::rpcINVALID_PARAMS, "unknownOption"};
|
||||
}
|
||||
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto dbResponse = context.backend->fetchLedgerObject(key, lgrInfo.seq);
|
||||
auto end = std::chrono::system_clock::now();
|
||||
auto time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start)
|
||||
.count();
|
||||
|
||||
if (!dbResponse or dbResponse->size() == 0)
|
||||
return Status{Error::rpcLGR_NOT_FOUND};
|
||||
|
||||
response["index"] = ripple::strHex(key);
|
||||
response["ledger_hash"] = ripple::strHex(lgrInfo.hash);
|
||||
response["ledger_index"] = lgrInfo.seq;
|
||||
|
||||
if (binary)
|
||||
{
|
||||
response["node_binary"] = ripple::strHex(*dbResponse);
|
||||
}
|
||||
else
|
||||
{
|
||||
ripple::STLedgerEntry sle{
|
||||
ripple::SerialIter{dbResponse->data(), dbResponse->size()}, key};
|
||||
response["node"] = toJson(sle);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,27 @@
|
||||
#include <handlers/methods/Ledger.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
boost::json::object
|
||||
doLedgerRange(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend)
|
||||
namespace RPC
|
||||
{
|
||||
boost::json::object response;
|
||||
|
||||
Result
|
||||
doLedgerRange(Context const& context)
|
||||
{
|
||||
boost::json::object response = {};
|
||||
|
||||
auto range = backend.fetchLedgerRange();
|
||||
auto range = context.backend->fetchLedgerRange();
|
||||
if (!range)
|
||||
{
|
||||
response["error"] = "No data";
|
||||
return Status{Error::rpcNOT_READY, "rangeNotFound"};
|
||||
}
|
||||
else
|
||||
{
|
||||
response["ledger_index_min"] = range->minSequence;
|
||||
response["ledger_index_max"] = range->maxSequence;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +1,40 @@
|
||||
#include <boost/json.hpp>
|
||||
#include <webserver/WsBase.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
#include <handlers/methods/Subscribe.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <server/WsBase.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
|
||||
static std::unordered_set<std::string> validStreams{
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
static std::unordered_set<std::string> validStreams {
|
||||
"ledger",
|
||||
"transactions",
|
||||
"transactions_proposed"};
|
||||
"transactions_proposed" };
|
||||
|
||||
boost::json::value
|
||||
Status
|
||||
validateStreams(boost::json::object const& request)
|
||||
{
|
||||
if (!request.at("streams").is_array())
|
||||
{
|
||||
return "missing or invalid streams";
|
||||
}
|
||||
|
||||
boost::json::array const& streams = request.at("streams").as_array();
|
||||
|
||||
for (auto const& stream : streams)
|
||||
{
|
||||
if (!stream.is_string())
|
||||
{
|
||||
return "streams must be strings";
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "streamNotString"};
|
||||
|
||||
std::string s = stream.as_string().c_str();
|
||||
|
||||
if (validStreams.find(s) == validStreams.end())
|
||||
{
|
||||
return boost::json::string("invalid stream " + s);
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidStream" + s};
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void
|
||||
subscribeToStreams(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& streams = request.at("streams").as_array();
|
||||
@@ -62,7 +57,7 @@ subscribeToStreams(
|
||||
void
|
||||
unsubscribeToStreams(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& streams = request.at("streams").as_array();
|
||||
@@ -82,34 +77,28 @@ unsubscribeToStreams(
|
||||
}
|
||||
}
|
||||
|
||||
boost::json::value
|
||||
validateAccounts(
|
||||
boost::json::object const& request,
|
||||
boost::json::array const& accounts)
|
||||
Status
|
||||
validateAccounts(boost::json::array const& accounts)
|
||||
{
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (!account.is_string())
|
||||
{
|
||||
return "account must be strings";
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountNotString"};
|
||||
|
||||
std::string s = account.as_string().c_str();
|
||||
auto id = accountFromStringStrict(s);
|
||||
|
||||
if (!id)
|
||||
{
|
||||
return boost::json::string("invalid account " + s);
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidAccount" + s};
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return OK;
|
||||
}
|
||||
|
||||
void
|
||||
subscribeToAccounts(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts = request.at("accounts").as_array();
|
||||
@@ -120,7 +109,7 @@ subscribeToAccounts(
|
||||
|
||||
auto accountID = accountFromStringStrict(s);
|
||||
|
||||
if (!accountID)
|
||||
if(!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -133,7 +122,7 @@ subscribeToAccounts(
|
||||
void
|
||||
unsubscribeToAccounts(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts = request.at("accounts").as_array();
|
||||
@@ -144,7 +133,7 @@ unsubscribeToAccounts(
|
||||
|
||||
auto accountID = accountFromStringStrict(s);
|
||||
|
||||
if (!accountID)
|
||||
if(!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -157,11 +146,10 @@ unsubscribeToAccounts(
|
||||
void
|
||||
subscribeToAccountsProposed(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
boost::json::array const& accounts = request.at("accounts_proposed").as_array();
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
@@ -169,7 +157,7 @@ subscribeToAccountsProposed(
|
||||
|
||||
auto accountID = ripple::parseBase58<ripple::AccountID>(s);
|
||||
|
||||
if (!accountID)
|
||||
if(!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -182,11 +170,10 @@ subscribeToAccountsProposed(
|
||||
void
|
||||
unsubscribeToAccountsProposed(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
std::shared_ptr<WsBase> session,
|
||||
SubscriptionManager& manager)
|
||||
{
|
||||
boost::json::array const& accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
boost::json::array const& accounts = request.at("accounts_proposed").as_array();
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
@@ -194,7 +181,7 @@ unsubscribeToAccountsProposed(
|
||||
|
||||
auto accountID = ripple::parseBase58<ripple::AccountID>(s);
|
||||
|
||||
if (!accountID)
|
||||
if(!accountID)
|
||||
{
|
||||
assert(false);
|
||||
continue;
|
||||
@@ -204,128 +191,114 @@ unsubscribeToAccountsProposed(
|
||||
}
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doSubscribe(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
SubscriptionManager& manager)
|
||||
|
||||
Result
|
||||
doSubscribe(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
|
||||
if (request.contains("streams"))
|
||||
{
|
||||
boost::json::value error = validateStreams(request);
|
||||
if (!request.at("streams").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
auto status = validateStreams(request);
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("accounts"))
|
||||
{
|
||||
|
||||
if (!request.at("accounts").is_array())
|
||||
{
|
||||
response["error"] = "accounts must be array";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
|
||||
|
||||
boost::json::array accounts = request.at("accounts").as_array();
|
||||
boost::json::value error = validateAccounts(request, accounts);
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
{
|
||||
if (!request.at("accounts_proposed").is_array())
|
||||
{
|
||||
response["error"] = "accounts_proposed must be array";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
|
||||
|
||||
boost::json::array accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
boost::json::value error = validateAccounts(request, accounts);
|
||||
boost::json::array accounts = request.at("accounts_proposed").as_array();
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
if(status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("streams"))
|
||||
subscribeToStreams(request, session, manager);
|
||||
subscribeToStreams(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts"))
|
||||
subscribeToAccounts(request, session, manager);
|
||||
subscribeToAccounts(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
subscribeToAccountsProposed(request, session, manager);
|
||||
subscribeToAccountsProposed(request, context.session, *context.subscriptions);
|
||||
|
||||
response["status"] = "success";
|
||||
boost::json::object response = {{"status", "success"}};
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::json::object
|
||||
doUnsubscribe(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
SubscriptionManager& manager)
|
||||
Result
|
||||
doUnsubscribe(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
|
||||
if (request.contains("streams"))
|
||||
|
||||
if (request.contains("streams"))
|
||||
{
|
||||
boost::json::value error = validateStreams(request);
|
||||
if (!request.at("streams").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "streamsNotArray"};
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
auto status = validateStreams(request);
|
||||
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("accounts"))
|
||||
{
|
||||
|
||||
if (!request.at("accounts").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsNotArray"};
|
||||
|
||||
boost::json::array accounts = request.at("accounts").as_array();
|
||||
boost::json::value error = validateAccounts(request, accounts);
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
{
|
||||
boost::json::array accounts =
|
||||
request.at("accounts_proposed").as_array();
|
||||
boost::json::value error = validateAccounts(request, accounts);
|
||||
if (!request.at("accounts_proposed").is_array())
|
||||
return Status{Error::rpcINVALID_PARAMS, "accountsProposedNotArray"};
|
||||
|
||||
if (!error.is_null())
|
||||
{
|
||||
response["error"] = error;
|
||||
return response;
|
||||
}
|
||||
boost::json::array accounts = request.at("accounts_proposed").as_array();
|
||||
auto status = validateAccounts(accounts);
|
||||
|
||||
if(status)
|
||||
return status;
|
||||
}
|
||||
|
||||
if (request.contains("streams"))
|
||||
unsubscribeToStreams(request, session, manager);
|
||||
unsubscribeToStreams(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts"))
|
||||
unsubscribeToAccounts(request, session, manager);
|
||||
unsubscribeToAccounts(request, context.session, *context.subscriptions);
|
||||
|
||||
if (request.contains("accounts_proposed"))
|
||||
unsubscribeToAccountsProposed(request, session, manager);
|
||||
unsubscribeToAccountsProposed(request, context.session, *context.subscriptions);
|
||||
|
||||
response["status"] = "success";
|
||||
boost::json::object response = {{"status", "success"}};
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -17,60 +17,64 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <handlers/methods/Transaction.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/Pg.h>
|
||||
|
||||
namespace RPC
|
||||
{
|
||||
|
||||
// {
|
||||
// transaction: <hex>
|
||||
// }
|
||||
|
||||
boost::json::object
|
||||
doTx(boost::json::object const& request, BackendInterface const& backend)
|
||||
Result
|
||||
doTx(Context const& context)
|
||||
{
|
||||
boost::json::object response;
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if (!request.contains("transaction"))
|
||||
{
|
||||
response["error"] = "Please specify a transaction hash";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcINVALID_PARAMS, "specifyTransaction"};
|
||||
|
||||
if (!request.at("transaction").is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "transactionNotString"};
|
||||
|
||||
ripple::uint256 hash;
|
||||
if (!hash.parseHex(request.at("transaction").as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTransaction"};
|
||||
|
||||
bool binary = false;
|
||||
if(request.contains("binary"))
|
||||
{
|
||||
response["error"] = "Error parsing transaction hash";
|
||||
return response;
|
||||
if(!request.at("binary").is_bool())
|
||||
return Status{Error::rpcINVALID_PARAMS, "binaryFlagNotBool"};
|
||||
|
||||
binary = request.at("binary").as_bool();
|
||||
}
|
||||
|
||||
auto range = backend.fetchLedgerRange();
|
||||
auto range = context.backend->fetchLedgerRange();
|
||||
if (!range)
|
||||
{
|
||||
response["error"] = "Database is empty";
|
||||
return response;
|
||||
}
|
||||
return Status{Error::rpcNOT_READY};
|
||||
|
||||
auto dbResponse = backend.fetchTransaction(hash);
|
||||
auto dbResponse = context.backend->fetchTransaction(hash);
|
||||
if (!dbResponse)
|
||||
{
|
||||
response["error"] = "Transaction not found in Cassandra";
|
||||
response["ledger_range"] = std::to_string(range->minSequence) + " - " +
|
||||
std::to_string(range->maxSequence);
|
||||
return Status{Error::rpcTXN_NOT_FOUND};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool binary =
|
||||
request.contains("binary") ? request.at("binary").as_bool() : false;
|
||||
if (!binary)
|
||||
{
|
||||
auto [sttx, meta] = deserializeTxPlusMeta(dbResponse.value());
|
||||
response["transaction"] = toJson(*sttx);
|
||||
response["metadata"] = toJson(*meta);
|
||||
response = toJson(*sttx);
|
||||
response["meta"] = toJson(*meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
response["transaction"] = ripple::strHex(dbResponse->transaction);
|
||||
response["tx"] = ripple::strHex(dbResponse->transaction);
|
||||
response["metadata"] = ripple::strHex(dbResponse->metadata);
|
||||
}
|
||||
response["ledger_sequence"] = dbResponse->ledgerSequence;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
36
src/main.cpp
36
src/main.cpp
@@ -1,17 +1,21 @@
|
||||
//
|
||||
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
// Official repository: https://github.com/boostorg/beast
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 Ripple Labs Inc.
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Example: WebSocket server, asynchronous
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
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 <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
@@ -28,7 +32,7 @@
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <server/listener.h>
|
||||
#include <webserver/Listener.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -109,10 +113,10 @@ main(int argc, char* argv[])
|
||||
// Check command line arguments.
|
||||
if (argc != 2)
|
||||
{
|
||||
std::cerr << "Usage: websocket-server-async "
|
||||
std::cerr << "Usage: clio_server "
|
||||
"<config_file> \n"
|
||||
<< "Example:\n"
|
||||
<< " websocket-server-async config.json \n";
|
||||
<< " clio_server config.json \n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
#include <server/Handlers.h>
|
||||
|
||||
bool
|
||||
shouldForwardToRippled(boost::json::object const& request)
|
||||
{
|
||||
if (request.contains("forward") && request.at("forward").is_bool())
|
||||
return request.at("forward").as_bool();
|
||||
BOOST_LOG_TRIVIAL(info) << "checked forward";
|
||||
|
||||
std::string strCommand = request.contains("command")
|
||||
? request.at("command").as_string().c_str()
|
||||
: request.at("method").as_string().c_str();
|
||||
BOOST_LOG_TRIVIAL(info) << "checked command";
|
||||
|
||||
if (forwardCommands.find(strCommand) != forwardCommands.end())
|
||||
return true;
|
||||
|
||||
if (request.contains("ledger_index"))
|
||||
{
|
||||
auto indexValue = request.at("ledger_index");
|
||||
if (indexValue.is_string())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "checking ledger as string";
|
||||
std::string index = indexValue.as_string().c_str();
|
||||
return index == "current" || index == "closed";
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "checked ledger";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<boost::json::object, uint32_t>
|
||||
buildResponse(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> manager,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
std::shared_ptr<WsBase> session)
|
||||
{
|
||||
std::string command = request.at("command").as_string().c_str();
|
||||
BOOST_LOG_TRIVIAL(info) << "Received rpc command : " << request;
|
||||
boost::json::object response;
|
||||
|
||||
if (shouldForwardToRippled(request))
|
||||
return {balancer->forwardToRippled(request), 10};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Not forwarding";
|
||||
switch (commandMap[command])
|
||||
{
|
||||
case tx:
|
||||
return {doTx(request, *backend), 1};
|
||||
case account_tx: {
|
||||
auto res = doAccountTx(request, *backend);
|
||||
if (res.contains("transactions"))
|
||||
return {res, res["transactions"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case ledger: {
|
||||
auto res = doLedger(request, *backend);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "did command";
|
||||
if (res.contains("transactions"))
|
||||
return {res, res["transactions"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case ledger_entry:
|
||||
return {doLedgerEntry(request, *backend), 1};
|
||||
case ledger_range:
|
||||
return {doLedgerRange(request, *backend), 1};
|
||||
case ledger_data: {
|
||||
auto res = doLedgerData(request, *backend);
|
||||
if (res.contains("objects"))
|
||||
return {res, res["objects"].as_array().size() * 4};
|
||||
return {res, 1};
|
||||
}
|
||||
case account_info:
|
||||
return {doAccountInfo(request, *backend), 1};
|
||||
case book_offers: {
|
||||
auto res = doBookOffers(request, *backend);
|
||||
if (res.contains("offers"))
|
||||
return {res, res["offers"].as_array().size() * 4};
|
||||
return {res, 1};
|
||||
}
|
||||
case account_channels: {
|
||||
auto res = doAccountChannels(request, *backend);
|
||||
if (res.contains("channels"))
|
||||
return {res, res["channels"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case account_lines: {
|
||||
auto res = doAccountLines(request, *backend);
|
||||
if (res.contains("lines"))
|
||||
return {res, res["lines"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case account_currencies: {
|
||||
auto res = doAccountCurrencies(request, *backend);
|
||||
size_t count = 1;
|
||||
if (res.contains("send_currencies"))
|
||||
count = res["send_currencies"].as_array().size();
|
||||
if (res.contains("receive_currencies"))
|
||||
count += res["receive_currencies"].as_array().size();
|
||||
return {res, count};
|
||||
}
|
||||
|
||||
case account_offers: {
|
||||
auto res = doAccountOffers(request, *backend);
|
||||
if (res.contains("offers"))
|
||||
return {res, res["offers"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case account_objects: {
|
||||
auto res = doAccountObjects(request, *backend);
|
||||
if (res.contains("objects"))
|
||||
return {res, res["objects"].as_array().size()};
|
||||
return {res, 1};
|
||||
}
|
||||
case channel_authorize: {
|
||||
return {doChannelAuthorize(request), 1};
|
||||
};
|
||||
case channel_verify:
|
||||
return {doChannelVerify(request), 1};
|
||||
case subscribe:
|
||||
return {doSubscribe(request, session, *manager), 1};
|
||||
case unsubscribe:
|
||||
return {doUnsubscribe(request, session, *manager), 1};
|
||||
case server_info: {
|
||||
return {doServerInfo(request, *backend), 1};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
response["error"] = "Unknown command: " + command;
|
||||
return {response, 1};
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <etl/ReportingETL.h>
|
||||
#include <server/WsBase.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef RIPPLE_REPORTING_HANDLERS_H
|
||||
#define RIPPLE_REPORTING_HANDLERS_H
|
||||
|
||||
class ReportingETL;
|
||||
class SubscriptionManager;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static std::unordered_set<std::string> forwardCommands{
|
||||
"submit",
|
||||
"submit_multisigned",
|
||||
"fee",
|
||||
"path_find",
|
||||
"ripple_path_find",
|
||||
"manifest"};
|
||||
|
||||
enum RPCCommand {
|
||||
tx,
|
||||
account_tx,
|
||||
ledger,
|
||||
account_info,
|
||||
ledger_data,
|
||||
book_offers,
|
||||
ledger_range,
|
||||
ledger_entry,
|
||||
account_channels,
|
||||
account_lines,
|
||||
account_currencies,
|
||||
account_offers,
|
||||
account_objects,
|
||||
channel_authorize,
|
||||
channel_verify,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
server_info
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, RPCCommand> commandMap{
|
||||
{"tx", tx},
|
||||
{"account_tx", account_tx},
|
||||
{"ledger", ledger},
|
||||
{"ledger_range", ledger_range},
|
||||
{"ledger_entry", ledger_entry},
|
||||
{"account_info", account_info},
|
||||
{"ledger_data", ledger_data},
|
||||
{"book_offers", book_offers},
|
||||
{"account_channels", account_channels},
|
||||
{"account_lines", account_lines},
|
||||
{"account_currencies", account_currencies},
|
||||
{"account_offers", account_offers},
|
||||
{"account_objects", account_objects},
|
||||
{"channel_authorize", channel_authorize},
|
||||
{"channel_verify", channel_verify},
|
||||
{"subscribe", subscribe},
|
||||
{"unsubscribe", unsubscribe},
|
||||
{"server_info", server_info}};
|
||||
|
||||
boost::json::object
|
||||
doTx(boost::json::object const& request, BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountTx(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
doBookOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
doLedgerData(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doLedgerEntry(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doLedger(boost::json::object const& request, BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doLedgerRange(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
doAccountInfo(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountChannels(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountLines(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountCurrencies(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountOffers(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
boost::json::object
|
||||
doAccountObjects(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
doChannelAuthorize(boost::json::object const& request);
|
||||
boost::json::object
|
||||
doChannelVerify(boost::json::object const& request);
|
||||
|
||||
boost::json::object
|
||||
doServerInfo(
|
||||
boost::json::object const& request,
|
||||
BackendInterface const& backend);
|
||||
|
||||
boost::json::object
|
||||
doSubscribe(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
SubscriptionManager& manager);
|
||||
boost::json::object
|
||||
doUnsubscribe(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<WsBase>& session,
|
||||
SubscriptionManager& manager);
|
||||
|
||||
std::pair<boost::json::object, uint32_t>
|
||||
buildResponse(
|
||||
boost::json::object const& request,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> manager,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
std::shared_ptr<WsBase> session);
|
||||
|
||||
#endif // RIPPLE_REPORTING_HANDLERS_H
|
||||
@@ -36,8 +36,8 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <server/DOSGuard.h>
|
||||
#include <server/Handlers.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <webserver/DOSGuard.h>
|
||||
#include <vector>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
@@ -77,29 +77,6 @@ httpFail(boost::beast::error_code ec, char const* what)
|
||||
std::cerr << what << ": " << ec.message() << "\n";
|
||||
}
|
||||
|
||||
bool
|
||||
validRequest(boost::json::object const& req)
|
||||
{
|
||||
if (!req.contains("method") || !req.at("method").is_string())
|
||||
return false;
|
||||
|
||||
if (!req.contains("params"))
|
||||
return true;
|
||||
|
||||
if (!req.at("params").is_array())
|
||||
return false;
|
||||
|
||||
auto array = req.at("params").as_array();
|
||||
|
||||
if (array.size() != 1)
|
||||
return false;
|
||||
|
||||
if (!array.at(0).is_object())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function produces an HTTP response for the given
|
||||
// request. The type of the response object depends on the
|
||||
// contents of the request, so the interface requires the
|
||||
@@ -112,9 +89,10 @@ handle_request(
|
||||
Send&& send,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
DOSGuard& dosGuard,
|
||||
std::string const& ip)
|
||||
{
|
||||
auto const response = [&req](
|
||||
auto const httpResponse = [&req](
|
||||
http::status status,
|
||||
std::string content_type,
|
||||
std::string message) {
|
||||
@@ -129,70 +107,108 @@ handle_request(
|
||||
|
||||
if (req.method() == http::verb::get && req.body() == "")
|
||||
{
|
||||
send(response(http::status::ok, "text/html", defaultResponse));
|
||||
send(httpResponse(http::status::ok, "text/html", defaultResponse));
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method() != http::verb::post)
|
||||
{
|
||||
send(response(
|
||||
return send(httpResponse(
|
||||
http::status::bad_request, "text/html", "Expected a POST request"));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!dosGuard.isOk(ip))
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcSLOW_DOWN))));
|
||||
|
||||
try
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "Received request: " << req.body();
|
||||
|
||||
boost::json::object request;
|
||||
std::string responseStr = "";
|
||||
try
|
||||
{
|
||||
request = boost::json::parse(req.body()).as_object();
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
send(response(
|
||||
http::status::bad_request,
|
||||
"text/html",
|
||||
"Cannot parse json in body"));
|
||||
|
||||
return;
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcBAD_SYNTAX))));
|
||||
}
|
||||
|
||||
if (!validRequest(request))
|
||||
if (!dosGuard.isOk(ip))
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcSLOW_DOWN))));
|
||||
|
||||
auto range = backend->fetchLedgerRange();
|
||||
if (!range)
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcNOT_READY))));
|
||||
|
||||
std::optional<RPC::Context> context = RPC::make_HttpContext(
|
||||
request,
|
||||
backend,
|
||||
nullptr,
|
||||
balancer,
|
||||
*range
|
||||
);
|
||||
|
||||
if (!context)
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcBAD_SYNTAX))));
|
||||
|
||||
boost::json::object response{{"result", boost::json::object{}}};
|
||||
boost::json::object& result = response["result"].as_object();
|
||||
|
||||
auto v = RPC::buildResponse(*context);
|
||||
|
||||
if (auto status = std::get_if<RPC::Status>(&v))
|
||||
{
|
||||
send(response(
|
||||
http::status::bad_request, "text/html", "Malformed request"));
|
||||
auto error = RPC::make_error(status->error);
|
||||
|
||||
return;
|
||||
error["request"] = request;
|
||||
|
||||
result = error;
|
||||
|
||||
responseStr = boost::json::serialize(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = std::get<boost::json::object>(v);
|
||||
result["status"] = "success";
|
||||
result["validated"] = true;
|
||||
|
||||
responseStr = boost::json::serialize(response);
|
||||
}
|
||||
|
||||
boost::json::object wsStyleRequest = request.contains("params")
|
||||
? request.at("params").as_array().at(0).as_object()
|
||||
: boost::json::object{};
|
||||
if (!dosGuard.add(ip, responseStr.size()))
|
||||
result["warning"] = "Too many requests";
|
||||
|
||||
wsStyleRequest["command"] = request["method"];
|
||||
|
||||
auto [builtResponse, cost] =
|
||||
buildResponse(wsStyleRequest, backend, nullptr, balancer, nullptr);
|
||||
|
||||
send(response(
|
||||
return send(httpResponse(
|
||||
http::status::ok,
|
||||
"application/json",
|
||||
boost::json::serialize(builtResponse)));
|
||||
|
||||
return;
|
||||
responseStr));
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
std::cout << e.what() << std::endl;
|
||||
send(response(
|
||||
return send(httpResponse(
|
||||
http::status::internal_server_error,
|
||||
"text/html",
|
||||
"Internal server error occurred"));
|
||||
|
||||
return;
|
||||
"application/json",
|
||||
boost::json::serialize(RPC::make_error(RPC::Error::rpcINTERNAL))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +316,6 @@ public:
|
||||
if (ec)
|
||||
return httpFail(ec, "read");
|
||||
|
||||
auto ip = derived().ip();
|
||||
if (boost::beast::websocket::is_upgrade(req_))
|
||||
{
|
||||
// Disable the timeout.
|
||||
@@ -316,9 +331,11 @@ public:
|
||||
dosGuard_);
|
||||
}
|
||||
|
||||
auto ip = derived().ip();
|
||||
|
||||
// Send the response
|
||||
handle_request(
|
||||
std::move(req_), lambda_, backend_, balancer_, dosGuard_);
|
||||
std::move(req_), lambda_, backend_, balancer_, dosGuard_, ip);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef RIPPLE_REPORTING_HTTP_SESSION_H
|
||||
#define RIPPLE_REPORTING_HTTP_SESSION_H
|
||||
|
||||
#include <server/HttpBase.h>
|
||||
#include <webserver/HttpBase.h>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace net = boost::asio;
|
||||
@@ -35,7 +35,8 @@ class HttpSession : public HttpBase<HttpSession>,
|
||||
|
||||
public:
|
||||
// Take ownership of the socket
|
||||
explicit HttpSession(
|
||||
explicit
|
||||
HttpSession(
|
||||
tcp::socket&& socket,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
352
src/webserver/Listener.h
Normal file
352
src/webserver/Listener.h
Normal file
@@ -0,0 +1,352 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2020 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 LISTENER_H
|
||||
#define LISTENER_H
|
||||
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <webserver/HttpSession.h>
|
||||
#include <webserver/PlainWsSession.h>
|
||||
#include <webserver/SslHttpSession.h>
|
||||
#include <webserver/SslWsSession.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
class SubscriptionManager;
|
||||
|
||||
template <class PlainSession, class SslSession>
|
||||
class Detector
|
||||
: public std::enable_shared_from_this<Detector<PlainSession, SslSession>>
|
||||
{
|
||||
using std::enable_shared_from_this<
|
||||
Detector<PlainSession, SslSession>>::shared_from_this;
|
||||
|
||||
boost::beast::tcp_stream stream_;
|
||||
std::optional<std::reference_wrapper<ssl::context>> ctx_;
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManager> subscriptions_;
|
||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||
DOSGuard& dosGuard_;
|
||||
boost::beast::flat_buffer buffer_;
|
||||
|
||||
public:
|
||||
Detector(
|
||||
tcp::socket&& socket,
|
||||
std::optional<std::reference_wrapper<ssl::context>> ctx,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
: stream_(std::move(socket))
|
||||
, ctx_(ctx)
|
||||
, backend_(backend)
|
||||
, subscriptions_(subscriptions)
|
||||
, balancer_(balancer)
|
||||
, dosGuard_(dosGuard)
|
||||
{
|
||||
}
|
||||
|
||||
// Launch the detector
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Set the timeout.
|
||||
boost::beast::get_lowest_layer(stream_).expires_after(
|
||||
std::chrono::seconds(30));
|
||||
// Detect a TLS handshake
|
||||
async_detect_ssl(
|
||||
stream_,
|
||||
buffer_,
|
||||
boost::beast::bind_front_handler(
|
||||
&Detector::on_detect, shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
on_detect(boost::beast::error_code ec, bool result)
|
||||
{
|
||||
if (ec)
|
||||
return httpFail(ec, "detect");
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (!ctx_)
|
||||
return httpFail(ec, "ssl not supported by this server");
|
||||
// Launch SSL session
|
||||
std::make_shared<SslSession>(
|
||||
stream_.release_socket(),
|
||||
*ctx_,
|
||||
backend_,
|
||||
subscriptions_,
|
||||
balancer_,
|
||||
dosGuard_,
|
||||
std::move(buffer_))
|
||||
->run();
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch plain session
|
||||
std::make_shared<PlainSession>(
|
||||
stream_.release_socket(),
|
||||
backend_,
|
||||
subscriptions_,
|
||||
balancer_,
|
||||
dosGuard_,
|
||||
std::move(buffer_))
|
||||
->run();
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
make_websocket_session(
|
||||
boost::beast::tcp_stream stream,
|
||||
http::request<http::string_body> req,
|
||||
boost::beast::flat_buffer buffer,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
{
|
||||
std::make_shared<WsUpgrader>(
|
||||
std::move(stream),
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
dosGuard,
|
||||
std::move(buffer),
|
||||
std::move(req))
|
||||
->run();
|
||||
}
|
||||
|
||||
void
|
||||
make_websocket_session(
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
|
||||
http::request<http::string_body> req,
|
||||
boost::beast::flat_buffer buffer,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
{
|
||||
std::make_shared<SslWsUpgrader>(
|
||||
std::move(stream),
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
dosGuard,
|
||||
std::move(buffer),
|
||||
std::move(req))
|
||||
->run();
|
||||
}
|
||||
|
||||
template <class PlainSession, class SslSession>
|
||||
class Listener
|
||||
: public std::enable_shared_from_this<Listener<PlainSession, SslSession>>
|
||||
{
|
||||
using std::enable_shared_from_this<
|
||||
Listener<PlainSession, SslSession>>::shared_from_this;
|
||||
|
||||
net::io_context& ioc_;
|
||||
std::optional<ssl::context> ctx_;
|
||||
tcp::acceptor acceptor_;
|
||||
std::shared_ptr<BackendInterface> backend_;
|
||||
std::shared_ptr<SubscriptionManager> subscriptions_;
|
||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||
DOSGuard& dosGuard_;
|
||||
|
||||
public:
|
||||
Listener(
|
||||
net::io_context& ioc,
|
||||
std::optional<ssl::context>&& ctx,
|
||||
tcp::endpoint endpoint,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
: ioc_(ioc)
|
||||
, ctx_(std::move(ctx))
|
||||
, acceptor_(net::make_strand(ioc))
|
||||
, backend_(backend)
|
||||
, subscriptions_(subscriptions)
|
||||
, balancer_(balancer)
|
||||
, dosGuard_(dosGuard)
|
||||
{
|
||||
boost::beast::error_code ec;
|
||||
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (ec)
|
||||
{
|
||||
httpFail(ec, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(net::socket_base::reuse_address(true), ec);
|
||||
if (ec)
|
||||
{
|
||||
httpFail(ec, "set_option");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
httpFail(ec, "bind");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening for connections
|
||||
acceptor_.listen(net::socket_base::max_listen_connections, ec);
|
||||
if (ec)
|
||||
{
|
||||
httpFail(ec, "listen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start accepting incoming connections
|
||||
void
|
||||
run()
|
||||
{
|
||||
do_accept();
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
do_accept()
|
||||
{
|
||||
// The new connection gets its own strand
|
||||
acceptor_.async_accept(
|
||||
net::make_strand(ioc_),
|
||||
boost::beast::bind_front_handler(
|
||||
&Listener::on_accept, shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
on_accept(boost::beast::error_code ec, tcp::socket socket)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
httpFail(ec, "listener_accept");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ctxRef = ctx_
|
||||
? std::optional<
|
||||
std::reference_wrapper<ssl::context>>{ctx_.value()}
|
||||
: std::nullopt;
|
||||
// Create the detector session and run it
|
||||
std::make_shared<Detector<PlainSession, SslSession>>(
|
||||
std::move(socket),
|
||||
ctxRef,
|
||||
backend_,
|
||||
subscriptions_,
|
||||
balancer_,
|
||||
dosGuard_)
|
||||
->run();
|
||||
}
|
||||
|
||||
// Accept another connection
|
||||
do_accept();
|
||||
}
|
||||
};
|
||||
|
||||
namespace Server {
|
||||
std::optional<ssl::context>
|
||||
parse_certs(const char* certFilename, const char* keyFilename)
|
||||
{
|
||||
std::ifstream readCert(certFilename, std::ios::in | std::ios::binary);
|
||||
if (!readCert)
|
||||
return {};
|
||||
|
||||
std::stringstream contents;
|
||||
contents << readCert.rdbuf();
|
||||
readCert.close();
|
||||
std::string cert = contents.str();
|
||||
|
||||
std::ifstream readKey(keyFilename, std::ios::in | std::ios::binary);
|
||||
if (!readKey)
|
||||
return {};
|
||||
|
||||
contents.str("");
|
||||
contents << readKey.rdbuf();
|
||||
readKey.close();
|
||||
std::string key = contents.str();
|
||||
|
||||
ssl::context ctx{ssl::context::tlsv12};
|
||||
|
||||
ctx.set_options(
|
||||
boost::asio::ssl::context::default_workarounds |
|
||||
boost::asio::ssl::context::no_sslv2);
|
||||
|
||||
ctx.use_certificate_chain(boost::asio::buffer(cert.data(), cert.size()));
|
||||
|
||||
ctx.use_private_key(
|
||||
boost::asio::buffer(key.data(), key.size()),
|
||||
boost::asio::ssl::context::file_format::pem);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
using HttpServer = Listener<HttpSession, SslHttpSession>;
|
||||
|
||||
static std::shared_ptr<HttpServer>
|
||||
make_HttpServer(
|
||||
boost::json::object const& config,
|
||||
boost::asio::io_context& ioc,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||
DOSGuard& dosGuard)
|
||||
{
|
||||
if (!config.contains("server"))
|
||||
return nullptr;
|
||||
|
||||
auto const& serverConfig = config.at("server").as_object();
|
||||
std::optional<ssl::context> sslCtx;
|
||||
if (serverConfig.contains("ssl_cert_file") &&
|
||||
serverConfig.contains("ssl_key_file"))
|
||||
{
|
||||
sslCtx = parse_certs(
|
||||
serverConfig.at("ssl_cert_file").as_string().c_str(),
|
||||
serverConfig.at("ssl_key_file").as_string().c_str());
|
||||
}
|
||||
|
||||
auto const address = boost::asio::ip::make_address(
|
||||
serverConfig.at("ip").as_string().c_str());
|
||||
auto const port =
|
||||
static_cast<unsigned short>(serverConfig.at("port").as_int64());
|
||||
|
||||
auto server = std::make_shared<HttpServer>(
|
||||
ioc,
|
||||
std::move(sslCtx),
|
||||
boost::asio::ip::tcp::endpoint{address, port},
|
||||
backend,
|
||||
subscriptions,
|
||||
balancer,
|
||||
dosGuard);
|
||||
|
||||
server->run();
|
||||
return server;
|
||||
}
|
||||
} // namespace Server
|
||||
|
||||
#endif // LISTENER_H
|
||||
@@ -27,9 +27,9 @@
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
|
||||
#include <etl/ReportingETL.h>
|
||||
#include <server/Handlers.h>
|
||||
#include <server/WsBase.h>
|
||||
#include <server/listener.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <webserver/WsBase.h>
|
||||
#include <webserver/Listener.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -133,7 +133,6 @@ public:
|
||||
void
|
||||
run()
|
||||
{
|
||||
std::cout << "RUNNING" << std::endl;
|
||||
// We need to be executing within a strand to perform async operations
|
||||
// on the I/O objects in this session. Although not strictly necessary
|
||||
// for single-threaded contexts, this example code is written to be
|
||||
@@ -149,7 +148,6 @@ private:
|
||||
void
|
||||
do_upgrade()
|
||||
{
|
||||
std::cout << "doing upgrade" << std::endl;
|
||||
parser_.emplace();
|
||||
|
||||
// Apply a reasonable limit to the allowed size
|
||||
@@ -168,16 +166,12 @@ private:
|
||||
{
|
||||
// See if it is a WebSocket Upgrade
|
||||
if (!websocket::is_upgrade(req_))
|
||||
{
|
||||
std::cout << "is not upgrade" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the timeout.
|
||||
// The websocket::stream uses its own timeout settings.
|
||||
boost::beast::get_lowest_layer(http_).expires_never();
|
||||
|
||||
std::cout << "making session" << std::endl;
|
||||
std::make_shared<PlainWsSession>(
|
||||
http_.release_socket(),
|
||||
backend_,
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef RIPPLE_REPORTING_HTTPS_SESSION_H
|
||||
#define RIPPLE_REPORTING_HTTPS_SESSION_H
|
||||
|
||||
#include <server/HttpBase.h>
|
||||
#include <webserver/HttpBase.h>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace net = boost::asio;
|
||||
@@ -35,7 +35,8 @@ class SslHttpSession : public HttpBase<SslHttpSession>,
|
||||
|
||||
public:
|
||||
// Take ownership of the socket
|
||||
explicit SslHttpSession(
|
||||
explicit
|
||||
SslHttpSession(
|
||||
tcp::socket&& socket,
|
||||
ssl::context& ctx,
|
||||
std::shared_ptr<BackendInterface> backend,
|
||||
@@ -129,4 +130,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
#endif // RIPPLE_REPORTING_HTTPS_SESSION_H
|
||||
#endif // RIPPLE_REPORTING_HTTPS_SESSION_H
|
||||
@@ -27,9 +27,8 @@
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
|
||||
#include <etl/ReportingETL.h>
|
||||
#include <server/Handlers.h>
|
||||
|
||||
#include <server/WsBase.h>
|
||||
#include <webserver/WsBase.h>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace net = boost::asio;
|
||||
@@ -154,7 +153,6 @@ private:
|
||||
void
|
||||
do_upgrade()
|
||||
{
|
||||
std::cout << "doing upgrade" << std::endl;
|
||||
parser_.emplace();
|
||||
|
||||
// Apply a reasonable limit to the allowed size
|
||||
@@ -1,16 +1,18 @@
|
||||
#include <handlers/RPCHelpers.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
#include <server/WsBase.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
#include <webserver/WsBase.h>
|
||||
|
||||
void
|
||||
SubscriptionManager::subLedger(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[Ledgers].emplace(std::move(session));
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::unsubLedger(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[Ledgers].erase(session);
|
||||
}
|
||||
|
||||
@@ -36,19 +38,22 @@ SubscriptionManager::pubLedger(
|
||||
pubMsg["validated_ledgers"] = ledgerRange;
|
||||
pubMsg["txn_count"] = txnCount;
|
||||
|
||||
for (auto const& session : streamSubscribers_[Ledgers])
|
||||
std::lock_guard lk(m_);
|
||||
for (auto const& session: streamSubscribers_[Ledgers])
|
||||
session->send(boost::json::serialize(pubMsg));
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::subTransactions(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[Transactions].emplace(std::move(session));
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::unsubTransactions(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[Transactions].erase(session);
|
||||
}
|
||||
|
||||
@@ -57,6 +62,7 @@ SubscriptionManager::subAccount(
|
||||
ripple::AccountID const& account,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
accountSubscribers_[account].emplace(std::move(session));
|
||||
}
|
||||
|
||||
@@ -65,6 +71,7 @@ SubscriptionManager::unsubAccount(
|
||||
ripple::AccountID const& account,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
accountSubscribers_[account].erase(session);
|
||||
}
|
||||
|
||||
@@ -73,6 +80,8 @@ SubscriptionManager::pubTransaction(
|
||||
Backend::TransactionAndMetadata const& blob,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
|
||||
auto [tx, meta] = deserializeTxPlusMeta(blob, seq);
|
||||
|
||||
boost::json::object pubMsg;
|
||||
@@ -94,6 +103,7 @@ void
|
||||
SubscriptionManager::forwardProposedTransaction(
|
||||
boost::json::object const& response)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
for (auto const& session : streamSubscribers_[TransactionsProposed])
|
||||
session->send(boost::json::serialize(response));
|
||||
|
||||
@@ -110,6 +120,7 @@ SubscriptionManager::subProposedAccount(
|
||||
ripple::AccountID const& account,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
accountProposedSubscribers_[account].emplace(std::move(session));
|
||||
}
|
||||
|
||||
@@ -118,17 +129,43 @@ SubscriptionManager::unsubProposedAccount(
|
||||
ripple::AccountID const& account,
|
||||
std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
accountProposedSubscribers_[account].erase(session);
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::subProposedTransactions(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[TransactionsProposed].emplace(std::move(session));
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::unsubProposedTransactions(std::shared_ptr<WsBase>& session)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
streamSubscribers_[TransactionsProposed].erase(session);
|
||||
}
|
||||
|
||||
void
|
||||
SubscriptionManager::clearSession(WsBase* s)
|
||||
{
|
||||
std::lock_guard lk(m_);
|
||||
|
||||
// need the == operator. No-op delete
|
||||
std::shared_ptr<WsBase> targetSession(s, [](WsBase*){});
|
||||
for(auto& stream : streamSubscribers_)
|
||||
stream.erase(targetSession);
|
||||
|
||||
for(auto& [account, subscribers] : accountSubscribers_)
|
||||
{
|
||||
if (subscribers.find(targetSession) != subscribers.end())
|
||||
accountSubscribers_[account].erase(targetSession);
|
||||
}
|
||||
|
||||
for(auto& [account, subscribers] : accountProposedSubscribers_)
|
||||
{
|
||||
if (subscribers.find(targetSession) != subscribers.end())
|
||||
accountProposedSubscribers_[account].erase(targetSession);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <memory>
|
||||
#include <backend/BackendInterface.h>
|
||||
|
||||
class WsBase;
|
||||
|
||||
@@ -37,6 +38,7 @@ class SubscriptionManager
|
||||
finalEntry
|
||||
};
|
||||
|
||||
std::mutex m_;
|
||||
std::array<subscriptions, finalEntry> streamSubscribers_;
|
||||
std::unordered_map<ripple::AccountID, subscriptions> accountSubscribers_;
|
||||
std::unordered_map<ripple::AccountID, subscriptions>
|
||||
@@ -101,6 +103,9 @@ public:
|
||||
|
||||
void
|
||||
unsubProposedTransactions(std::shared_ptr<WsBase>& session);
|
||||
|
||||
void
|
||||
clearSession(WsBase* session);
|
||||
};
|
||||
|
||||
#endif // SUBSCRIPTION_MANAGER_H
|
||||
@@ -1,14 +1,37 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2021 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_REPORTING_WS_BASE_SESSION_H
|
||||
#define RIPPLE_REPORTING_WS_BASE_SESSION_H
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <etl/ETLSource.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <server/DOSGuard.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <etl/ETLSource.h>
|
||||
#include <handlers/Handlers.h>
|
||||
#include <handlers/Status.h>
|
||||
#include <webserver/DOSGuard.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace net = boost::asio;
|
||||
@@ -22,6 +45,20 @@ wsFail(boost::beast::error_code ec, char const* what)
|
||||
std::cerr << what << ": " << ec.message() << "\n";
|
||||
}
|
||||
|
||||
inline boost::json::object
|
||||
getDefaultWsResponse(boost::json::value const& id)
|
||||
{
|
||||
boost::json::object defaultResp = {};
|
||||
if(!id.is_null())
|
||||
defaultResp["id"] = id;
|
||||
|
||||
defaultResp["result"] = boost::json::object_kind;
|
||||
defaultResp["status"] = "success";
|
||||
defaultResp["type"] = "response";
|
||||
|
||||
return defaultResp;
|
||||
}
|
||||
|
||||
class WsBase
|
||||
{
|
||||
public:
|
||||
@@ -34,11 +71,16 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class SubscriptionManager;
|
||||
class ETLLoadBalancer;
|
||||
|
||||
// Echoes back all received WebSocket messages
|
||||
template <class Derived>
|
||||
class WsSession : public WsBase,
|
||||
public std::enable_shared_from_this<WsSession<Derived>>
|
||||
{
|
||||
using std::enable_shared_from_this<WsSession<Derived>>::shared_from_this;
|
||||
|
||||
boost::beast::flat_buffer buffer_;
|
||||
std::string response_;
|
||||
|
||||
@@ -64,6 +106,7 @@ public:
|
||||
virtual ~WsSession()
|
||||
{
|
||||
}
|
||||
|
||||
// Access the derived class, this is part of
|
||||
// the Curiously Recurring Template Pattern idiom.
|
||||
Derived&
|
||||
@@ -71,6 +114,7 @@ public:
|
||||
{
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
void
|
||||
send(std::string&& msg)
|
||||
{
|
||||
@@ -80,15 +124,14 @@ public:
|
||||
boost::beast::bind_front_handler(
|
||||
&WsSession::on_write, this->shared_from_this()));
|
||||
}
|
||||
|
||||
void
|
||||
run(http::request<http::string_body> req)
|
||||
{
|
||||
std::cout << "Running ws" << std::endl;
|
||||
// Set suggested timeout settings for the websocket
|
||||
derived().ws().set_option(websocket::stream_base::timeout::suggested(
|
||||
boost::beast::role_type::server));
|
||||
|
||||
std::cout << "Trying to decorate" << std::endl;
|
||||
// Set a decorator to change the Server of the handshake
|
||||
derived().ws().set_option(websocket::stream_base::decorator(
|
||||
[](websocket::response_type& res) {
|
||||
@@ -98,8 +141,6 @@ public:
|
||||
" websocket-server-async");
|
||||
}));
|
||||
|
||||
std::cout << "trying to async accept" << std::endl;
|
||||
|
||||
derived().ws().async_accept(
|
||||
req,
|
||||
boost::beast::bind_front_handler(
|
||||
@@ -116,6 +157,18 @@ public:
|
||||
do_read();
|
||||
}
|
||||
|
||||
void
|
||||
do_close()
|
||||
{
|
||||
std::shared_ptr<SubscriptionManager> mgr = subscriptions_.lock();
|
||||
|
||||
if(!mgr)
|
||||
return;
|
||||
|
||||
mgr->clearSession(this);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
do_read()
|
||||
{
|
||||
@@ -134,7 +187,6 @@ public:
|
||||
// This indicates that the session was closed
|
||||
if (ec == boost::beast::websocket::error::closed)
|
||||
{
|
||||
std::cout << "session closed" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,53 +211,72 @@ public:
|
||||
BOOST_LOG_TRIVIAL(debug) << " received request : " << request;
|
||||
try
|
||||
{
|
||||
std::shared_ptr<SubscriptionManager> subPtr =
|
||||
subscriptions_.lock();
|
||||
if (!subPtr)
|
||||
return;
|
||||
auto range = backend_->fetchLedgerRange();
|
||||
if (!range)
|
||||
return send(boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcNOT_READY)));
|
||||
|
||||
auto [res, cost] = buildResponse(
|
||||
std::optional<RPC::Context> context = RPC::make_WsContext(
|
||||
request,
|
||||
backend_,
|
||||
subPtr,
|
||||
subscriptions_.lock(),
|
||||
balancer_,
|
||||
this->shared_from_this());
|
||||
auto start = std::chrono::system_clock::now();
|
||||
response = std::move(res);
|
||||
if (!dosGuard_.add(ip, cost))
|
||||
shared_from_this(),
|
||||
*range
|
||||
);
|
||||
|
||||
if (!context)
|
||||
return send(boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcBAD_SYNTAX)));
|
||||
|
||||
auto id = request.contains("id")
|
||||
? request.at("id")
|
||||
: nullptr;
|
||||
|
||||
auto response = getDefaultWsResponse(id);
|
||||
boost::json::object& result = response["result"].as_object();
|
||||
|
||||
auto v = RPC::buildResponse(*context);
|
||||
|
||||
if (auto status = std::get_if<RPC::Status>(&v))
|
||||
{
|
||||
response["warning"] = "Too many requests";
|
||||
auto error = RPC::make_error(status->error);
|
||||
|
||||
if (!id.is_null())
|
||||
error["id"] = id;
|
||||
error["request"] = request;
|
||||
|
||||
response_ = boost::json::serialize(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto json = std::get<boost::json::object>(v);
|
||||
response_ = boost::json::serialize(json);
|
||||
}
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< __func__ << " RPC call took "
|
||||
<< ((end - start).count() / 1000000000.0)
|
||||
<< " . request = " << request;
|
||||
if (!dosGuard_.add(ip, response_.size()))
|
||||
result["warning"] = "Too many requests";
|
||||
|
||||
}
|
||||
catch (Backend::DatabaseTimeout const& t)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __func__ << " Database timeout";
|
||||
response["error"] =
|
||||
"Database read timeout. Please retry the request";
|
||||
return send(boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcNOT_READY)));
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error)
|
||||
<< __func__ << "caught exception : " << e.what();
|
||||
response["error"] = "Unknown exception";
|
||||
<< __func__ << " caught exception : " << e.what();
|
||||
|
||||
return send(boost::json::serialize(
|
||||
RPC::make_error(RPC::Error::rpcINTERNAL)));
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(trace) << __func__ << response;
|
||||
response_ = boost::json::serialize(response);
|
||||
BOOST_LOG_TRIVIAL(trace) << __func__ << response_;
|
||||
|
||||
// Echo the message
|
||||
derived().ws().text(derived().ws().got_text());
|
||||
derived().ws().async_write(
|
||||
boost::asio::buffer(response_),
|
||||
boost::beast::bind_front_handler(
|
||||
&WsSession::on_write, this->shared_from_this()));
|
||||
send(std::move(response_));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -223,4 +294,5 @@ public:
|
||||
do_read();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // RIPPLE_REPORTING_WS_BASE_SESSION_H
|
||||
@@ -2,11 +2,9 @@
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2020 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
|
||||
@@ -23,11 +21,11 @@
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <server/HttpSession.h>
|
||||
#include <server/PlainWsSession.h>
|
||||
#include <server/SslHttpSession.h>
|
||||
#include <server/SslWsSession.h>
|
||||
#include <server/SubscriptionManager.h>
|
||||
#include <webserver/HttpSession.h>
|
||||
#include <webserver/PlainWsSession.h>
|
||||
#include <webserver/SslHttpSession.h>
|
||||
#include <webserver/SslWsSession.h>
|
||||
#include <webserver/SubscriptionManager.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -156,6 +154,7 @@ make_websocket_session(
|
||||
std::move(req))
|
||||
->run();
|
||||
}
|
||||
|
||||
template <class PlainSession, class SslSession>
|
||||
class Listener
|
||||
: public std::enable_shared_from_this<Listener<PlainSession, SslSession>>
|
||||
@@ -306,7 +305,7 @@ parse_certs(const char* certFilename, const char* keyFilename)
|
||||
|
||||
return ctx;
|
||||
}
|
||||
using WebsocketServer = Listener<WsUpgrader, SslWsUpgrader>;
|
||||
|
||||
using HttpServer = Listener<HttpSession, SslHttpSession>;
|
||||
|
||||
static std::shared_ptr<HttpServer>
|
||||
@@ -350,4 +349,4 @@ make_HttpServer(
|
||||
}
|
||||
} // namespace Server
|
||||
|
||||
#endif // LISTENER_H
|
||||
#endif // LISTENER_H
|
||||
Reference in New Issue
Block a user