rippled
KnownFormatToGRPC_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 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 <ripple/basics/safe_cast.h>
21 #include <ripple/beast/unit_test.h>
22 #include <ripple/protocol/InnerObjectFormats.h>
23 #include <ripple/protocol/LedgerFormats.h>
24 #include <ripple/protocol/TxFormats.h>
25 
26 #include "org/xrpl/rpc/v1/ledger_objects.pb.h"
27 #include "org/xrpl/rpc/v1/transaction.pb.h"
28 
29 #include <cctype>
30 #include <map>
31 #include <string>
32 #include <type_traits>
33 
34 namespace ripple {
35 
36 // This test suite uses the google::protobuf::Descriptor class to do runtime
37 // reflection on our gRPC stuff. At the time of this writing documentation
38 // for Descriptor could be found here:
39 //
40 // https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.descriptor#Descriptor
41 
42 class KnownFormatToGRPC_test : public beast::unit_test::suite
43 {
44 private:
45  static constexpr auto fieldTYPE_UINT32 =
46  google::protobuf::FieldDescriptor::Type::TYPE_UINT32;
47 
48  static constexpr auto fieldTYPE_UINT64 =
49  google::protobuf::FieldDescriptor::Type::TYPE_UINT64;
50 
51  static constexpr auto fieldTYPE_BYTES =
52  google::protobuf::FieldDescriptor::Type::TYPE_BYTES;
53 
54  static constexpr auto fieldTYPE_STRING =
55  google::protobuf::FieldDescriptor::Type::TYPE_STRING;
56 
57  static constexpr auto fieldTYPE_MESSAGE =
58  google::protobuf::FieldDescriptor::Type::TYPE_MESSAGE;
59 
60  // Format names are CamelCase and FieldDescriptor names are snake_case.
61  // Convert from CamelCase to snake_case. Do not be fooled by consecutive
62  // capital letters like in NegativeUNL.
63  static std::string
65  {
66  std::string entryName;
67  entryName.reserve(fmtName.size());
68  bool prevUpper = false;
69  for (std::size_t i = 0; i < fmtName.size(); i++)
70  {
71  char const ch = fmtName[i];
72  bool const upper = std::isupper(ch);
73  if (i > 0 && !prevUpper && upper)
74  entryName.push_back('_');
75 
76  prevUpper = upper;
77  entryName.push_back(std::tolower(ch));
78  }
79  return entryName;
80  };
81 
82  // Create a map of (most) all the SFields in an SOTemplate. This map
83  // can be used to correlate a gRPC Descriptor to its corresponding SField.
84  template <typename KeyType>
87  SOTemplate const& soTemplate,
88  [[maybe_unused]] KeyType fmtId)
89  {
91  for (SOElement const& element : soTemplate)
92  {
93  SField const& sField = element.sField();
94 
95  // Fields that gRPC never includes.
96  //
97  // o sfLedgerIndex and
98  // o sfLedgerEntryType are common to all ledger objects, so
99  // gRPC includes them at a higher level than the ledger
100  // object itself.
101  //
102  // o sfOperationLimit is an optional field in all transactions,
103  // but no one knows what it was intended for.
104  using FieldCode_t =
105  std::remove_const<decltype(SField::fieldCode)>::type;
106  static const std::set<FieldCode_t> excludedSFields{
110 
111  if (excludedSFields.count(sField.fieldCode))
112  continue;
113 
114  // There are certain fields that gRPC never represents in
115  // transactions. Exclude those.
116  //
117  // o sfPreviousTxnID is obsolete and was replaced by
118  // sfAccountTxnID some time before November of 2014.
119  //
120  // o sfWalletLocator and
121  // o sfWalletSize have been deprecated for six years or more.
122  //
123  // o sfTransactionType is not needed by gRPC, since the typing
124  // is handled using protobuf message types.
125  if constexpr (std::is_same_v<KeyType, TxType>)
126  {
127  static const std::set<FieldCode_t> excludedTxFields{
132 
133  if (excludedTxFields.count(sField.fieldCode))
134  continue;
135  }
136 
137  // If fmtId is a LedgerEntryType, exclude certain fields.
138  if constexpr (std::is_same_v<KeyType, LedgerEntryType>)
139  {
140  // Fields that gRPC does not include in certain LedgerFormats.
141  //
142  // o sfWalletLocator,
143  // o sfWalletSize,
144  // o sfExchangeRate, and
145  // o sfFirstLedgerSequence are all deprecated fields in
146  // their respective ledger objects.
147  static const std::
148  map<LedgerEntryType, std::vector<SField const*>>
149  gRPCOmitFields{
153  };
154 
155  if (auto const iter = gRPCOmitFields.find(fmtId);
156  iter != gRPCOmitFields.end())
157  {
158  std::vector<SField const*> const& omits = iter->second;
159 
160  // Check for fields that gRPC omits from this type.
161  if (std::find_if(
162  omits.begin(),
163  omits.end(),
164  [&sField](SField const* const omit) {
165  return *omit == sField;
166  }) != omits.end())
167  {
168  // This is one of the fields that gRPC omits.
169  continue;
170  }
171  }
172  }
173 
174  // The SFields and gRPC disagree on the names of some fields.
175  // Provide a mapping from SField names to gRPC names for the
176  // known exceptions.
177  //
178  // clang-format off
179  //
180  // The implementers of the gRPC interface made the decision not
181  // to abbreviate anything. This accounts for the following
182  // field name differences:
183  //
184  // "AccountTxnID", "AccountTransactionID"
185  // "PreviousTxnID", "PreviousTransactionID"
186  // "PreviousTxnLgrSeq", "PreviousTransactionLedgerSequence"
187  // "SigningPubKey", "SigningPublicKey"
188  // "TxnSignature", "TransactionSignature"
189  //
190  // gRPC adds typing information for Fee, which accounts for
191  // "Fee", "XRPDropsAmount"
192  //
193  // There's one misspelling which accounts for
194  // "TakerGetsCurrency", "TakerGetsCurreny"
195  //
196  // The implementers of the gRPC interface observed that a
197  // PaymentChannelClaim transaction has a TxnSignature field at the
198  // upper level and a Signature field at the lever level. They
199  // felt that was confusing, which is the reason for
200  // "Signature", "PaymentChannelSignature"
201  //
202  static const std::map<std::string, std::string> sFieldToGRPC{
203  {"AccountTxnID", "AccountTransactionID"},
204  {"Fee", "XRPDropsAmount"},
205  {"PreviousTxnID", "PreviousTransactionID"},
206  {"PreviousTxnLgrSeq", "PreviousTransactionLedgerSequence"},
207  {"Signature", "PaymentChannelSignature"},
208  {"SigningPubKey", "SigningPublicKey"},
209  {"TakerGetsCurrency", "TakerGetsCurreny"},
210  {"TxnSignature", "TransactionSignature"},
211  };
212  // clang-format on
213 
214  auto const iter = sFieldToGRPC.find(sField.getName());
215  std::string gRPCName =
216  iter != sFieldToGRPC.end() ? iter->second : sField.getName();
217 
218  sFields.insert({std::move(gRPCName), &sField});
219  }
220  return sFields;
221  }
222 
223  // Given a Descriptor for a KnownFormat and a map of the SFields of that
224  // KnownFormat, make sure the fields are aligned.
225  void
227  google::protobuf::Descriptor const* const pbufDescriptor,
228  google::protobuf::Descriptor const* const commonFields,
229  std::string const& knownFormatName,
231  {
232  // Create namespace aliases for shorter names.
233  namespace pbuf = google::protobuf;
234 
235  // We'll be running through two sets of pbuf::Descriptors: the ones in
236  // the OneOf and the common fields. Here is a lambda that factors out
237  // the common checking code for these two cases.
238  auto checkFieldDesc =
239  [this, &sFields, &knownFormatName](
240  pbuf::FieldDescriptor const* const fieldDesc) {
241  // gRPC has different handling for repeated vs non-repeated
242  // types. So we need to do that too.
243  std::string name;
244  if (fieldDesc->is_repeated())
245  {
246  // Repeated-type handling.
247 
248  // Munge the fieldDescriptor name so it looks like the
249  // name in sFields.
250  name = fieldDesc->camelcase_name();
251  name[0] = toupper(name[0]);
252 
253  // The ledger gives UNL all caps. Adapt to that.
254  if (size_t const i = name.find("Unl");
255  i != std::string::npos)
256  {
257  name[i + 1] = 'N';
258  name[i + 2] = 'L';
259  }
260 
261  if (!sFields.count(name))
262  {
263  fail(
264  std::string("Repeated Protobuf Descriptor '") +
265  name + "' expected in KnownFormat '" +
266  knownFormatName + "' and not found",
267  __FILE__,
268  __LINE__);
269  return;
270  }
271  pass();
272 
273  validateRepeatedField(fieldDesc, sFields.at(name));
274  }
275  else
276  {
277  // Non-repeated handling.
278  pbuf::Descriptor const* const entryDesc =
279  fieldDesc->message_type();
280  if (entryDesc == nullptr)
281  return;
282 
283  name = entryDesc->name();
284  if (!sFields.count(name))
285  {
286  fail(
287  std::string("Protobuf Descriptor '") +
288  entryDesc->name() +
289  "' expected in KnownFormat '" +
290  knownFormatName + "' and not found",
291  __FILE__,
292  __LINE__);
293  return;
294  }
295  pass();
296 
298  entryDesc, sFields.at(entryDesc->name()));
299  }
300  // Remove the validated field from the map so we can tell if
301  // there are left over fields at the end of all comparisons.
302  sFields.erase(name);
303  };
304 
305  // Compare the SFields to the FieldDescriptor->Descriptors.
306  for (int i = 0; i < pbufDescriptor->field_count(); ++i)
307  {
308  pbuf::FieldDescriptor const* const fieldDesc =
309  pbufDescriptor->field(i);
310  if (fieldDesc == nullptr || fieldDesc->type() != fieldTYPE_MESSAGE)
311  continue;
312 
313  checkFieldDesc(fieldDesc);
314  }
315 
316  // Now all of the OneOf-specific fields have been removed from
317  // sFields. But there may be common fields left in there. Process
318  // the commonFields next.
319  if (commonFields)
320  {
321  for (int i = 0; i < commonFields->field_count(); ++i)
322  {
323  // If the field we picked up is a OneOf, skip it. Common
324  // fields are never OneOfs.
325  pbuf::FieldDescriptor const* const fieldDesc =
326  commonFields->field(i);
327 
328  if (fieldDesc == nullptr ||
329  fieldDesc->containing_oneof() != nullptr ||
330  fieldDesc->type() != fieldTYPE_MESSAGE)
331  continue;
332 
333  checkFieldDesc(fieldDesc);
334  }
335  }
336 
337  // All SFields in the KnownFormat have corresponding gRPC fields
338  // if the sFields map is now empty.
339  if (!sFields.empty())
340  {
341  fail(
342  std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
343  "' did not account for all fields in KnownFormat '" +
344  knownFormatName + "'. Left over field: `" +
345  sFields.begin()->first + "'",
346  __FILE__,
347  __LINE__);
348  return;
349  }
350  pass();
351  }
352 
353  // Compare a protobuf descriptor with multiple oneOfFields to choose from
354  // to an SField.
355  void
357  google::protobuf::Descriptor const* const entryDesc,
358  SField const* const sField)
359  {
360  // Create namespace aliases for shorter names.
361  namespace pbuf = google::protobuf;
362 
363  // Note that it's not okay to compare names because SFields and
364  // gRPC do not always agree on the names.
365  if (entryDesc->field_count() == 0 || entryDesc->oneof_decl_count() != 1)
366  {
367  fail(
368  std::string("Protobuf Descriptor '") + entryDesc->name() +
369  "' expected to have multiple OneOf fields and nothing else",
370  __FILE__,
371  __LINE__);
372  return;
373  }
374 
375  pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
376  if (fieldDesc == nullptr)
377  {
378  fail(
379  std::string("Internal test failure. Unhandled nullptr "
380  "in FieldDescriptor for '") +
381  entryDesc->name() + "'",
382  __FILE__,
383  __LINE__);
384  return;
385  }
386 
387  // Special handling for CurrencyAmount
388  if (sField->fieldType == STI_AMOUNT &&
389  entryDesc->name() == "CurrencyAmount")
390  {
391  // SFields of type STI_AMOUNT are represented in gRPC by a
392  // multi-field CurrencyAmount. We don't really learn anything
393  // by diving into the interior of CurrencyAmount, so we stop here
394  // and call it good.
395  pass();
396  return;
397  }
398 
399  fail(
400  std::string("Unhandled OneOf Protobuf Descriptor '") +
401  entryDesc->name() + "'",
402  __FILE__,
403  __LINE__);
404  }
405 
406  void
408  google::protobuf::Descriptor const* const entryDesc,
409  SField const* const sField)
410  {
411  // Create namespace aliases for shorter names.
412  namespace pbuf = google::protobuf;
413 
414  if (entryDesc->field_count() <= 1 || entryDesc->oneof_decl_count() != 0)
415  {
416  fail(
417  std::string("Protobuf Descriptor '") + entryDesc->name() +
418  "' expected to have multiple fields and nothing else",
419  __FILE__,
420  __LINE__);
421  return;
422  }
423 
424  // There are composite fields that the SFields handle differently
425  // from gRPC. Handle those here.
426  {
427  struct FieldContents
428  {
429  std::string_view fieldName;
430  google::protobuf::FieldDescriptor::Type fieldType;
431 
432  bool
433  operator<(FieldContents const& other) const
434  {
435  return this->fieldName < other.fieldName;
436  }
437 
438  bool
439  operator==(FieldContents const& other) const
440  {
441  return this->fieldName == other.fieldName &&
442  this->fieldType == other.fieldType;
443  }
444  };
445 
446  struct SpecialEntry
447  {
448  std::string_view const descriptorName;
449  SerializedTypeID const sFieldType;
450  std::set<FieldContents> const fields;
451  };
452 
453  // clang-format off
454  static const std::array specialEntries{
455  SpecialEntry{
456  "Currency", STI_HASH160,
457  {
458  {"name", fieldTYPE_STRING},
459  {"code", fieldTYPE_BYTES}
460  }
461  },
462  SpecialEntry{
463  "Memo", STI_OBJECT,
464  {
465  {"memo_data", fieldTYPE_BYTES},
466  {"memo_format", fieldTYPE_BYTES},
467  {"memo_type", fieldTYPE_BYTES}
468  }
469  }
470  };
471  // clang-format on
472 
473  // If we're handling a SpecialEntry...
474  if (auto const iter = std::find_if(
475  specialEntries.begin(),
476  specialEntries.end(),
477  [entryDesc, sField](SpecialEntry const& entry) {
478  return entryDesc->name() == entry.descriptorName &&
479  sField->fieldType == entry.sFieldType;
480  });
481  iter != specialEntries.end())
482  {
483  // Verify the SField.
484  if (!BEAST_EXPECT(sField->fieldType == iter->sFieldType))
485  return;
486 
487  // Verify all of the fields in the entryDesc.
488  if (!BEAST_EXPECT(
489  entryDesc->field_count() == iter->fields.size()))
490  return;
491 
492  for (int i = 0; i < entryDesc->field_count(); ++i)
493  {
494  pbuf::FieldDescriptor const* const fieldDesc =
495  entryDesc->field(i);
496 
497  FieldContents const contents{
498  fieldDesc->name(), fieldDesc->type()};
499 
500  if (!BEAST_EXPECT(
501  iter->fields.find(contents) != iter->fields.end()))
502  return;
503  }
504 
505  // This field is good.
506  pass();
507  return;
508  }
509  }
510 
511  // If the field was not one of the SpecialEntries, we expect it to be
512  // an InnerObjectFormat.
513  SOTemplate const* const innerFormat =
515  if (innerFormat == nullptr)
516  {
517  fail(
518  "SOTemplate for field '" + sField->getName() + "' not found",
519  __FILE__,
520  __LINE__);
521  return;
522  }
523 
524  // Create a map we can use use to correlate each field in the
525  // gRPC Descriptor to its corresponding SField.
527  soTemplateToSFields(*innerFormat, 0);
528 
529  // Compare the SFields to the FieldDescriptor->Descriptors.
531  entryDesc, nullptr, sField->getName(), std::move(sFields));
532  }
533 
534  // Compare a protobuf descriptor with only one field to an SField.
535  void
537  google::protobuf::Descriptor const* const entryDesc,
538  SField const* const sField)
539  {
540  // Create namespace aliases for shorter names.
541  namespace pbuf = google::protobuf;
542 
543  // Note that it's not okay to compare names because SFields and
544  // gRPC do not always agree on the names.
545  if (entryDesc->field_count() != 1 || entryDesc->oneof_decl_count() != 0)
546  {
547  fail(
548  std::string("Protobuf Descriptor '") + entryDesc->name() +
549  "' expected to be one field and nothing else",
550  __FILE__,
551  __LINE__);
552  return;
553  }
554 
555  pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
556  if (fieldDesc == nullptr)
557  {
558  fail(
559  std::string("Internal test failure. Unhandled nullptr "
560  "in FieldDescriptor for '") +
561  entryDesc->name() + "'",
562  __FILE__,
563  __LINE__);
564  return;
565  }
566 
567  // Create a map from SerializedTypeID to pbuf::FieldDescriptor::Type.
568  //
569  // This works for most, but not all, types because of divergence
570  // between the gRPC and LedgerFormat implementations. We deal
571  // with the special cases later.
572  // clang-format off
574  sTypeToFieldDescType{
578 
580 
582 
588  };
589  //clang-format on
590 
591  // If the SField and FieldDescriptor::Type correlate we're good.
592  if (auto const iter = sTypeToFieldDescType.find(sField->fieldType);
593  iter != sTypeToFieldDescType.end() &&
594  iter->second == fieldDesc->type())
595  {
596  pass();
597  return;
598  }
599 
600  // Handle special cases for specific SFields.
602  sFieldCodeToFieldDescType{
605 
606  if (auto const iter = sFieldCodeToFieldDescType.find(sField->fieldCode);
607  iter != sFieldCodeToFieldDescType.end() &&
608  iter->second == fieldDesc->type())
609  {
610  pass();
611  return;
612  }
613 
614  // Special handling for all Message types.
615  if (fieldDesc->type() == fieldTYPE_MESSAGE)
616  {
617  // We need to recurse to get to the bottom of the field(s)
618  // in question.
619 
620  // Start by identifying which fields we need to be handling.
621  // clang-format off
622  static const std::map<int, std::string> messageMap{
623  {sfAccount.fieldCode, "AccountAddress"},
624  {sfAmount.fieldCode, "CurrencyAmount"},
625  {sfAuthorize.fieldCode, "AccountAddress"},
626  {sfBalance.fieldCode, "CurrencyAmount"},
627  {sfDestination.fieldCode, "AccountAddress"},
628  {sfFee.fieldCode, "XRPDropsAmount"},
629  {sfHighLimit.fieldCode, "CurrencyAmount"},
630  {sfLowLimit.fieldCode, "CurrencyAmount"},
631  {sfOwner.fieldCode, "AccountAddress"},
632  {sfRegularKey.fieldCode, "AccountAddress"},
633  {sfSendMax.fieldCode, "CurrencyAmount"},
634  {sfTakerGets.fieldCode, "CurrencyAmount"},
635  {sfTakerGetsCurrency.fieldCode, "Currency"},
636  {sfTakerPays.fieldCode, "CurrencyAmount"},
637  {sfTakerPaysCurrency.fieldCode, "Currency"},
638  };
639  // clang-format on
640  if (messageMap.count(sField->fieldCode))
641  {
642  pbuf::Descriptor const* const entry2Desc =
643  fieldDesc->message_type();
644 
645  if (entry2Desc == nullptr)
646  {
647  fail(
648  std::string("Unexpected gRPC. ") + fieldDesc->name() +
649  " MESSAGE with null Descriptor",
650  __FILE__,
651  __LINE__);
652  return;
653  }
654 
655  // The Descriptor name should match the messageMap name.
656  if (messageMap.at(sField->fieldCode) != entry2Desc->name())
657  {
658  fail(
659  std::string(
660  "Internal test error. Mismatch between SField '") +
661  sField->getName() + "' and gRPC Descriptor name '" +
662  entry2Desc->name() + "'",
663  __FILE__,
664  __LINE__);
665  return;
666  }
667  pass();
668 
669  // Recurse to the next lower Descriptor.
670  validateDescriptor(entry2Desc, sField);
671  }
672  return;
673  }
674 
675  fail(
676  std::string("Internal test error. Unhandled FieldDescriptor '") +
677  entryDesc->name() + "' has type `" + fieldDesc->type_name() +
678  "` and label " + std::to_string(fieldDesc->label()),
679  __FILE__,
680  __LINE__);
681  }
682 
683  // Compare a repeated protobuf FieldDescriptor to an SField.
684  void
686  google::protobuf::FieldDescriptor const* const fieldDesc,
687  SField const* const sField)
688  {
689  // Create namespace aliases for shorter names.
690  namespace pbuf = google::protobuf;
691 
692  pbuf::Descriptor const* const entryDesc = fieldDesc->message_type();
693  if (entryDesc == nullptr)
694  {
695  fail(
696  std::string("Expected Descriptor for repeated type ") +
697  sField->getName(),
698  __FILE__,
699  __LINE__);
700  return;
701  }
702 
703  // The following repeated types provide no further structure for their
704  // in-ledger representation. We just have to trust that the gRPC
705  // representation is reasonable for what the ledger implements.
706  static const std::set<std::string> noFurtherDetail{{sfPaths.getName()}};
707 
708  if (noFurtherDetail.count(sField->getName()))
709  {
710  // There is no Format representation for further details of this
711  // repeated type. We've done the best we can.
712  pass();
713  return;
714  }
715 
716  // All of the repeated types that the test currently supports.
717  static const std::map<std::string, SField const*> repeatsWhat{
723  {sfMemos.getName(), &sfMemo},
725  {sfSigners.getName(), &sfSigner}};
726 
727  if (!repeatsWhat.count(sField->getName()))
728  {
729  fail(
730  std::string("Unexpected repeated type ") + fieldDesc->name(),
731  __FILE__,
732  __LINE__);
733  return;
734  }
735  pass();
736 
737  // Process the type contained by the repeated type.
738  validateDescriptor(entryDesc, repeatsWhat.at(sField->getName()));
739  }
740 
741  // Determine which of the Descriptor validators to dispatch to.
742  void
744  google::protobuf::Descriptor const* const entryDesc,
745  SField const* const sField)
746  {
747  if (entryDesc->nested_type_count() != 0 ||
748  entryDesc->enum_type_count() != 0 ||
749  entryDesc->extension_range_count() != 0 ||
750  entryDesc->reserved_range_count() != 0)
751  {
752  fail(
753  std::string("Protobuf Descriptor '") + entryDesc->name() +
754  "' uses unsupported protobuf features",
755  __FILE__,
756  __LINE__);
757  return;
758  }
759 
760  // Dispatch to the correct validator
761  if (entryDesc->oneof_decl_count() > 0)
762  return validateOneOfDescriptor(entryDesc, sField);
763 
764  if (entryDesc->field_count() > 1)
765  return validateMultiFieldDescriptor(entryDesc, sField);
766 
767  return validateOneDescriptor(entryDesc, sField);
768  }
769 
770  // Compare a protobuf descriptor to a KnownFormat::Item
771  template <typename FmtType>
772  void
774  google::protobuf::Descriptor const* const pbufDescriptor,
775  google::protobuf::Descriptor const* const commonFields,
776  typename KnownFormats<FmtType>::Item const* const knownFormatItem)
777  {
778  // Create namespace aliases for shorter names.
779  namespace pbuf = google::protobuf;
780 
781  // The names should usually be the same, but the bpufDescriptor
782  // name might have "Object" appended.
783  if (knownFormatItem->getName() != pbufDescriptor->name() &&
784  knownFormatItem->getName() + "Object" != pbufDescriptor->name())
785  {
786  fail(
787  std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
788  "' and KnownFormat::Item '" + knownFormatItem->getName() +
789  "' don't have the same name",
790  __FILE__,
791  __LINE__);
792  return;
793  }
794  pass();
795 
796  // Create a map we can use use to correlate each field in the
797  // gRPC Descriptor to its corresponding SField.
799  knownFormatItem->getSOTemplate(), knownFormatItem->getType());
800 
801  // Compare the SFields to the FieldDescriptor->Descriptors.
803  pbufDescriptor,
804  commonFields,
805  knownFormatItem->getName(),
806  std::move(sFields));
807  }
808 
809  template <typename FmtType>
810  void
812  KnownFormats<FmtType> const& knownFormat,
813  std::string const& knownFormatName,
814  google::protobuf::Descriptor const* const commonFields,
815  google::protobuf::OneofDescriptor const* const oneOfDesc)
816  {
817  // Create namespace aliases for shorter names.
818  namespace grpc = org::xrpl::rpc::v1;
819  namespace pbuf = google::protobuf;
820 
821  if (!BEAST_EXPECT(oneOfDesc != nullptr))
822  return;
823 
824  // Get corresponding names for all KnownFormat Items.
826  formatTypes;
827 
828  for (auto const& item : knownFormat)
829  {
830  if constexpr (std::is_same_v<FmtType, LedgerEntryType>)
831  {
832  // Skip LedgerEntryTypes that gRPC does not currently support.
833  static constexpr std::array<LedgerEntryType, 0> notSupported{};
834 
835  if (std::find(
836  notSupported.begin(),
837  notSupported.end(),
838  item.getType()) != notSupported.end())
839  continue;
840  }
841 
842  if constexpr (std::is_same_v<FmtType, TxType>)
843  {
844  // Skip TxTypes that gRPC does not currently support.
845  static constexpr std::array notSupported{
847 
848  if (std::find(
849  notSupported.begin(),
850  notSupported.end(),
851  item.getType()) != notSupported.end())
852  continue;
853  }
854 
855  BEAST_EXPECT(
856  formatTypes
857  .insert({formatNameToEntryTypeName(item.getName()), &item})
858  .second == true);
859  }
860 
861  // Verify that the OneOf objects match. Start by comparing
862  // KnownFormat vs gRPC OneOf counts.
863  {
864  BEAST_EXPECT(formatTypes.size() == oneOfDesc->field_count());
865  }
866 
867  // This loop
868  // 1. Iterates through the gRPC OneOfs,
869  // 2. Finds each gRPC OneOf's matching KnownFormat::Item,
870  // 3. Sanity checks that the fields of the objects align well.
871  for (auto i = 0; i < oneOfDesc->field_count(); ++i)
872  {
873  pbuf::FieldDescriptor const* const fieldDesc = oneOfDesc->field(i);
874 
875  // The Field should be a TYPE_MESSAGE, which means we can get its
876  // descriptor.
877  if (fieldDesc->type() != fieldTYPE_MESSAGE)
878  {
879  fail(
880  std::string("gRPC OneOf '") + fieldDesc->name() +
881  "' is not TYPE_MESSAGE",
882  __FILE__,
883  __LINE__);
884  continue;
885  }
886 
887  auto const fmtIter = formatTypes.find(fieldDesc->name());
888 
889  if (fmtIter == formatTypes.cend())
890  {
891  fail(
892  std::string("gRPC OneOf '") + fieldDesc->name() +
893  "' not found in " + knownFormatName,
894  __FILE__,
895  __LINE__);
896  continue;
897  }
898 
899  // Validate that the gRPC and KnownFormat fields align.
900  validateFields<FmtType>(
901  fieldDesc->message_type(), commonFields, fmtIter->second);
902 
903  // Remove the checked KnownFormat from the map. This way we
904  // can check for leftovers when we're done processing.
905  formatTypes.erase(fieldDesc->name());
906  }
907 
908  // Report any KnownFormats that don't have gRPC OneOfs.
909  for (auto const& spare : formatTypes)
910  {
911  fail(
912  knownFormatName + " '" + spare.second->getName() +
913  "' does not have a corresponding gRPC OneOf",
914  __FILE__,
915  __LINE__);
916  }
917  }
918 
919 public:
920  void
922  {
923  testcase("Ledger object validation");
924 
925  org::xrpl::rpc::v1::LedgerObject const ledgerObject;
926 
929  "LedgerFormats",
930  ledgerObject.GetDescriptor(),
931  ledgerObject.GetDescriptor()->FindOneofByName("object"));
932 
933  return;
934  }
935 
936  void
938  {
939  testcase("Transaction validation");
940 
941  org::xrpl::rpc::v1::Transaction const txData;
942 
945  "TxFormats",
946  txData.GetDescriptor(),
947  txData.GetDescriptor()->FindOneofByName("transaction_data"));
948 
949  return;
950  }
951 
952  void
953  run() override
954  {
957  }
958 };
959 
960 BEAST_DEFINE_TESTSUITE(KnownFormatToGRPC, protocol, ripple);
961 
962 } // namespace ripple
ripple::sfRegularKey
const SF_Account sfRegularKey(access, STI_ACCOUNT, 8, "RegularKey")
Definition: SField.h:488
ripple::KnownFormatToGRPC_test::formatNameToEntryTypeName
static std::string formatNameToEntryTypeName(std::string const &fmtName)
Definition: KnownFormatToGRPC_test.cpp:64
std::string
STL class.
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::KnownFormatToGRPC_test::validateOneOfDescriptor
void validateOneOfDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:356
ripple::KnownFormatToGRPC_test::validateDescriptor
void validateDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:743
ripple::sfLedgerEntryType
const SF_U16 sfLedgerEntryType(access, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never)
Definition: SField.h:346
ripple::sfLedgerIndex
const SF_U256 sfLedgerIndex(access, STI_HASH256, 6, "LedgerIndex")
Definition: SField.h:424
std::string_view
STL class.
ripple::sfSigners
const SField sfSigners(access, STI_ARRAY, 3, "Signers", SField::sMD_Default, SField::notSigning)
Definition: SField.h:517
ripple::KnownFormats::Item::getName
std::string const & getName() const
Retrieve the name of the format.
Definition: KnownFormats.h:64
ripple::InnerObjectFormats::findSOTemplateBySField
SOTemplate const * findSOTemplateBySField(SField const &sField) const
Definition: InnerObjectFormats.cpp:64
ripple::sfOperationLimit
const SF_U32 sfOperationLimit(access, STI_UINT32, 29, "OperationLimit")
Definition: SField.h:382
std::string::reserve
T reserve(T... args)
std::vector
STL class.
std::find_if
T find_if(T... args)
std::string::size
T size(T... args)
ripple::STI_AMOUNT
@ STI_AMOUNT
Definition: SField.h:64
ripple::STI_UINT8
@ STI_UINT8
Definition: SField.h:72
ripple::SerializedTypeID
SerializedTypeID
Definition: SField.h:52
ripple::sfAccount
const SF_Account sfAccount(access, STI_ACCOUNT, 1, "Account")
Definition: SField.h:481
ripple::sfMajorities
const SField sfMajorities(access, STI_ARRAY, 16, "Majorities")
Definition: SField.h:524
ripple::KnownFormatToGRPC_test::validateOneDescriptor
void validateOneDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:536
ripple::sfWalletLocator
const SF_U256 sfWalletLocator(access, STI_HASH256, 7, "WalletLocator")
Definition: SField.h:425
ripple::sfTakerPays
const SF_Amount sfTakerPays(access, STI_AMOUNT, 4, "TakerPays")
Definition: SField.h:444
ripple::KnownFormatToGRPC_test::fieldTYPE_MESSAGE
static constexpr auto fieldTYPE_MESSAGE
Definition: KnownFormatToGRPC_test.cpp:57
ripple::SOElement
An element in a SOTemplate.
Definition: SOTemplate.h:42
ripple::KnownFormatToGRPC_test::testLedgerObjectGRPCOneOfs
void testLedgerObjectGRPCOneOfs()
Definition: KnownFormatToGRPC_test.cpp:921
ripple::STI_ACCOUNT
@ STI_ACCOUNT
Definition: SField.h:66
ripple::ltLEDGER_HASHES
@ ltLEDGER_HASHES
Definition: LedgerFormats.h:74
ripple::STI_HASH160
@ STI_HASH160
Definition: SField.h:73
ripple::sfSigner
const SField sfSigner(access, STI_OBJECT, 16, "Signer")
Definition: SField.h:510
ripple::sfDisabledValidators
const SField sfDisabledValidators(access, STI_ARRAY, 17, "DisabledValidators")
Definition: SField.h:525
ripple::sfTakerGetsCurrency
const SF_U160 sfTakerGetsCurrency(access, STI_HASH160, 3, "TakerGetsCurrency")
Definition: SField.h:415
ripple::sfAmount
const SF_Amount sfAmount(access, STI_AMOUNT, 1, "Amount")
Definition: SField.h:441
ripple::sfSignerEntries
const SField sfSignerEntries(access, STI_ARRAY, 4, "SignerEntries")
Definition: SField.h:518
ripple::sfHighLimit
const SF_Amount sfHighLimit(access, STI_AMOUNT, 7, "HighLimit")
Definition: SField.h:447
ripple::KnownFormatToGRPC_test::validateDescriptorAgainstSFields
void validateDescriptorAgainstSFields(google::protobuf::Descriptor const *const pbufDescriptor, google::protobuf::Descriptor const *const commonFields, std::string const &knownFormatName, std::map< std::string, SField const * > &&sFields)
Definition: KnownFormatToGRPC_test.cpp:226
ripple::sfLowLimit
const SF_Amount sfLowLimit(access, STI_AMOUNT, 6, "LowLimit")
Definition: SField.h:446
ripple::operator==
bool operator==(Manifest const &lhs, Manifest const &rhs)
Definition: Manifest.h:155
ripple::sfMajority
const SField sfMajority(access, STI_OBJECT, 18, "Majority")
Definition: SField.h:511
ripple::KnownFormatToGRPC_test::soTemplateToSFields
static std::map< std::string, SField const * > soTemplateToSFields(SOTemplate const &soTemplate, [[maybe_unused]] KeyType fmtId)
Definition: KnownFormatToGRPC_test.cpp:86
ripple::KnownFormatToGRPC_test::fieldTYPE_BYTES
static constexpr auto fieldTYPE_BYTES
Definition: KnownFormatToGRPC_test.cpp:51
ripple::ttFEE
@ ttFEE
Definition: TxFormats.h:62
std::string::push_back
T push_back(T... args)
ripple::SField::fieldType
const SerializedTypeID fieldType
Definition: SField.h:127
ripple::KnownFormats::Item
A known format.
Definition: KnownFormats.h:44
ripple::sfOwner
const SF_Account sfOwner(access, STI_ACCOUNT, 2, "Owner")
Definition: SField.h:482
ripple::SOTemplate
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:81
ripple::TxFormats::getInstance
static TxFormats const & getInstance()
Definition: TxFormats.cpp:277
ripple::KnownFormats
Manages a list of known formats.
Definition: KnownFormats.h:39
ripple::operator<
bool operator<(CanonicalTXSet::Key const &lhs, CanonicalTXSet::Key const &rhs)
Definition: CanonicalTXSet.cpp:25
ripple::sfSendMax
const SF_Amount sfSendMax(access, STI_AMOUNT, 9, "SendMax")
Definition: SField.h:449
ripple::sfAmendment
const SF_U256 sfAmendment(access, STI_HASH256, 19, "Amendment")
Definition: SField.h:433
ripple::KnownFormatToGRPC_test::validateMultiFieldDescriptor
void validateMultiFieldDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:407
std::to_string
T to_string(T... args)
std::array
STL class.
ripple::STI_VL
@ STI_VL
Definition: SField.h:65
ripple::STI_UINT16
@ STI_UINT16
Definition: SField.h:59
ripple::sfTransactionType
const SF_U16 sfTransactionType(access, STI_UINT16, 2, "TransactionType")
Definition: SField.h:347
ripple::STI_HASH256
@ STI_HASH256
Definition: SField.h:63
ripple::KnownFormatToGRPC_test::run
void run() override
Definition: KnownFormatToGRPC_test.cpp:953
std::map::erase
T erase(T... args)
ripple::sfExchangeRate
const SF_U64 sfExchangeRate(access, STI_UINT64, 6, "ExchangeRate")
Definition: SField.h:402
ripple::KnownFormats::Item::getSOTemplate
SOTemplate const & getSOTemplate() const
Definition: KnownFormats.h:78
ripple::sfSignerEntry
const SField sfSignerEntry(access, STI_OBJECT, 11, "SignerEntry")
Definition: SField.h:509
ripple::SField::fieldCode
const int fieldCode
Definition: SField.h:126
ripple::KnownFormatToGRPC_test::validateRepeatedField
void validateRepeatedField(google::protobuf::FieldDescriptor const *const fieldDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:685
ripple::KnownFormatToGRPC_test::fieldTYPE_STRING
static constexpr auto fieldTYPE_STRING
Definition: KnownFormatToGRPC_test.cpp:54
ripple::ttAMENDMENT
@ ttAMENDMENT
Definition: TxFormats.h:61
map
ripple::sfIndexes
const SF_Vec256 sfIndexes(access, STI_VECTOR256, 1, "Indexes", SField::sMD_Never)
Definition: SField.h:494
ripple::sfFirstLedgerSequence
const SF_U32 sfFirstLedgerSequence(access, STI_UINT32, 26, "FirstLedgerSequence")
Definition: SField.h:379
ripple::sfFee
const SF_Amount sfFee(access, STI_AMOUNT, 8, "Fee")
Definition: SField.h:448
ripple::KeyType
KeyType
Definition: KeyType.h:28
ripple::LedgerFormats::getInstance
static LedgerFormats const & getInstance()
Definition: LedgerFormats.cpp:240
ripple::sfMemo
const SField sfMemo(access, STI_OBJECT, 10, "Memo")
Definition: SField.h:508
ripple::sfHashes
const SF_Vec256 sfHashes(access, STI_VECTOR256, 2, "Hashes")
Definition: SField.h:495
ripple::STI_UINT32
@ STI_UINT32
Definition: SField.h:60
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::sfWalletSize
const SF_U32 sfWalletSize(access, STI_UINT32, 12, "WalletSize")
Definition: SField.h:364
ripple::sfBalance
const SF_Amount sfBalance(access, STI_AMOUNT, 2, "Balance")
Definition: SField.h:442
ripple::sfTakerPaysCurrency
const SF_U160 sfTakerPaysCurrency(access, STI_HASH160, 1, "TakerPaysCurrency")
Definition: SField.h:413
ripple::SField
Identifies fields.
Definition: SField.h:109
ripple::STI_UINT64
@ STI_UINT64
Definition: SField.h:61
std::vector::begin
T begin(T... args)
ripple::SField::getName
std::string const & getName() const
Definition: SField.h:172
ripple::STI_HASH128
@ STI_HASH128
Definition: SField.h:62
std::map::insert
T insert(T... args)
ripple::KnownFormatToGRPC_test::validateFields
void validateFields(google::protobuf::Descriptor const *const pbufDescriptor, google::protobuf::Descriptor const *const commonFields, typename KnownFormats< FmtType >::Item const *const knownFormatItem)
Definition: KnownFormatToGRPC_test.cpp:773
ripple::KnownFormats::Item::getType
KeyType getType() const
Retrieve the transaction type this format represents.
Definition: KnownFormats.h:72
ripple::KnownFormatToGRPC_test::fieldTYPE_UINT64
static constexpr auto fieldTYPE_UINT64
Definition: KnownFormatToGRPC_test.cpp:48
ripple::sfPaths
const SField sfPaths(access, STI_PATHSET, 1, "Paths")
Definition: SField.h:491
ripple::sfMemos
const SField sfMemos(access, STI_ARRAY, 9, "Memos")
Definition: SField.h:523
cctype
ripple::sfTakerGets
const SF_Amount sfTakerGets(access, STI_AMOUNT, 5, "TakerGets")
Definition: SField.h:445
std::remove_const
ripple::ttUNL_MODIFY
@ ttUNL_MODIFY
Definition: TxFormats.h:63
ripple::sfDestination
const SF_Account sfDestination(access, STI_ACCOUNT, 3, "Destination")
Definition: SField.h:483
std::size_t
ripple::InnerObjectFormats::getInstance
static InnerObjectFormats const & getInstance()
Definition: InnerObjectFormats.cpp:57
std::vector::end
T end(T... args)
ripple::KnownFormatToGRPC_test::fieldTYPE_UINT32
static constexpr auto fieldTYPE_UINT32
Definition: KnownFormatToGRPC_test.cpp:45
ripple::ltDIR_NODE
@ ltDIR_NODE
Directory node.
Definition: LedgerFormats.h:64
ripple::KnownFormatToGRPC_test::testTransactionGRPCOneOfs
void testTransactionGRPCOneOfs()
Definition: KnownFormatToGRPC_test.cpp:937
ripple::STI_OBJECT
@ STI_OBJECT
Definition: SField.h:68
ripple::sfAmendments
const SF_Vec256 sfAmendments(access, STI_VECTOR256, 3, "Amendments")
Definition: SField.h:496
ripple::sfAuthorize
const SF_Account sfAuthorize(access, STI_ACCOUNT, 5, "Authorize")
Definition: SField.h:485
ripple::ltACCOUNT_ROOT
@ ltACCOUNT_ROOT
Definition: LedgerFormats.h:53
ripple::KnownFormatToGRPC_test::testKnownFormats
void testKnownFormats(KnownFormats< FmtType > const &knownFormat, std::string const &knownFormatName, google::protobuf::Descriptor const *const commonFields, google::protobuf::OneofDescriptor const *const oneOfDesc)
Definition: KnownFormatToGRPC_test.cpp:811
ripple::KnownFormatToGRPC_test
Definition: KnownFormatToGRPC_test.cpp:42
type_traits
ripple::sfLedgerHash
const SF_U256 sfLedgerHash(access, STI_HASH256, 1, "LedgerHash")
Definition: SField.h:419
std::set
STL class.
ripple::sfPreviousTxnID
const SF_U256 sfPreviousTxnID(access, STI_HASH256, 5, "PreviousTxnID", SField::sMD_DeleteFinal)
Definition: SField.h:423
ripple::sfDisabledValidator
const SField sfDisabledValidator(access, STI_OBJECT, 19, "DisabledValidator")
Definition: SField.h:512
string
ripple::sfDomain
const SF_Blob sfDomain(access, STI_VL, 7, "Domain")
Definition: SField.h:463