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 = [this, &sFields, &knownFormatName](
239  pbuf::FieldDescriptor const* const
240  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"); i != std::string::npos)
255  {
256  name[i + 1] = 'N';
257  name[i + 2] = 'L';
258  }
259 
260  // The ledger gives the NFT part of NFToken all caps.
261  // Adapt to that.
262  if (size_t const i = name.find("Nft"); i != std::string::npos)
263  {
264  name[i + 1] = 'F';
265  name[i + 2] = 'T';
266  }
267 
268  if (!sFields.count(name))
269  {
270  fail(
271  std::string("Repeated Protobuf Descriptor '") + name +
272  "' expected in KnownFormat '" + knownFormatName +
273  "' and not found",
274  __FILE__,
275  __LINE__);
276  return;
277  }
278  pass();
279 
280  validateRepeatedField(fieldDesc, sFields.at(name));
281  }
282  else
283  {
284  // Non-repeated handling.
285  pbuf::Descriptor const* const entryDesc =
286  fieldDesc->message_type();
287  if (entryDesc == nullptr)
288  return;
289 
290  name = entryDesc->name();
291  if (!sFields.count(name))
292  {
293  fail(
294  std::string("Protobuf Descriptor '") +
295  entryDesc->name() + "' expected in KnownFormat '" +
296  knownFormatName + "' and not found",
297  __FILE__,
298  __LINE__);
299  return;
300  }
301  pass();
302 
303  validateDescriptor(entryDesc, sFields.at(entryDesc->name()));
304  }
305  // Remove the validated field from the map so we can tell if
306  // there are left over fields at the end of all comparisons.
307  sFields.erase(name);
308  };
309 
310  // Compare the SFields to the FieldDescriptor->Descriptors.
311  for (int i = 0; i < pbufDescriptor->field_count(); ++i)
312  {
313  pbuf::FieldDescriptor const* const fieldDesc =
314  pbufDescriptor->field(i);
315  if (fieldDesc == nullptr || fieldDesc->type() != fieldTYPE_MESSAGE)
316  continue;
317 
318  checkFieldDesc(fieldDesc);
319  }
320 
321  // Now all of the OneOf-specific fields have been removed from
322  // sFields. But there may be common fields left in there. Process
323  // the commonFields next.
324  if (commonFields)
325  {
326  for (int i = 0; i < commonFields->field_count(); ++i)
327  {
328  // If the field we picked up is a OneOf, skip it. Common
329  // fields are never OneOfs.
330  pbuf::FieldDescriptor const* const fieldDesc =
331  commonFields->field(i);
332 
333  if (fieldDesc == nullptr ||
334  fieldDesc->containing_oneof() != nullptr ||
335  fieldDesc->type() != fieldTYPE_MESSAGE)
336  continue;
337 
338  checkFieldDesc(fieldDesc);
339  }
340  }
341 
342  // All SFields in the KnownFormat have corresponding gRPC fields
343  // if the sFields map is now empty.
344  if (!sFields.empty())
345  {
346  fail(
347  std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
348  "' did not account for all fields in KnownFormat '" +
349  knownFormatName + "'. Left over field: `" +
350  sFields.begin()->first + "'",
351  __FILE__,
352  __LINE__);
353  return;
354  }
355  pass();
356  }
357 
358  // Compare a protobuf descriptor with multiple oneOfFields to choose from
359  // to an SField.
360  void
362  google::protobuf::Descriptor const* const entryDesc,
363  SField const* const sField)
364  {
365  // Create namespace aliases for shorter names.
366  namespace pbuf = google::protobuf;
367 
368  // Note that it's not okay to compare names because SFields and
369  // gRPC do not always agree on the names.
370  if (entryDesc->field_count() == 0 || entryDesc->oneof_decl_count() != 1)
371  {
372  fail(
373  std::string("Protobuf Descriptor '") + entryDesc->name() +
374  "' expected to have multiple OneOf fields and nothing else",
375  __FILE__,
376  __LINE__);
377  return;
378  }
379 
380  pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
381  if (fieldDesc == nullptr)
382  {
383  fail(
384  std::string("Internal test failure. Unhandled nullptr "
385  "in FieldDescriptor for '") +
386  entryDesc->name() + "'",
387  __FILE__,
388  __LINE__);
389  return;
390  }
391 
392  // Special handling for CurrencyAmount
393  if (sField->fieldType == STI_AMOUNT &&
394  entryDesc->name() == "CurrencyAmount")
395  {
396  // SFields of type STI_AMOUNT are represented in gRPC by a
397  // multi-field CurrencyAmount. We don't really learn anything
398  // by diving into the interior of CurrencyAmount, so we stop here
399  // and call it good.
400  pass();
401  return;
402  }
403 
404  fail(
405  std::string("Unhandled OneOf Protobuf Descriptor '") +
406  entryDesc->name() + "'",
407  __FILE__,
408  __LINE__);
409  }
410 
411  void
413  google::protobuf::Descriptor const* const entryDesc,
414  SField const* const sField)
415  {
416  // Create namespace aliases for shorter names.
417  namespace pbuf = google::protobuf;
418 
419  if (entryDesc->field_count() <= 1 || entryDesc->oneof_decl_count() != 0)
420  {
421  fail(
422  std::string("Protobuf Descriptor '") + entryDesc->name() +
423  "' expected to have multiple fields and nothing else",
424  __FILE__,
425  __LINE__);
426  return;
427  }
428 
429  // There are composite fields that the SFields handle differently
430  // from gRPC. Handle those here.
431  {
432  struct FieldContents
433  {
434  std::string_view fieldName;
435  google::protobuf::FieldDescriptor::Type fieldType;
436 
437  bool
438  operator<(FieldContents const& other) const
439  {
440  return this->fieldName < other.fieldName;
441  }
442 
443  bool
444  operator==(FieldContents const& other) const
445  {
446  return this->fieldName == other.fieldName &&
447  this->fieldType == other.fieldType;
448  }
449  };
450 
451  struct SpecialEntry
452  {
453  std::string_view const descriptorName;
454  SerializedTypeID const sFieldType;
455  std::set<FieldContents> const fields;
456  };
457 
458  // clang-format off
459  static const std::array specialEntries{
460  SpecialEntry{
461  "Currency", STI_UINT160,
462  {
463  {"name", fieldTYPE_STRING},
464  {"code", fieldTYPE_BYTES}
465  }
466  },
467  SpecialEntry{
468  "Memo", STI_OBJECT,
469  {
470  {"memo_data", fieldTYPE_BYTES},
471  {"memo_format", fieldTYPE_BYTES},
472  {"memo_type", fieldTYPE_BYTES}
473  }
474  }
475  };
476  // clang-format on
477 
478  // If we're handling a SpecialEntry...
479  if (auto const iter = std::find_if(
480  specialEntries.begin(),
481  specialEntries.end(),
482  [entryDesc, sField](SpecialEntry const& entry) {
483  return entryDesc->name() == entry.descriptorName &&
484  sField->fieldType == entry.sFieldType;
485  });
486  iter != specialEntries.end())
487  {
488  // Verify the SField.
489  if (!BEAST_EXPECT(sField->fieldType == iter->sFieldType))
490  return;
491 
492  // Verify all of the fields in the entryDesc.
493  if (!BEAST_EXPECT(
494  entryDesc->field_count() == iter->fields.size()))
495  return;
496 
497  for (int i = 0; i < entryDesc->field_count(); ++i)
498  {
499  pbuf::FieldDescriptor const* const fieldDesc =
500  entryDesc->field(i);
501 
502  FieldContents const contents{
503  fieldDesc->name(), fieldDesc->type()};
504 
505  if (!BEAST_EXPECT(
506  iter->fields.find(contents) != iter->fields.end()))
507  return;
508  }
509 
510  // This field is good.
511  pass();
512  return;
513  }
514  }
515 
516  // If the field was not one of the SpecialEntries, we expect it to be
517  // an InnerObjectFormat.
518  SOTemplate const* const innerFormat =
520  if (innerFormat == nullptr)
521  {
522  fail(
523  "SOTemplate for field '" + sField->getName() + "' not found",
524  __FILE__,
525  __LINE__);
526  return;
527  }
528 
529  // Create a map we can use use to correlate each field in the
530  // gRPC Descriptor to its corresponding SField.
532  soTemplateToSFields(*innerFormat, 0);
533 
534  // Compare the SFields to the FieldDescriptor->Descriptors.
536  entryDesc, nullptr, sField->getName(), std::move(sFields));
537  }
538 
539  // Compare a protobuf descriptor with only one field to an SField.
540  void
542  google::protobuf::Descriptor const* const entryDesc,
543  SField const* const sField)
544  {
545  // Create namespace aliases for shorter names.
546  namespace pbuf = google::protobuf;
547 
548  // Note that it's not okay to compare names because SFields and
549  // gRPC do not always agree on the names.
550  if (entryDesc->field_count() != 1 || entryDesc->oneof_decl_count() != 0)
551  {
552  fail(
553  std::string("Protobuf Descriptor '") + entryDesc->name() +
554  "' expected to be one field and nothing else",
555  __FILE__,
556  __LINE__);
557  return;
558  }
559 
560  pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
561  if (fieldDesc == nullptr)
562  {
563  fail(
564  std::string("Internal test failure. Unhandled nullptr "
565  "in FieldDescriptor for '") +
566  entryDesc->name() + "'",
567  __FILE__,
568  __LINE__);
569  return;
570  }
571 
572  // Create a map from SerializedTypeID to pbuf::FieldDescriptor::Type.
573  //
574  // This works for most, but not all, types because of divergence
575  // between the gRPC and LedgerFormat implementations. We deal
576  // with the special cases later.
577  // clang-format off
579  sTypeToFieldDescType{
583 
585 
587 
593  };
594  //clang-format on
595 
596  // If the SField and FieldDescriptor::Type correlate we're good.
597  if (auto const iter = sTypeToFieldDescType.find(sField->fieldType);
598  iter != sTypeToFieldDescType.end() &&
599  iter->second == fieldDesc->type())
600  {
601  pass();
602  return;
603  }
604 
605  // Handle special cases for specific SFields.
607  sFieldCodeToFieldDescType{
611 
612  if (auto const iter = sFieldCodeToFieldDescType.find(sField->fieldCode);
613  iter != sFieldCodeToFieldDescType.end() &&
614  iter->second == fieldDesc->type())
615  {
616  pass();
617  return;
618  }
619 
620  // Special handling for all Message types.
621  if (fieldDesc->type() == fieldTYPE_MESSAGE)
622  {
623  // We need to recurse to get to the bottom of the field(s)
624  // in question.
625 
626  // Start by identifying which fields we need to be handling.
627  // clang-format off
628  static const std::map<int, std::string> messageMap{
629  {sfAccount.fieldCode, "AccountAddress"},
630  {sfAmount.fieldCode, "CurrencyAmount"},
631  {sfAuthorize.fieldCode, "AccountAddress"},
632  {sfBalance.fieldCode, "CurrencyAmount"},
633  {sfDestination.fieldCode, "AccountAddress"},
634  {sfFee.fieldCode, "XRPDropsAmount"},
635  {sfHighLimit.fieldCode, "CurrencyAmount"},
636  {sfLowLimit.fieldCode, "CurrencyAmount"},
637  {sfOwner.fieldCode, "AccountAddress"},
638  {sfRegularKey.fieldCode, "AccountAddress"},
639  {sfSendMax.fieldCode, "CurrencyAmount"},
640  {sfTakerGets.fieldCode, "CurrencyAmount"},
641  {sfTakerGetsCurrency.fieldCode, "Currency"},
642  {sfTakerPays.fieldCode, "CurrencyAmount"},
643  {sfTakerPaysCurrency.fieldCode, "Currency"},
644  };
645  // clang-format on
646  if (messageMap.count(sField->fieldCode))
647  {
648  pbuf::Descriptor const* const entry2Desc =
649  fieldDesc->message_type();
650 
651  if (entry2Desc == nullptr)
652  {
653  fail(
654  std::string("Unexpected gRPC. ") + fieldDesc->name() +
655  " MESSAGE with null Descriptor",
656  __FILE__,
657  __LINE__);
658  return;
659  }
660 
661  // The Descriptor name should match the messageMap name.
662  if (messageMap.at(sField->fieldCode) != entry2Desc->name())
663  {
664  fail(
665  std::string(
666  "Internal test error. Mismatch between SField '") +
667  sField->getName() + "' and gRPC Descriptor name '" +
668  entry2Desc->name() + "'",
669  __FILE__,
670  __LINE__);
671  return;
672  }
673  pass();
674 
675  // Recurse to the next lower Descriptor.
676  validateDescriptor(entry2Desc, sField);
677  }
678  return;
679  }
680 
681  fail(
682  std::string("Internal test error. Unhandled FieldDescriptor '") +
683  entryDesc->name() + "' has type `" + fieldDesc->type_name() +
684  "` and label " + std::to_string(fieldDesc->label()),
685  __FILE__,
686  __LINE__);
687  }
688 
689  // Compare a repeated protobuf FieldDescriptor to an SField.
690  void
692  google::protobuf::FieldDescriptor const* const fieldDesc,
693  SField const* const sField)
694  {
695  // Create namespace aliases for shorter names.
696  namespace pbuf = google::protobuf;
697 
698  pbuf::Descriptor const* const entryDesc = fieldDesc->message_type();
699  if (entryDesc == nullptr)
700  {
701  fail(
702  std::string("Expected Descriptor for repeated type ") +
703  sField->getName(),
704  __FILE__,
705  __LINE__);
706  return;
707  }
708 
709  // The following repeated types provide no further structure for their
710  // in-ledger representation. We just have to trust that the gRPC
711  // representation is reasonable for what the ledger implements.
712  static const std::set<std::string> noFurtherDetail{
713  {sfPaths.getName()},
714  };
715 
716  if (noFurtherDetail.count(sField->getName()))
717  {
718  // There is no Format representation for further details of this
719  // repeated type. We've done the best we can.
720  pass();
721  return;
722  }
723 
724  // All of the repeated types that the test currently supports.
725  static const std::map<std::string, SField const*> repeatsWhat{
731  {sfMemos.getName(), &sfMemo},
734  {sfSigners.getName(), &sfSigner},
736 
737  if (!repeatsWhat.count(sField->getName()))
738  {
739  fail(
740  std::string("Unexpected repeated type ") + fieldDesc->name(),
741  __FILE__,
742  __LINE__);
743  return;
744  }
745  pass();
746 
747  // Process the type contained by the repeated type.
748  validateDescriptor(entryDesc, repeatsWhat.at(sField->getName()));
749  }
750 
751  // Determine which of the Descriptor validators to dispatch to.
752  void
754  google::protobuf::Descriptor const* const entryDesc,
755  SField const* const sField)
756  {
757  if (entryDesc->nested_type_count() != 0 ||
758  entryDesc->enum_type_count() != 0 ||
759  entryDesc->extension_range_count() != 0 ||
760  entryDesc->reserved_range_count() != 0)
761  {
762  fail(
763  std::string("Protobuf Descriptor '") + entryDesc->name() +
764  "' uses unsupported protobuf features",
765  __FILE__,
766  __LINE__);
767  return;
768  }
769 
770  // Dispatch to the correct validator
771  if (entryDesc->oneof_decl_count() > 0)
772  return validateOneOfDescriptor(entryDesc, sField);
773 
774  if (entryDesc->field_count() > 1)
775  return validateMultiFieldDescriptor(entryDesc, sField);
776 
777  return validateOneDescriptor(entryDesc, sField);
778  }
779 
780  // Compare a protobuf descriptor to a KnownFormat::Item
781  template <typename FmtType, typename FmtName>
782  void
784  google::protobuf::Descriptor const* const pbufDescriptor,
785  google::protobuf::Descriptor const* const commonFields,
786  typename KnownFormats<FmtType, FmtName>::Item const* const
787  knownFormatItem)
788  {
789  // Create namespace aliases for shorter names.
790  namespace pbuf = google::protobuf;
791 
792  // The names should usually be the same, but the bpufDescriptor
793  // name might have "Object" appended.
794  if (knownFormatItem->getName() != pbufDescriptor->name() &&
795  knownFormatItem->getName() + "Object" != pbufDescriptor->name())
796  {
797  fail(
798  std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
799  "' and KnownFormat::Item '" + knownFormatItem->getName() +
800  "' don't have the same name",
801  __FILE__,
802  __LINE__);
803  return;
804  }
805  pass();
806 
807  // Create a map we can use use to correlate each field in the
808  // gRPC Descriptor to its corresponding SField.
810  knownFormatItem->getSOTemplate(), knownFormatItem->getType());
811 
812  // Compare the SFields to the FieldDescriptor->Descriptors.
814  pbufDescriptor,
815  commonFields,
816  knownFormatItem->getName(),
817  std::move(sFields));
818  }
819 
820  template <typename FmtType, typename FmtName>
821  void
823  KnownFormats<FmtType, FmtName> const& knownFormat,
824  std::string const& knownFormatName,
825  google::protobuf::Descriptor const* const commonFields,
826  google::protobuf::OneofDescriptor const* const oneOfDesc)
827  {
828  // Create namespace aliases for shorter names.
829  namespace grpc = org::xrpl::rpc::v1;
830  namespace pbuf = google::protobuf;
831 
832  if (!BEAST_EXPECT(oneOfDesc != nullptr))
833  return;
834 
835  // Get corresponding names for all KnownFormat Items.
836  std::map<
837  std::string,
838  typename KnownFormats<FmtType, FmtName>::Item const*>
839  formatTypes;
840 
841  for (auto const& item : knownFormat)
842  {
843  if constexpr (std::is_same_v<FmtType, LedgerEntryType>)
844  {
845  // Skip LedgerEntryTypes that gRPC does not currently support.
846  static constexpr std::array<LedgerEntryType, 0> notSupported{};
847 
848  if (std::find(
849  notSupported.begin(),
850  notSupported.end(),
851  item.getType()) != notSupported.end())
852  continue;
853  }
854 
855  if constexpr (std::is_same_v<FmtType, TxType>)
856  {
857  // Skip TxTypes that gRPC does not currently support.
858  static constexpr std::array notSupported{
860 
861  if (std::find(
862  notSupported.begin(),
863  notSupported.end(),
864  item.getType()) != notSupported.end())
865  continue;
866  }
867 
868  BEAST_EXPECT(
869  formatTypes
870  .insert({formatNameToEntryTypeName(item.getName()), &item})
871  .second == true);
872  }
873 
874  // Verify that the OneOf objects match. Start by comparing
875  // KnownFormat vs gRPC OneOf counts.
876  {
877  BEAST_EXPECT(formatTypes.size() == oneOfDesc->field_count());
878  }
879 
880  // This loop
881  // 1. Iterates through the gRPC OneOfs,
882  // 2. Finds each gRPC OneOf's matching KnownFormat::Item,
883  // 3. Sanity checks that the fields of the objects align well.
884  for (auto i = 0; i < oneOfDesc->field_count(); ++i)
885  {
886  pbuf::FieldDescriptor const* const fieldDesc = oneOfDesc->field(i);
887 
888  // The Field should be a TYPE_MESSAGE, which means we can get its
889  // descriptor.
890  if (fieldDesc->type() != fieldTYPE_MESSAGE)
891  {
892  fail(
893  std::string("gRPC OneOf '") + fieldDesc->name() +
894  "' is not TYPE_MESSAGE",
895  __FILE__,
896  __LINE__);
897  continue;
898  }
899 
900  auto const fmtIter = formatTypes.find(fieldDesc->name());
901 
902  if (fmtIter == formatTypes.cend())
903  {
904  fail(
905  std::string("gRPC OneOf '") + fieldDesc->name() +
906  "' not found in " + knownFormatName,
907  __FILE__,
908  __LINE__);
909  continue;
910  }
911 
912  // Validate that the gRPC and KnownFormat fields align.
913  validateFields<FmtType, FmtName>(
914  fieldDesc->message_type(), commonFields, fmtIter->second);
915 
916  // Remove the checked KnownFormat from the map. This way we
917  // can check for leftovers when we're done processing.
918  formatTypes.erase(fieldDesc->name());
919  }
920 
921  // Report any KnownFormats that don't have gRPC OneOfs.
922  for (auto const& spare : formatTypes)
923  {
924  fail(
925  knownFormatName + " '" + spare.second->getName() +
926  "' does not have a corresponding gRPC OneOf",
927  __FILE__,
928  __LINE__);
929  }
930  }
931 
932 public:
933  void
935  {
936  testcase("Ledger object validation");
937 
938  org::xrpl::rpc::v1::LedgerObject const ledgerObject;
939 
942  "LedgerFormats",
943  ledgerObject.GetDescriptor(),
944  ledgerObject.GetDescriptor()->FindOneofByName("object"));
945 
946  return;
947  }
948 
949  void
951  {
952  testcase("Transaction validation");
953 
954  org::xrpl::rpc::v1::Transaction const txData;
955 
958  "TxFormats",
959  txData.GetDescriptor(),
960  txData.GetDescriptor()->FindOneofByName("transaction_data"));
961 
962  return;
963  }
964 
965  void
966  run() override
967  {
970  }
971 };
972 
973 BEAST_DEFINE_TESTSUITE(KnownFormatToGRPC, protocol, ripple);
974 
975 } // namespace ripple
ripple::sfPaths
const SField sfPaths
ripple::KnownFormatToGRPC_test::formatNameToEntryTypeName
static std::string formatNameToEntryTypeName(std::string const &fmtName)
Definition: KnownFormatToGRPC_test.cpp:64
ripple::sfSendMax
const SF_AMOUNT sfSendMax
std::string
STL class.
ripple::sfSigners
const SField sfSigners
ripple::STI_UINT128
@ STI_UINT128
Definition: SField.h:61
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:361
ripple::KnownFormatToGRPC_test::validateDescriptor
void validateDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:753
ripple::sfNFTokenOffers
const SF_VECTOR256 sfNFTokenOffers
std::string_view
STL class.
ripple::sfDestination
const SF_ACCOUNT sfDestination
ripple::sfAmount
const SF_AMOUNT sfAmount
ripple::ltLEDGER_HASHES
@ ltLEDGER_HASHES
A ledger object that contains a list of ledger hashes.
Definition: LedgerFormats.h:102
ripple::sfWalletSize
const SF_UINT32 sfWalletSize
ripple::sfFirstLedgerSequence
const SF_UINT32 sfFirstLedgerSequence
ripple::InnerObjectFormats::findSOTemplateBySField
SOTemplate const * findSOTemplateBySField(SField const &sField) const
Definition: InnerObjectFormats.cpp:71
std::string::reserve
T reserve(T... args)
ripple::sfOwner
const SF_ACCOUNT sfOwner
ripple::sfRegularKey
const SF_ACCOUNT sfRegularKey
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:63
ripple::STI_UINT8
@ STI_UINT8
Definition: SField.h:71
ripple::sfTakerPaysCurrency
const SF_UINT160 sfTakerPaysCurrency
ripple::SerializedTypeID
SerializedTypeID
Definition: SField.h:52
ripple::sfLedgerIndex
const SF_UINT256 sfLedgerIndex
ripple::ttFEE
@ ttFEE
This system-generated transaction type is used to update the network's fee settings.
Definition: TxFormats.h:152
ripple::KnownFormatToGRPC_test::validateOneDescriptor
void validateOneDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:541
ripple::KnownFormatToGRPC_test::fieldTYPE_MESSAGE
static constexpr auto fieldTYPE_MESSAGE
Definition: KnownFormatToGRPC_test.cpp:57
ripple::STI_UINT160
@ STI_UINT160
Definition: SField.h:72
ripple::KnownFormatToGRPC_test::testKnownFormats
void testKnownFormats(KnownFormats< FmtType, FmtName > const &knownFormat, std::string const &knownFormatName, google::protobuf::Descriptor const *const commonFields, google::protobuf::OneofDescriptor const *const oneOfDesc)
Definition: KnownFormatToGRPC_test.cpp:822
ripple::SOElement
An element in a SOTemplate.
Definition: SOTemplate.h:43
ripple::KnownFormatToGRPC_test::testLedgerObjectGRPCOneOfs
void testLedgerObjectGRPCOneOfs()
Definition: KnownFormatToGRPC_test.cpp:934
ripple::STI_ACCOUNT
@ STI_ACCOUNT
Definition: SField.h:65
ripple::KnownFormats::Item::getName
std::string const & getName() const
Retrieve the name of the format.
Definition: KnownFormats.h:65
ripple::ttAMENDMENT
@ ttAMENDMENT
This system-generated transaction type is used to update the status of the various amendments.
Definition: TxFormats.h:146
ripple::sfTakerGetsCurrency
const SF_UINT160 sfTakerGetsCurrency
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::operator==
bool operator==(Manifest const &lhs, Manifest const &rhs)
Definition: Manifest.h:161
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::ltDIR_NODE
@ ltDIR_NODE
A ledger object which contains a list of object identifiers.
Definition: LedgerFormats.h:66
ripple::KnownFormatToGRPC_test::fieldTYPE_BYTES
static constexpr auto fieldTYPE_BYTES
Definition: KnownFormatToGRPC_test.cpp:51
ripple::sfIndexes
const SF_VECTOR256 sfIndexes
std::string::push_back
T push_back(T... args)
ripple::SField::fieldType
const SerializedTypeID fieldType
Definition: SField.h:130
ripple::sfTakerPays
const SF_AMOUNT sfTakerPays
ripple::KnownFormats::Item
A known format.
Definition: KnownFormats.h:45
ripple::sfTransactionType
const SF_UINT16 sfTransactionType
ripple::sfLowLimit
const SF_AMOUNT sfLowLimit
ripple::ttUNL_MODIFY
@ ttUNL_MODIFY
This system-generated transaction type is used to update the network's negative UNL.
Definition: TxFormats.h:158
ripple::SOTemplate
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:82
ripple::sfMemos
const SField sfMemos
ripple::TxFormats::getInstance
static TxFormats const & getInstance()
Definition: TxFormats.cpp:328
ripple::KnownFormats
Manages a list of known formats.
Definition: KnownFormats.h:40
ripple::operator<
bool operator<(CanonicalTXSet::Key const &lhs, CanonicalTXSet::Key const &rhs)
Definition: CanonicalTXSet.cpp:25
ripple::KnownFormatToGRPC_test::validateFields
void validateFields(google::protobuf::Descriptor const *const pbufDescriptor, google::protobuf::Descriptor const *const commonFields, typename KnownFormats< FmtType, FmtName >::Item const *const knownFormatItem)
Definition: KnownFormatToGRPC_test.cpp:783
ripple::sfLedgerHash
const SF_UINT256 sfLedgerHash
ripple::KnownFormatToGRPC_test::validateMultiFieldDescriptor
void validateMultiFieldDescriptor(google::protobuf::Descriptor const *const entryDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:412
std::to_string
T to_string(T... args)
std::array
STL class.
ripple::STI_VL
@ STI_VL
Definition: SField.h:64
ripple::STI_UINT16
@ STI_UINT16
Definition: SField.h:58
ripple::KnownFormatToGRPC_test::run
void run() override
Definition: KnownFormatToGRPC_test.cpp:966
ripple::sfTakerGets
const SF_AMOUNT sfTakerGets
ripple::sfMajority
const SField sfMajority
ripple::KnownFormats::Item::getType
KeyType getType() const
Retrieve the transaction type this format represents.
Definition: KnownFormats.h:73
ripple::sfPreviousTxnID
const SF_UINT256 sfPreviousTxnID
ripple::SField::fieldCode
const int fieldCode
Definition: SField.h:129
ripple::sfAuthorize
const SF_ACCOUNT sfAuthorize
ripple::sfHighLimit
const SF_AMOUNT sfHighLimit
ripple::KnownFormatToGRPC_test::validateRepeatedField
void validateRepeatedField(google::protobuf::FieldDescriptor const *const fieldDesc, SField const *const sField)
Definition: KnownFormatToGRPC_test.cpp:691
ripple::sfExchangeRate
const SF_UINT64 sfExchangeRate
ripple::KnownFormatToGRPC_test::fieldTYPE_STRING
static constexpr auto fieldTYPE_STRING
Definition: KnownFormatToGRPC_test.cpp:54
map
ripple::sfSigner
const SField sfSigner
ripple::sfSignerEntry
const SField sfSignerEntry
ripple::KeyType
KeyType
Definition: KeyType.h:28
ripple::LedgerFormats::getInstance
static LedgerFormats const & getInstance()
Definition: LedgerFormats.cpp:268
ripple::sfNFToken
const SField sfNFToken
ripple::sfNFTokens
const SField sfNFTokens
ripple::sfSignerEntries
const SField sfSignerEntries
ripple::sfOperationLimit
const SF_UINT32 sfOperationLimit
ripple::sfHashes
const SF_VECTOR256 sfHashes
ripple::STI_UINT32
@ STI_UINT32
Definition: SField.h:59
ripple::sfURI
const SF_VL sfURI
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::sfWalletLocator
const SF_UINT256 sfWalletLocator
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
protocol
Definition: ValidatorList.h:38
ripple::SField
Identifies fields.
Definition: SField.h:112
ripple::STI_UINT64
@ STI_UINT64
Definition: SField.h:60
std::vector::begin
T begin(T... args)
ripple::SField::getName
std::string const & getName() const
Definition: SField.h:175
ripple::ltACCOUNT_ROOT
@ ltACCOUNT_ROOT
A ledger object which describes an account.
Definition: LedgerFormats.h:59
std::map::insert
T insert(T... args)
ripple::sfDisabledValidator
const SField sfDisabledValidator
ripple::KnownFormatToGRPC_test::fieldTYPE_UINT64
static constexpr auto fieldTYPE_UINT64
Definition: KnownFormatToGRPC_test.cpp:48
ripple::sfBalance
const SF_AMOUNT sfBalance
cctype
std::remove_const
std::size_t
ripple::sfFee
const SF_AMOUNT sfFee
ripple::sfMemo
const SField sfMemo
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::InnerObjectFormats::getInstance
static InnerObjectFormats const & getInstance()
Definition: InnerObjectFormats.cpp:64
std::vector::end
T end(T... args)
ripple::KnownFormatToGRPC_test::fieldTYPE_UINT32
static constexpr auto fieldTYPE_UINT32
Definition: KnownFormatToGRPC_test.cpp:45
ripple::KnownFormatToGRPC_test::testTransactionGRPCOneOfs
void testTransactionGRPCOneOfs()
Definition: KnownFormatToGRPC_test.cpp:950
ripple::STI_OBJECT
@ STI_OBJECT
Definition: SField.h:67
ripple::sfDomain
const SF_VL sfDomain
ripple::sfAmendment
const SF_UINT256 sfAmendment
ripple::STI_UINT256
@ STI_UINT256
Definition: SField.h:62
ripple::sfMajorities
const SField sfMajorities
ripple::KnownFormatToGRPC_test
Definition: KnownFormatToGRPC_test.cpp:42
ripple::sfDisabledValidators
const SField sfDisabledValidators
type_traits
std::set
STL class.
ripple::KnownFormats::Item::getSOTemplate
SOTemplate const & getSOTemplate() const
Definition: KnownFormats.h:79
ripple::sfAmendments
const SF_VECTOR256 sfAmendments
string