rippled
Loading...
Searching...
No Matches
compression_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright 2020 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx/Account.h>
21#include <test/jtx/Env.h>
22#include <test/jtx/WSClient.h>
23#include <test/jtx/amount.h>
24#include <test/jtx/pay.h>
25#include <xrpld/app/ledger/Ledger.h>
26#include <xrpld/app/ledger/LedgerMaster.h>
27#include <xrpld/app/misc/Manifest.h>
28#include <xrpld/overlay/Compression.h>
29#include <xrpld/overlay/Message.h>
30#include <xrpld/overlay/detail/Handshake.h>
31#include <xrpld/overlay/detail/ProtocolMessage.h>
32#include <xrpld/overlay/detail/ZeroCopyStream.h>
33#include <xrpld/shamap/SHAMapNodeID.h>
34#include <xrpl/basics/random.h>
35#include <xrpl/beast/unit_test.h>
36#include <xrpl/beast/utility/Journal.h>
37#include <xrpl/protocol/HashPrefix.h>
38#include <xrpl/protocol/PublicKey.h>
39#include <xrpl/protocol/SecretKey.h>
40#include <xrpl/protocol/Sign.h>
41#include <xrpl/protocol/digest.h>
42#include <xrpl/protocol/jss.h>
43#include <xrpl/protocol/messages.h>
44#include <boost/asio/ip/address_v4.hpp>
45#include <boost/beast/core/multi_buffer.hpp>
46#include <boost/endian/conversion.hpp>
47#include <algorithm>
48
49namespace ripple {
50
51namespace test {
52
53using namespace ripple::test;
54using namespace ripple::test::jtx;
55
56static uint256
58{
59 return ripple::sha512Half(
61 std::uint32_t(info.seq),
63 info.parentHash,
64 info.txHash,
65 info.accountHash,
70}
71
73{
76
77public:
79 {
80 }
81
82 template <typename T>
83 void
86 protocol::MessageType mt,
87 uint16_t nbuffers,
88 std::string msg)
89 {
90 testcase("Compress/Decompress: " + msg);
91
92 Message m(*proto, mt);
93
94 auto& buffer = m.getBuffer(Compressed::On);
95
96 boost::beast::multi_buffer buffers;
97
98 // simulate multi-buffer
99 auto sz = buffer.size() / nbuffers;
100 for (int i = 0; i < nbuffers; i++)
101 {
102 auto start = buffer.begin() + sz * i;
103 auto end = i < nbuffers - 1 ? (buffer.begin() + sz * (i + 1))
104 : buffer.end();
105 std::vector<std::uint8_t> slice(start, end);
106 buffers.commit(boost::asio::buffer_copy(
107 buffers.prepare(slice.size()), boost::asio::buffer(slice)));
108 }
109
110 boost::system::error_code ec;
112 ec, buffers.data(), buffer.size());
113
114 BEAST_EXPECT(header);
115
116 if (!header || header->algorithm == Algorithm::None)
117 return;
118
119 std::vector<std::uint8_t> decompressed;
120 decompressed.resize(header->uncompressed_size);
121
122 BEAST_EXPECT(
123 header->payload_wire_size == buffer.size() - header->header_size);
124
125 ZeroCopyInputStream stream(buffers.data());
126 stream.Skip(header->header_size);
127
128 auto decompressedSize = ripple::compression::decompress(
129 stream,
130 header->payload_wire_size,
131 decompressed.data(),
132 header->uncompressed_size);
133 BEAST_EXPECT(decompressedSize == header->uncompressed_size);
134 auto const proto1 = std::make_shared<T>();
135
136 BEAST_EXPECT(
137 proto1->ParseFromArray(decompressed.data(), decompressedSize));
138 auto uncompressed = m.getBuffer(Compressed::Off);
139 BEAST_EXPECT(std::equal(
140 uncompressed.begin() + ripple::compression::headerBytes,
141 uncompressed.end(),
142 decompressed.begin()));
143 }
144
147 {
148 auto manifests = std::make_shared<protocol::TMManifests>();
149 manifests->mutable_list()->Reserve(n);
150 for (int i = 0; i < n; i++)
151 {
152 auto master = randomKeyPair(KeyType::ed25519);
153 auto signing = randomKeyPair(KeyType::ed25519);
155 st[sfSequence] = i;
156 st[sfPublicKey] = std::get<0>(master);
157 st[sfSigningPubKey] = std::get<0>(signing);
158 st[sfDomain] = makeSlice(
159 std::string("example") + std::to_string(i) +
160 std::string(".com"));
161 sign(
162 st,
165 std::get<1>(master),
166 sfMasterSignature);
167 sign(
168 st,
171 std::get<1>(signing));
172 Serializer s;
173 st.add(s);
174 auto* manifest = manifests->add_list();
175 manifest->set_stobject(s.data(), s.size());
176 }
177 return manifests;
178 }
179
182 {
183 auto endpoints = std::make_shared<protocol::TMEndpoints>();
184 endpoints->mutable_endpoints_v2()->Reserve(n);
185 for (int i = 0; i < n; i++)
186 {
187 auto ep = endpoints->add_endpoints_v2();
188 ep->set_endpoint(std::string("10.0.1.") + std::to_string(i));
189 ep->set_hops(i);
190 }
191 endpoints->set_version(2);
192
193 return endpoints;
194 }
195
198 {
199 Env env(*this, envconfig());
200 int fund = 10000;
201 auto const alice = Account("alice");
202 auto const bob = Account("bob");
203 env.fund(XRP(fund), "alice", "bob");
204 env.trust(bob["USD"](fund), alice);
205 env.close();
206
207 auto toBinary = [this](std::string const& text) {
208 auto blob = strUnHex(text);
209 BEAST_EXPECT(blob);
210 return std::string{
211 reinterpret_cast<char const*>(blob->data()), blob->size()};
212 };
213
214 std::string usdTxBlob = "";
215 auto wsc = makeWSClient(env.app().config());
216 {
217 Json::Value jrequestUsd;
218 jrequestUsd[jss::secret] = toBase58(generateSeed("bob"));
219 jrequestUsd[jss::tx_json] =
220 pay("bob", "alice", bob["USD"](fund / 2));
221 Json::Value jreply_usd = wsc->invoke("sign", jrequestUsd);
222
223 usdTxBlob =
224 toBinary(jreply_usd[jss::result][jss::tx_blob].asString());
225 }
226
227 auto transaction = std::make_shared<protocol::TMTransaction>();
228 transaction->set_rawtransaction(usdTxBlob);
229 transaction->set_status(protocol::tsNEW);
230 transaction->set_receivetimestamp(rand_int<std::uint64_t>());
231 transaction->set_deferred(true);
232
233 return transaction;
234 }
235
238 {
239 auto getLedger = std::make_shared<protocol::TMGetLedger>();
240 getLedger->set_itype(protocol::liTS_CANDIDATE);
241 getLedger->set_ltype(protocol::TMLedgerType::ltACCEPTED);
242 uint256 const hash(ripple::sha512Half(123456789));
243 getLedger->set_ledgerhash(hash.begin(), hash.size());
244 getLedger->set_ledgerseq(123456789);
245 ripple::SHAMapNodeID sha(64, hash);
246 getLedger->add_nodeids(sha.getRawString());
247 getLedger->set_requestcookie(123456789);
248 getLedger->set_querytype(protocol::qtINDIRECT);
249 getLedger->set_querydepth(3);
250 return getLedger;
251 }
252
254 buildLedgerData(uint32_t n, Logs& logs)
255 {
256 auto ledgerData = std::make_shared<protocol::TMLedgerData>();
257 uint256 const hash(ripple::sha512Half(12356789));
258 ledgerData->set_ledgerhash(hash.data(), hash.size());
259 ledgerData->set_ledgerseq(123456789);
260 ledgerData->set_type(protocol::TMLedgerInfoType::liAS_NODE);
261 ledgerData->set_requestcookie(123456789);
262 ledgerData->set_error(protocol::TMReplyError::reNO_LEDGER);
263 ledgerData->mutable_nodes()->Reserve(n);
264 uint256 parentHash(0);
265
266 NetClock::duration const resolution{10};
267 NetClock::time_point ct{resolution};
268
269 for (int i = 0; i < n; i++)
270 {
271 LedgerInfo info;
272 info.seq = i;
273 info.parentCloseTime = ct;
274 info.hash = ripple::sha512Half(i);
275 info.txHash = ripple::sha512Half(i + 1);
276 info.accountHash = ripple::sha512Half(i + 2);
277 info.parentHash = parentHash;
278 info.drops = XRPAmount(10);
279 info.closeTimeResolution = resolution;
280 info.closeTime = ct;
281 ct += resolution;
282 parentHash = ledgerHash(info);
283 Serializer nData;
284 ripple::addRaw(info, nData);
285 ledgerData->add_nodes()->set_nodedata(
286 nData.getDataPtr(), nData.getLength());
287 }
288
289 return ledgerData;
290 }
291
294 {
295 auto getObject = std::make_shared<protocol::TMGetObjectByHash>();
296
297 getObject->set_type(protocol::TMGetObjectByHash_ObjectType::
298 TMGetObjectByHash_ObjectType_otTRANSACTION);
299 getObject->set_query(true);
300 getObject->set_seq(123456789);
301 uint256 hash(ripple::sha512Half(123456789));
302 getObject->set_ledgerhash(hash.data(), hash.size());
303 getObject->set_fat(true);
304 for (int i = 0; i < 100; i++)
305 {
307 auto object = getObject->add_objects();
308 object->set_hash(hash.data(), hash.size());
309 ripple::SHAMapNodeID sha(64, hash);
310 object->set_nodeid(sha.getRawString());
311 object->set_index("");
312 object->set_data("");
313 object->set_ledgerseq(i);
314 }
315 return getObject;
316 }
317
320 {
321 auto list = std::make_shared<protocol::TMValidatorList>();
322
323 auto master = randomKeyPair(KeyType::ed25519);
324 auto signing = randomKeyPair(KeyType::ed25519);
326 st[sfSequence] = 0;
327 st[sfPublicKey] = std::get<0>(master);
328 st[sfSigningPubKey] = std::get<0>(signing);
329 st[sfDomain] = makeSlice(std::string("example.com"));
330 sign(
331 st,
334 std::get<1>(master),
335 sfMasterSignature);
336 sign(st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
337 Serializer s;
338 st.add(s);
339 list->set_manifest(s.data(), s.size());
340 list->set_version(3);
341 STObject signature(sfSignature);
343 st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
344 Serializer s1;
345 st.add(s1);
346 list->set_signature(s1.data(), s1.size());
347 list->set_blob(strHex(s.slice()));
348 return list;
349 }
350
353 {
354 auto list = std::make_shared<protocol::TMValidatorListCollection>();
355
356 auto master = randomKeyPair(KeyType::ed25519);
357 auto signing = randomKeyPair(KeyType::ed25519);
359 st[sfSequence] = 0;
360 st[sfPublicKey] = std::get<0>(master);
361 st[sfSigningPubKey] = std::get<0>(signing);
362 st[sfDomain] = makeSlice(std::string("example.com"));
363 sign(
364 st,
367 std::get<1>(master),
368 sfMasterSignature);
369 sign(st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
370 Serializer s;
371 st.add(s);
372 list->set_manifest(s.data(), s.size());
373 list->set_version(4);
374 STObject signature(sfSignature);
376 st, HashPrefix::manifest, KeyType::ed25519, std::get<1>(signing));
377 Serializer s1;
378 st.add(s1);
379 auto& blob = *list->add_blobs();
380 blob.set_signature(s1.data(), s1.size());
381 blob.set_blob(strHex(s.slice()));
382 return list;
383 }
384
385 void
387 {
389 auto logs = std::make_unique<Logs>(thresh);
390
391 protocol::TMManifests manifests;
392 protocol::TMEndpoints endpoints;
393 protocol::TMTransaction transaction;
394 protocol::TMGetLedger get_ledger;
395 protocol::TMLedgerData ledger_data;
396 protocol::TMGetObjectByHash get_object;
397 protocol::TMValidatorList validator_list;
398 protocol::TMValidatorListCollection validator_list_collection;
399
400 // 4.5KB
401 doTest(buildManifests(20), protocol::mtMANIFESTS, 4, "TMManifests20");
402 // 22KB
403 doTest(buildManifests(100), protocol::mtMANIFESTS, 4, "TMManifests100");
404 // 131B
405 doTest(buildEndpoints(10), protocol::mtENDPOINTS, 4, "TMEndpoints10");
406 // 1.3KB
407 doTest(buildEndpoints(100), protocol::mtENDPOINTS, 4, "TMEndpoints100");
408 // 242B
409 doTest(
410 buildTransaction(*logs),
411 protocol::mtTRANSACTION,
412 1,
413 "TMTransaction");
414 // 87B
415 doTest(buildGetLedger(), protocol::mtGET_LEDGER, 1, "TMGetLedger");
416 // 61KB
417 doTest(
418 buildLedgerData(500, *logs),
419 protocol::mtLEDGER_DATA,
420 10,
421 "TMLedgerData500");
422 // 122 KB
423 doTest(
424 buildLedgerData(1000, *logs),
425 protocol::mtLEDGER_DATA,
426 20,
427 "TMLedgerData1000");
428 // 1.2MB
429 doTest(
430 buildLedgerData(10000, *logs),
431 protocol::mtLEDGER_DATA,
432 50,
433 "TMLedgerData10000");
434 // 12MB
435 doTest(
436 buildLedgerData(100000, *logs),
437 protocol::mtLEDGER_DATA,
438 100,
439 "TMLedgerData100000");
440 // 61MB
441 doTest(
442 buildLedgerData(500000, *logs),
443 protocol::mtLEDGER_DATA,
444 100,
445 "TMLedgerData500000");
446 // 7.7KB
447 doTest(
449 protocol::mtGET_OBJECTS,
450 4,
451 "TMGetObjectByHash");
452 // 895B
453 doTest(
455 protocol::mtVALIDATORLIST,
456 4,
457 "TMValidatorList");
458 doTest(
460 protocol::mtVALIDATORLISTCOLLECTION,
461 4,
462 "TMValidatorListCollection");
463 }
464
465 void
467 {
468 testcase("Handshake");
469 auto getEnv = [&](bool enable) {
470 Config c;
472 str << "[reduce_relay]\n"
473 << "vp_enable=1\n"
474 << "vp_squelch=1\n"
475 << "[compression]\n"
476 << enable << "\n";
477 c.loadFromString(str.str());
478 auto env = std::make_shared<jtx::Env>(*this);
479 env->app().config().COMPRESSION = c.COMPRESSION;
480 env->app().config().VP_REDUCE_RELAY_ENABLE =
482 env->app().config().VP_REDUCE_RELAY_SQUELCH =
484 return env;
485 };
486 auto handshake = [&](int outboundEnable, int inboundEnable) {
487 beast::IP::Address addr =
488 boost::asio::ip::address::from_string("172.1.1.100");
489
490 auto env = getEnv(outboundEnable);
491 auto request = ripple::makeRequest(
492 true,
493 env->app().config().COMPRESSION,
494 false,
495 env->app().config().TX_REDUCE_RELAY_ENABLE,
496 env->app().config().VP_REDUCE_RELAY_ENABLE);
497 http_request_type http_request;
498 http_request.version(request.version());
499 http_request.base() = request.base();
500 // feature enabled on the peer's connection only if both sides are
501 // enabled
502 auto const peerEnabled = inboundEnable && outboundEnable;
503 // inbound is enabled if the request's header has the feature
504 // enabled and the peer's configuration is enabled
505 auto const inboundEnabled = peerFeatureEnabled(
506 http_request, FEATURE_COMPR, "lz4", inboundEnable);
507 BEAST_EXPECT(!(peerEnabled ^ inboundEnabled));
508
509 env.reset();
510 env = getEnv(inboundEnable);
511 auto http_resp = ripple::makeResponse(
512 true,
513 http_request,
514 addr,
515 addr,
516 uint256{1},
517 1,
518 {1, 0},
519 env->app());
520 // outbound is enabled if the response's header has the feature
521 // enabled and the peer's configuration is enabled
522 auto const outboundEnabled = peerFeatureEnabled(
523 http_resp, FEATURE_COMPR, "lz4", outboundEnable);
524 BEAST_EXPECT(!(peerEnabled ^ outboundEnabled));
525 };
526 handshake(1, 1);
527 handshake(1, 0);
528 handshake(0, 1);
529 handshake(0, 0);
530 }
531
532 void
533 run() override
534 {
535 testProtocol();
537 }
538};
539
540BEAST_DEFINE_TESTSUITE_MANUAL(compression, ripple_data, ripple);
541
542} // namespace test
543} // namespace ripple
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:147
A testsuite class.
Definition: suite.h:53
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
virtual Config & config()=0
bool VP_REDUCE_RELAY_ENABLE
Definition: Config.h:255
void loadFromString(std::string const &fileContents)
Load the config from the contents of the string.
Definition: Config.cpp:476
bool COMPRESSION
Definition: Config.h:227
bool VP_REDUCE_RELAY_SQUELCH
Definition: Config.h:264
Manages partitions for logging.
Definition: Log.h:49
std::vector< uint8_t > const & getBuffer(Compressed tryCompressed)
Retrieve the packed message data.
Definition: Message.cpp:209
Identifies a node inside a SHAMap.
Definition: SHAMapNodeID.h:34
std::string getRawString() const
void add(Serializer &s) const override
Definition: STObject.cpp:111
std::size_t size() const noexcept
Definition: Serializer.h:72
void const * data() const noexcept
Definition: Serializer.h:78
Slice slice() const noexcept
Definition: Serializer.h:66
int getLength() const
Definition: Serializer.h:233
const void * getDataPtr() const
Definition: Serializer.h:223
constexpr value_type drops() const
Returns the number of drops.
Definition: XRPAmount.h:177
Implements ZeroCopyInputStream around a buffer sequence.
iterator begin()
Definition: base_uint.h:135
pointer data()
Definition: base_uint.h:124
static constexpr std::size_t size()
Definition: base_uint.h:525
std::shared_ptr< protocol::TMGetObjectByHash > buildGetObjectByHash()
void run() override
Runs the suite.
std::shared_ptr< protocol::TMManifests > buildManifests(int n)
std::shared_ptr< protocol::TMLedgerData > buildLedgerData(uint32_t n, Logs &logs)
std::shared_ptr< protocol::TMValidatorListCollection > buildValidatorListCollection()
void doTest(std::shared_ptr< T > proto, protocol::MessageType mt, uint16_t nbuffers, std::string msg)
std::shared_ptr< protocol::TMValidatorList > buildValidatorList()
std::shared_ptr< protocol::TMGetLedger > buildGetLedger()
std::shared_ptr< protocol::TMTransaction > buildTransaction(Logs &logs)
std::shared_ptr< protocol::TMEndpoints > buildEndpoints(int n)
Immutable cryptographic account descriptor.
Definition: Account.h:38
A transaction testing environment.
Definition: Env.h:117
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:268
Application & app()
Definition: Env.h:255
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:237
T data(T... args)
T equal(T... args)
boost::asio::ip::address Address
Definition: IPAddress.h:41
std::size_t constexpr headerBytes
Definition: Compression.h:31
std::size_t decompress(InputStream &in, std::size_t inSize, std::uint8_t *decompressed, std::size_t decompressedSize, Algorithm algorithm=Algorithm::LZ4)
Decompress input stream.
Definition: Compression.h:50
std::optional< MessageHeader > parseMessageHeader(boost::system::error_code &ec, BufferSequence const &bufs, std::size_t size)
Parse a message header.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:35
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
void sign(Json::Value &jv, Account const &account)
Sign automatically.
Definition: utility.cpp:46
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
static uint256 ledgerHash(LedgerInfo const &info)
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:304
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:106
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
static constexpr char FEATURE_COMPR[]
Definition: Handshake.h:142
SField const sfGeneric
http_response_type makeResponse(bool crawlPublic, http_request_type const &req, beast::IP::Address public_ip, beast::IP::Address remote_ip, uint256 const &sharedValue, std::optional< std::uint32_t > networkID, ProtocolVersion protocol, Application &app)
Make http response.
Definition: Handshake.cpp:389
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
Definition: SecretKey.cpp:238
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:243
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handoff.h:31
bool peerFeatureEnabled(headers const &request, std::string const &feature, std::string value, bool config)
Check if a feature should be enabled for a peer.
Definition: Handshake.h:199
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:362
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
Definition: SecretKey.cpp:368
@ manifest
Manifest.
@ ledgerMaster
ledger master data for signing
void addRaw(LedgerHeader const &, Serializer &, bool includeHash=false)
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:69
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition: digest.h:223
T resize(T... args)
T size(T... args)
T str(T... args)
Information about the notional ledger backing the view.
Definition: LedgerHeader.h:34
NetClock::time_point closeTime
Definition: LedgerHeader.h:74
NetClock::duration closeTimeResolution
Definition: LedgerHeader.h:68
NetClock::time_point parentCloseTime
Definition: LedgerHeader.h:42
T time_since_epoch(T... args)
T to_string(T... args)