Skip to content

Commit aff75e7

Browse files
Broneksophiax851
authored andcommitted
APIv2: consistently return ledger_index as integer (XRPLF#4820)
For api_version 2, always return ledger_index as integer in JSON output. api_version 1 retains prior behavior.
1 parent 64d649c commit aff75e7

File tree

4 files changed

+232
-81
lines changed

4 files changed

+232
-81
lines changed

src/ripple/app/ledger/impl/LedgerToJson.cpp

+21-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <ripple/protocol/jss.h>
2828
#include <ripple/rpc/Context.h>
2929
#include <ripple/rpc/DeliveredAmount.h>
30+
#include <ripple/rpc/impl/RPCHelpers.h>
3031

3132
namespace ripple {
3233

@@ -52,10 +53,17 @@ isBinary(LedgerFill const& fill)
5253

5354
template <class Object>
5455
void
55-
fillJson(Object& json, bool closed, LedgerInfo const& info, bool bFull)
56+
fillJson(
57+
Object& json,
58+
bool closed,
59+
LedgerInfo const& info,
60+
bool bFull,
61+
unsigned apiVersion)
5662
{
5763
json[jss::parent_hash] = to_string(info.parentHash);
58-
json[jss::ledger_index] = to_string(info.seq);
64+
json[jss::ledger_index] = (apiVersion > 1)
65+
? Json::Value(info.seq)
66+
: Json::Value(std::to_string(info.seq));
5967

6068
if (closed)
6169
{
@@ -159,7 +167,10 @@ fillJsonTx(
159167
txJson[jss::validated] = validated;
160168
if (validated)
161169
{
162-
txJson[jss::ledger_index] = to_string(fill.ledger.seq());
170+
auto const seq = fill.ledger.seq();
171+
txJson[jss::ledger_index] = (fill.context->apiVersion > 1)
172+
? Json::Value(seq)
173+
: Json::Value(std::to_string(seq));
163174
if (fill.closeTime)
164175
txJson[jss::close_time_iso] = to_string_iso(*fill.closeTime);
165176
}
@@ -315,7 +326,13 @@ fillJson(Object& json, LedgerFill const& fill)
315326
if (isBinary(fill))
316327
fillJsonBinary(json, !fill.ledger.open(), fill.ledger.info());
317328
else
318-
fillJson(json, !fill.ledger.open(), fill.ledger.info(), bFull);
329+
fillJson(
330+
json,
331+
!fill.ledger.open(),
332+
fill.ledger.info(),
333+
bFull,
334+
(fill.context ? fill.context->apiVersion
335+
: RPC::apiMaximumSupportedVersion));
319336

320337
if (bFull || fill.options & LedgerFill::dumpTxrp)
321338
fillJsonTx(json, fill);

src/ripple/app/misc/NetworkOPs.cpp

+25-23
Original file line numberDiff line numberDiff line change
@@ -2181,8 +2181,10 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
21812181
if (masterKey != signerPublic)
21822182
jvObj[jss::master_key] = toBase58(TokenType::NodePublic, masterKey);
21832183

2184+
// NOTE *seq is a number, but old API versions used string. We replace
2185+
// number with a string using MultiApiJson near end of this function
21842186
if (auto const seq = (*val)[~sfLedgerSequence])
2185-
jvObj[jss::ledger_index] = to_string(*seq);
2187+
jvObj[jss::ledger_index] = *seq;
21862188

21872189
if (val->isFieldPresent(sfAmendments))
21882190
{
@@ -2220,12 +2222,28 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
22202222
reserveIncXRP && reserveIncXRP->native())
22212223
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
22222224

2225+
// NOTE Use MultiApiJson to publish two slightly different JSON objects
2226+
// for consumers supporting different API versions
2227+
MultiApiJson multiObj{jvObj};
2228+
visit<RPC::apiMinimumSupportedVersion, RPC::apiMaximumValidVersion>(
2229+
multiObj, //
2230+
[](Json::Value& jvTx, unsigned int apiVersion) {
2231+
// Type conversion for older API versions to string
2232+
if (jvTx.isMember(jss::ledger_index) && apiVersion < 2)
2233+
{
2234+
jvTx[jss::ledger_index] =
2235+
std::to_string(jvTx[jss::ledger_index].asUInt());
2236+
}
2237+
});
2238+
22232239
for (auto i = mStreamMaps[sValidations].begin();
22242240
i != mStreamMaps[sValidations].end();)
22252241
{
22262242
if (auto p = i->second.lock())
22272243
{
2228-
p->send(jvObj, true);
2244+
p->send(
2245+
multiObj.select(apiVersionSelector(p->getApiVersion())),
2246+
true);
22292247
++i;
22302248
}
22312249
else
@@ -3159,25 +3177,10 @@ NetworkOPsImp::transJson(
31593177
}
31603178

31613179
std::string const hash = to_string(transaction->getTransactionID());
3162-
MultiApiJson multiObj({jvObj, jvObj});
3163-
// Minimum supported API version must match index 0 in MultiApiJson
3164-
static_assert(apiVersionSelector(RPC::apiMinimumSupportedVersion)() == 0);
3165-
// Last valid (possibly beta) API ver. must match last index in MultiApiJson
3166-
static_assert(
3167-
apiVersionSelector(RPC::apiMaximumValidVersion)() + 1 //
3168-
== MultiApiJson::size);
3169-
for (unsigned apiVersion = RPC::apiMinimumSupportedVersion,
3170-
lastIndex = MultiApiJson::size;
3171-
apiVersion <= RPC::apiMaximumValidVersion;
3172-
++apiVersion)
3173-
{
3174-
unsigned const index = apiVersionSelector(apiVersion)();
3175-
assert(index < MultiApiJson::size);
3176-
if (index != lastIndex)
3177-
{
3178-
lastIndex = index;
3179-
3180-
Json::Value& jvTx = multiObj.val[index];
3180+
MultiApiJson multiObj{jvObj};
3181+
visit<RPC::apiMinimumSupportedVersion, RPC::apiMaximumValidVersion>(
3182+
multiObj, //
3183+
[&](Json::Value& jvTx, unsigned int apiVersion) {
31813184
RPC::insertDeliverMax(
31823185
jvTx[jss::transaction], transaction->getTxnType(), apiVersion);
31833186

@@ -3190,8 +3193,7 @@ NetworkOPsImp::transJson(
31903193
{
31913194
jvTx[jss::transaction][jss::hash] = hash;
31923195
}
3193-
}
3194-
}
3196+
});
31953197

31963198
return multiObj;
31973199
}

src/ripple/json/MultivarJson.h

+51-13
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,24 @@
2626
#include <cassert>
2727
#include <concepts>
2828
#include <cstdlib>
29+
#include <type_traits>
30+
#include <utility>
2931

3032
namespace ripple {
3133
template <std::size_t Size>
3234
struct MultivarJson
3335
{
34-
std::array<Json::Value, Size> val;
36+
std::array<Json::Value, Size> val = {};
3537
constexpr static std::size_t size = Size;
3638

39+
explicit MultivarJson(Json::Value const& init = {})
40+
{
41+
if (init == Json::Value{})
42+
return; // All elements are already default-initialized
43+
for (auto& v : val)
44+
v = init;
45+
}
46+
3747
Json::Value const&
3848
select(auto&& selector) const
3949
requires std::same_as<std::size_t, decltype(selector())>
@@ -68,7 +78,7 @@ struct MultivarJson
6878
};
6979

7080
// Wrapper for Json for all supported API versions.
71-
using MultiApiJson = MultivarJson<2>;
81+
using MultiApiJson = MultivarJson<3>;
7282

7383
/*
7484
@@ -78,21 +88,17 @@ If a future API version change adds another possible format, change the size of
7888
`MultiApiJson`, and update `apiVersionSelector()` to return the appropriate
7989
selection value for the new `apiVersion` and higher.
8090
81-
e.g. There are 2 formats now, the first, for version one, the second for
82-
versions > 1. Hypothetically, if API version 4 adds a new format, `MultiApiJson`
83-
would be MultivarJson<3>, and `apiVersionSelector` would return
84-
`static_cast<std::size_t>(apiVersion < 2 ? 0u : (apiVersion < 4 ? 1u : 2u))`
85-
86-
NOTE:
87-
8891
The more different JSON formats we support, the more CPU cycles we need to
89-
pre-build JSON for different API versions e.g. when publishing streams to
92+
prepare JSON for different API versions e.g. when publishing streams to
9093
`subscribe` clients. Hence it is desirable to keep MultiApiJson small and
9194
instead fully deprecate and remove support for old API versions. For example, if
9295
we removed support for API version 1 and added a different format for API
9396
version 3, the `apiVersionSelector` would change to
9497
`static_cast<std::size_t>(apiVersion > 2)`
9598
99+
Such hypothetical change should correspond with change in RPCHelpers.h
100+
`apiMinimumSupportedVersion = 2;`
101+
96102
*/
97103

98104
// Helper to create appropriate selector for indexing MultiApiJson by apiVersion
@@ -101,12 +107,44 @@ apiVersionSelector(unsigned int apiVersion) noexcept
101107
{
102108
return [apiVersion]() constexpr
103109
{
104-
// apiVersion <= 1 returns 0
105-
// apiVersion > 1 returns 1
106-
return static_cast<std::size_t>(apiVersion > 1);
110+
return static_cast<std::size_t>(
111+
apiVersion <= 1 //
112+
? 0 //
113+
: (apiVersion <= 2 //
114+
? 1 //
115+
: 2));
107116
};
108117
}
109118

119+
// Helper to execute a callback for every version. Want both min and max version
120+
// provided explicitly, so user will know to do update `size` when they change
121+
template <
122+
unsigned int minVer,
123+
unsigned int maxVer,
124+
std::size_t size,
125+
typename Fn>
126+
requires //
127+
(maxVer >= minVer) && //
128+
(size == maxVer + 1 - minVer) && //
129+
(apiVersionSelector(minVer)() == 0) && //
130+
(apiVersionSelector(maxVer)() + 1 == size) && //
131+
requires(Json::Value& json, Fn fn)
132+
{
133+
fn(json, static_cast<unsigned int>(1));
134+
}
135+
void
136+
visit(MultivarJson<size>& json, Fn fn)
137+
{
138+
[&]<std::size_t... offset>(std::index_sequence<offset...>)
139+
{
140+
static_assert(((apiVersionSelector(minVer + offset)() >= 0) && ...));
141+
static_assert(((apiVersionSelector(minVer + offset)() < size) && ...));
142+
(fn(json.val[apiVersionSelector(minVer + offset)()], minVer + offset),
143+
...);
144+
}
145+
(std::make_index_sequence<size>{});
146+
}
147+
110148
} // namespace ripple
111149

112150
#endif

0 commit comments

Comments
 (0)