rippled
AmendmentTable.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 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/app/main/Application.h>
21 #include <ripple/app/misc/AmendmentTable.h>
22 #include <ripple/protocol/STValidation.h>
23 #include <ripple/core/DatabaseCon.h>
24 #include <ripple/core/ConfigSections.h>
25 #include <ripple/protocol/jss.h>
26 #include <ripple/protocol/TxFlags.h>
27 #include <boost/format.hpp>
28 #include <boost/regex.hpp>
29 #include <algorithm>
30 #include <mutex>
31 
32 namespace ripple {
33 
34 static
36 parseSection (Section const& section)
37 {
38  static boost::regex const re1 (
39  "^" // start of line
40  "(?:\\s*)" // whitespace (optional)
41  "([abcdefABCDEF0-9]{64})" // <hexadecimal amendment ID>
42  "(?:\\s+)" // whitespace
43  "(\\S+)" // <description>
44  , boost::regex_constants::optimize
45  );
46 
48 
49  for (auto const& line : section.lines ())
50  {
51  boost::smatch match;
52 
53  if (!boost::regex_match (line, match, re1))
54  Throw<std::runtime_error> (
55  "Invalid entry '" + line +
56  "' in [" + section.name () + "]");
57 
58  uint256 id;
59 
60  if (!id.SetHexExact (match[1]))
61  Throw<std::runtime_error> (
62  "Invalid amendment ID '" + match[1] +
63  "' in [" + section.name () + "]");
64 
65  names.push_back (std::make_pair (id, match[2]));
66  }
67 
68  return names;
69 }
70 
76 {
78  bool vetoed = false;
79 
85  bool enabled = false;
86 
88  bool supported = false;
89 
92 
93  explicit AmendmentState () = default;
94 };
95 
98 {
99 private:
100  // How many yes votes each amendment received
102 
103 public:
104  // number of trusted validations
106 
107  // number of votes needed
108  int mThreshold = 0;
109 
110  AmendmentSet () = default;
111 
112  void tally (std::set<uint256> const& amendments)
113  {
115 
116  for (auto const& amendment : amendments)
117  ++votes_[amendment];
118  }
119 
120  int votes (uint256 const& amendment) const
121  {
122  auto const& it = votes_.find (amendment);
123 
124  if (it == votes_.end())
125  return 0;
126 
127  return it->second;
128  }
129 };
130 
131 //------------------------------------------------------------------------------
132 
140  : public AmendmentTable
141 {
142 protected:
144 
147 
148  // Time that an amendment must hold a majority for
150 
151  // The amount of support that an amendment must receive
152  // 0 = 0% and 256 = 100%
153  int const majorityFraction_;
154 
155  // The results of the last voting round - may be empty if
156  // we haven't participated in one yet.
158 
159  // True if an unsupported amendment is enabled
161  // Unset if no unsupported amendments reach majority,
162  // else set to the earliest time an unsupported amendment
163  // will be enabled.
164  boost::optional<NetClock::time_point> firstUnsupportedExpected_;
165 
167 
168  // Finds or creates state
169  AmendmentState* add (uint256 const& amendment);
170 
171  // Finds existing state
172  AmendmentState* get (uint256 const& amendment);
173 
174  void setJson (Json::Value& v, uint256 const& amendment, const AmendmentState&);
175 
176 public:
178  std::chrono::seconds majorityTime,
179  int majorityFraction,
180  Section const& supported,
181  Section const& enabled,
182  Section const& vetoed,
183  beast::Journal journal);
184 
185  uint256 find (std::string const& name) override;
186 
187  bool veto (uint256 const& amendment) override;
188  bool unVeto (uint256 const& amendment) override;
189 
190  bool enable (uint256 const& amendment) override;
191  bool disable (uint256 const& amendment) override;
192 
193  bool isEnabled (uint256 const& amendment) override;
194  bool isSupported (uint256 const& amendment) override;
195 
196  bool hasUnsupportedEnabled () override;
197  boost::optional<NetClock::time_point>
198  firstUnsupportedExpected() override;
199 
200  Json::Value getJson (int) override;
201  Json::Value getJson (uint256 const&) override;
202 
203  bool needValidatedLedger (LedgerIndex seq) override;
204 
205  void
207  LedgerIndex seq,
208  std::set<uint256> const& enabled,
209  majorityAmendments_t const& majority) override;
210 
212  doValidation (std::set<uint256> const& enabledAmendments) override;
213 
215  getDesired () override;
216 
218  doVoting (
219  NetClock::time_point closeTime,
220  std::set<uint256> const& enabledAmendments,
221  majorityAmendments_t const& majorityAmendments,
222  std::vector<STValidation::pointer> const& validations) override;
223 };
224 
225 //------------------------------------------------------------------------------
226 
228  std::chrono::seconds majorityTime,
229  int majorityFraction,
230  Section const& supported,
231  Section const& enabled,
232  Section const& vetoed,
233  beast::Journal journal)
234  : lastUpdateSeq_ (0)
235  , majorityTime_ (majorityTime)
236  , majorityFraction_ (majorityFraction)
237  , unsupportedEnabled_ (false)
238  , j_ (journal)
239 {
240  assert (majorityFraction_ != 0);
241 
242  std::lock_guard sl (mutex_);
243 
244  for (auto const& a : parseSection(supported))
245  {
246  if (auto s = add (a.first))
247  {
248  JLOG (j_.debug()) <<
249  "Amendment " << a.first << " is supported.";
250 
251  if (!a.second.empty ())
252  s->name = a.second;
253 
254  s->supported = true;
255  }
256  }
257 
258  for (auto const& a : parseSection (enabled))
259  {
260  if (auto s = add (a.first))
261  {
262  JLOG (j_.debug()) <<
263  "Amendment " << a.first << " is enabled.";
264 
265  if (!a.second.empty ())
266  s->name = a.second;
267 
268  s->supported = true;
269  s->enabled = true;
270  }
271  }
272 
273  for (auto const& a : parseSection (vetoed))
274  {
275  // Unknown amendments are effectively vetoed already
276  if (auto s = get (a.first))
277  {
278  JLOG (j_.info()) <<
279  "Amendment " << a.first << " is vetoed.";
280 
281  if (!a.second.empty ())
282  s->name = a.second;
283 
284  s->vetoed = true;
285  }
286  }
287 }
288 
290 AmendmentTableImpl::add (uint256 const& amendmentHash)
291 {
292  // call with the mutex held
293  return &amendmentMap_[amendmentHash];
294 }
295 
297 AmendmentTableImpl::get (uint256 const& amendmentHash)
298 {
299  // call with the mutex held
300  auto ret = amendmentMap_.find (amendmentHash);
301 
302  if (ret == amendmentMap_.end())
303  return nullptr;
304 
305  return &ret->second;
306 }
307 
308 uint256
310 {
311  std::lock_guard sl (mutex_);
312 
313  for (auto const& e : amendmentMap_)
314  {
315  if (name == e.second.name)
316  return e.first;
317  }
318 
319  return {};
320 }
321 
322 bool
324 {
325  std::lock_guard sl (mutex_);
326  auto s = add (amendment);
327 
328  if (s->vetoed)
329  return false;
330  s->vetoed = true;
331  return true;
332 }
333 
334 bool
336 {
337  std::lock_guard sl (mutex_);
338  auto s = get (amendment);
339 
340  if (!s || !s->vetoed)
341  return false;
342  s->vetoed = false;
343  return true;
344 }
345 
346 bool
348 {
349  std::lock_guard sl (mutex_);
350  auto s = add (amendment);
351 
352  if (s->enabled)
353  return false;
354 
355  s->enabled = true;
356 
357  if (! s->supported)
358  {
359  JLOG (j_.error()) <<
360  "Unsupported amendment " << amendment << " activated.";
361  unsupportedEnabled_ = true;
362  }
363 
364  return true;
365 }
366 
367 bool
369 {
370  std::lock_guard sl (mutex_);
371  auto s = get (amendment);
372 
373  if (!s || !s->enabled)
374  return false;
375 
376  s->enabled = false;
377  return true;
378 }
379 
380 bool
382 {
383  std::lock_guard sl (mutex_);
384  auto s = get (amendment);
385  return s && s->enabled;
386 }
387 
388 bool
390 {
391  std::lock_guard sl (mutex_);
392  auto s = get (amendment);
393  return s && s->supported;
394 }
395 
396 bool
398 {
399  std::lock_guard sl (mutex_);
400  return unsupportedEnabled_;
401 }
402 
403 boost::optional<NetClock::time_point>
405 {
408 }
409 
412  std::set<uint256> const& enabled)
413 {
414  // Get the list of amendments we support and do not
415  // veto, but that are not already enabled
416  std::vector <uint256> amendments;
417  amendments.reserve (amendmentMap_.size());
418 
419  {
420  std::lock_guard sl (mutex_);
421  for (auto const& e : amendmentMap_)
422  {
423  if (e.second.supported && ! e.second.vetoed &&
424  (enabled.count (e.first) == 0))
425  {
426  amendments.push_back (e.first);
427  }
428  }
429  }
430 
431  if (! amendments.empty())
432  std::sort (amendments.begin (), amendments.end ());
433 
434  return amendments;
435 }
436 
439 {
440  // Get the list of amendments we support and do not veto
441  return doValidation({});
442 }
443 
446  NetClock::time_point closeTime,
447  std::set<uint256> const& enabledAmendments,
448  majorityAmendments_t const& majorityAmendments,
450 {
451  JLOG (j_.trace()) <<
452  "voting at " << closeTime.time_since_epoch().count() <<
453  ": " << enabledAmendments.size() <<
454  ", " << majorityAmendments.size() <<
455  ", " << valSet.size();
456 
457  auto vote = std::make_unique <AmendmentSet> ();
458 
459  // process validations for ledger before flag ledger
460  for (auto const& val : valSet)
461  {
462  if (val->isTrusted ())
463  {
464  std::set<uint256> ballot;
465 
466  if (val->isFieldPresent (sfAmendments))
467  {
468  auto const choices =
469  val->getFieldV256 (sfAmendments);
470  ballot.insert (choices.begin (), choices.end ());
471  }
472 
473  vote->tally (ballot);
474  }
475  }
476 
477  vote->mThreshold = std::max(1,
478  (vote->mTrustedValidations * majorityFraction_) / 256);
479 
480  JLOG (j_.debug()) <<
481  "Received " << vote->mTrustedValidations <<
482  " trusted validations, threshold is: " << vote->mThreshold;
483 
484  // Map of amendments to the action to be taken for each one. The action is
485  // the value of the flags in the pseudo-transaction
487 
488  {
489  std::lock_guard sl (mutex_);
490 
491  // process all amendments we know of
492  for (auto const& entry : amendmentMap_)
493  {
494  NetClock::time_point majorityTime = {};
495 
496  bool const hasValMajority =
497  (vote->votes (entry.first) >= vote->mThreshold);
498 
499  {
500  auto const it = majorityAmendments.find (entry.first);
501  if (it != majorityAmendments.end ())
502  majorityTime = it->second;
503  }
504 
505  if (enabledAmendments.count (entry.first) != 0)
506  {
507  JLOG (j_.debug()) <<
508  entry.first << ": amendment already enabled";
509  }
510  else if (hasValMajority &&
511  (majorityTime == NetClock::time_point{}) &&
512  ! entry.second.vetoed)
513  {
514  // Ledger says no majority, validators say yes
515  JLOG (j_.debug()) <<
516  entry.first << ": amendment got majority";
517  actions[entry.first] = tfGotMajority;
518  }
519  else if (! hasValMajority &&
520  (majorityTime != NetClock::time_point{}))
521  {
522  // Ledger says majority, validators say no
523  JLOG (j_.debug()) <<
524  entry.first << ": amendment lost majority";
525  actions[entry.first] = tfLostMajority;
526  }
527  else if ((majorityTime != NetClock::time_point{}) &&
528  ((majorityTime + majorityTime_) <= closeTime) &&
529  ! entry.second.vetoed)
530  {
531  // Ledger says majority held
532  JLOG (j_.debug()) <<
533  entry.first << ": amendment majority held";
534  actions[entry.first] = 0;
535  }
536  }
537 
538  // Stash for reporting
539  lastVote_ = std::move(vote);
540  }
541 
542  return actions;
543 }
544 
545 bool
547 {
548  std::lock_guard sl (mutex_);
549 
550  // Is there a ledger in which an amendment could have been enabled
551  // between these two ledger sequences?
552 
553  return ((ledgerSeq - 1) / 256) != ((lastUpdateSeq_ - 1) / 256);
554 }
555 
556 void
558  LedgerIndex ledgerSeq,
559  std::set<uint256> const& enabled,
560  majorityAmendments_t const& majority)
561 {
562  for (auto& e : enabled)
563  enable(e);
564 
566  // Since we have the whole list in `majority`, reset the time flag, even if
567  // it's currently set. If it's not set when the loop is done, then any
568  // prior unknown amendments have lost majority.
570  for (auto const& [ hash, time ] : majority)
571  {
572  auto s = add(hash);
573 
574  if (s->enabled)
575  continue;
576 
577  if (!s->supported)
578  {
579  JLOG(j_.info()) << "Unsupported amendment " << hash
580  << " reached majority at " << to_string(time);
583  }
584  }
587 }
588 
589 void
591 {
592  if (!fs.name.empty())
593  v[jss::name] = fs.name;
594 
595  v[jss::supported] = fs.supported;
596  v[jss::vetoed] = fs.vetoed;
597  v[jss::enabled] = fs.enabled;
598 
599  if (!fs.enabled && lastVote_)
600  {
601  auto const votesTotal = lastVote_->mTrustedValidations;
602  auto const votesNeeded = lastVote_->mThreshold;
603  auto const votesFor = lastVote_->votes (id);
604 
605  v[jss::count] = votesFor;
606  v[jss::validations] = votesTotal;
607 
608  if (votesNeeded)
609  {
610  v[jss::vote] = votesFor * 256 / votesNeeded;
611  v[jss::threshold] = votesNeeded;
612  }
613  }
614 }
615 
618 {
620  {
622  for (auto const& e : amendmentMap_)
623  {
624  setJson (ret[to_string (e.first)] = Json::objectValue,
625  e.first, e.second);
626  }
627  }
628  return ret;
629 }
630 
633 {
635  Json::Value& jAmendment = (ret[to_string (amendmentID)] = Json::objectValue);
636 
637  {
639  auto a = add (amendmentID);
640  setJson (jAmendment, amendmentID, *a);
641  }
642 
643  return ret;
644 }
645 
647  std::chrono::seconds majorityTime,
648  int majorityFraction,
649  Section const& supported,
650  Section const& enabled,
651  Section const& vetoed,
652  beast::Journal journal)
653 {
654  return std::make_unique<AmendmentTableImpl> (
655  majorityTime,
656  majorityFraction,
657  supported,
658  enabled,
659  vetoed,
660  journal);
661 }
662 
663 } // ripple
ripple::Section
Holds a collection of configuration values.
Definition: BasicConfig.h:43
ripple::AmendmentState::supported
bool supported
Indicates an amendment that this server has code support for.
Definition: AmendmentTable.cpp:88
ripple::AmendmentTableImpl::lastUpdateSeq_
std::uint32_t lastUpdateSeq_
Definition: AmendmentTable.cpp:146
ripple::AmendmentTableImpl::disable
bool disable(uint256 const &amendment) override
Definition: AmendmentTable.cpp:368
ripple::AmendmentTableImpl::hasUnsupportedEnabled
bool hasUnsupportedEnabled() override
returns true if one or more amendments on the network have been enabled that this server does not sup...
Definition: AmendmentTable.cpp:397
ripple::AmendmentState
Current state of an amendment.
Definition: AmendmentTable.cpp:75
ripple::AmendmentTableImpl::firstUnsupportedExpected_
boost::optional< NetClock::time_point > firstUnsupportedExpected_
Definition: AmendmentTable.cpp:164
std::string
STL class.
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:287
ripple::AmendmentSet::AmendmentSet
AmendmentSet()=default
ripple::AmendmentTableImpl::mutex_
std::mutex mutex_
Definition: AmendmentTable.cpp:143
std::vector::reserve
T reserve(T... args)
ripple::AmendmentTableImpl::unsupportedEnabled_
bool unsupportedEnabled_
Definition: AmendmentTable.cpp:160
std::vector
STL class.
std::map::find
T find(T... args)
ripple::make_AmendmentTable
std::unique_ptr< AmendmentTable > make_AmendmentTable(std::chrono::seconds majorityTime, int majorityFraction, Section const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
Definition: AmendmentTable.cpp:646
std::set::size
T size(T... args)
ripple::AmendmentTableImpl::isSupported
bool isSupported(uint256 const &amendment) override
Definition: AmendmentTable.cpp:389
std::chrono::seconds
ripple::AmendmentState::vetoed
bool vetoed
If an amendment is vetoed, a server will not support it.
Definition: AmendmentTable.cpp:78
std::lock_guard
STL class.
ripple::AmendmentTableImpl::unVeto
bool unVeto(uint256 const &amendment) override
Definition: AmendmentTable.cpp:335
ripple::AmendmentSet::tally
void tally(std::set< uint256 > const &amendments)
Definition: AmendmentTable.cpp:112
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::AmendmentTableImpl::isEnabled
bool isEnabled(uint256 const &amendment) override
Definition: AmendmentTable.cpp:381
ripple::tfLostMajority
const std::uint32_t tfLostMajority
Definition: TxFlags.h:99
ripple::tfGotMajority
const std::uint32_t tfGotMajority
Definition: TxFlags.h:98
ripple::AmendmentTableImpl
Track the list of "amendments".
Definition: AmendmentTable.cpp:139
std::sort
T sort(T... args)
algorithm
ripple::parseSection
static std::vector< std::pair< uint256, std::string > > parseSection(Section const &section)
Definition: AmendmentTable.cpp:36
ripple::AmendmentSet::votes_
hash_map< uint256, int > votes_
Definition: AmendmentTable.cpp:101
std::vector::push_back
T push_back(T... args)
ripple::base_uint< 256 >
ripple::Section::name
std::string const & name() const
Returns the name of this section.
Definition: BasicConfig.h:60
std::chrono::time_point::time_since_epoch
T time_since_epoch(T... args)
ripple::AmendmentTableImpl::setJson
void setJson(Json::Value &v, uint256 const &amendment, const AmendmentState &)
Definition: AmendmentTable.cpp:590
ripple::AmendmentTableImpl::majorityFraction_
const int majorityFraction_
Definition: AmendmentTable.cpp:153
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
ripple::AmendmentTableImpl::firstUnsupportedExpected
boost::optional< NetClock::time_point > firstUnsupportedExpected() override
Definition: AmendmentTable.cpp:404
ripple::AmendmentTableImpl::find
uint256 find(std::string const &name) override
Definition: AmendmentTable.cpp:309
ripple::AmendmentState::AmendmentState
AmendmentState()=default
ripple::AmendmentSet::votes
int votes(uint256 const &amendment) const
Definition: AmendmentTable.cpp:120
beast::Journal::error
Stream error() const
Definition: Journal.h:307
beast::Journal::info
Stream info() const
Definition: Journal.h:297
std::chrono::time_point
ripple::Section::lines
std::vector< std::string > const & lines() const
Returns all the lines in the section.
Definition: BasicConfig.h:69
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
std::uint32_t
std::map
STL class.
ripple::AmendmentTableImpl::AmendmentTableImpl
AmendmentTableImpl(std::chrono::seconds majorityTime, int majorityFraction, Section const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
Definition: AmendmentTable.cpp:227
ripple::AmendmentTableImpl::veto
bool veto(uint256 const &amendment) override
Definition: AmendmentTable.cpp:323
ripple::AmendmentSet::mTrustedValidations
int mTrustedValidations
Definition: AmendmentTable.cpp:105
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::AmendmentTableImpl::getJson
Json::Value getJson(int) override
Definition: AmendmentTable.cpp:617
ripple::AmendmentTableImpl::amendmentMap_
hash_map< uint256, AmendmentState > amendmentMap_
Definition: AmendmentTable.cpp:145
std::vector::begin
T begin(T... args)
ripple::AmendmentTableImpl::doValidation
std::vector< uint256 > doValidation(std::set< uint256 > const &enabledAmendments) override
Definition: AmendmentTable.cpp:411
ripple::AmendmentTableImpl::lastVote_
std::unique_ptr< AmendmentSet > lastVote_
Definition: AmendmentTable.cpp:157
std::set::insert
T insert(T... args)
ripple::AmendmentTableImpl::majorityTime_
const std::chrono::seconds majorityTime_
Definition: AmendmentTable.cpp:149
ripple::AmendmentState::enabled
bool enabled
Indicates that the amendment has been enabled.
Definition: AmendmentTable.cpp:85
ripple::AmendmentSet::mThreshold
int mThreshold
Definition: AmendmentTable.cpp:108
ripple::AmendmentState::name
std::string name
The name of this amendment, possibly empty.
Definition: AmendmentTable.cpp:91
std::set::count
T count(T... args)
std::vector::empty
T empty(T... args)
ripple::AmendmentTableImpl::needValidatedLedger
bool needValidatedLedger(LedgerIndex seq) override
Called to determine whether the amendment logic needs to process a new validated ledger.
Definition: AmendmentTable.cpp:546
mutex
beast::Journal::debug
Stream debug() const
Definition: Journal.h:292
std::make_pair
T make_pair(T... args)
std::vector::end
T end(T... args)
ripple::sfAmendments
const SF_Vec256 sfAmendments(access, STI_VECTOR256, 3, "Amendments")
Definition: SField.h:475
std::max
T max(T... args)
ripple::AmendmentTableImpl::j_
const beast::Journal j_
Definition: AmendmentTable.cpp:166
ripple::AmendmentSet
The status of all amendments requested in a given window.
Definition: AmendmentTable.cpp:97
ripple::AmendmentTable
The amendment table stores the list of enabled and potential amendments.
Definition: AmendmentTable.h:34
std::unique_ptr
STL class.
ripple::AmendmentTableImpl::get
AmendmentState * get(uint256 const &amendment)
Definition: AmendmentTable.cpp:297
std::unordered_map
STL class.
std::set
STL class.
ripple::AmendmentTableImpl::doVoting
std::map< uint256, std::uint32_t > doVoting(NetClock::time_point closeTime, std::set< uint256 > const &enabledAmendments, majorityAmendments_t const &majorityAmendments, std::vector< STValidation::pointer > const &validations) override
Definition: AmendmentTable.cpp:445
ripple::AmendmentTableImpl::add
AmendmentState * add(uint256 const &amendment)
Definition: AmendmentTable.cpp:290
ripple::AmendmentTableImpl::getDesired
std::vector< uint256 > getDesired() override
Definition: AmendmentTable.cpp:438
ripple::AmendmentTableImpl::doValidatedLedger
void doValidatedLedger(LedgerIndex seq, std::set< uint256 > const &enabled, majorityAmendments_t const &majority) override
Definition: AmendmentTable.cpp:557
ripple::AmendmentTableImpl::enable
bool enable(uint256 const &amendment) override
Definition: AmendmentTable.cpp:347
Json::Value
Represents a JSON value.
Definition: json_value.h:141