mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
526 lines
15 KiB
C++
526 lines
15 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#ifndef RIPPLE_VALIDATORS_LOGIC_H_INCLUDED
|
|
#define RIPPLE_VALIDATORS_LOGIC_H_INCLUDED
|
|
|
|
namespace ripple {
|
|
namespace Validators {
|
|
|
|
// Encapsulates the logic for creating the chosen validators.
|
|
// This is a separate class to facilitate the unit tests.
|
|
//
|
|
class Logic
|
|
{
|
|
public:
|
|
struct State
|
|
{
|
|
State ()
|
|
: stopping (false)
|
|
{ }
|
|
|
|
/** True if we are stopping. */
|
|
bool stopping;
|
|
|
|
/** The source we are currently fetching. */
|
|
SharedPtr <Source> fetchSource;
|
|
};
|
|
|
|
typedef SharedData <State> SharedState;
|
|
|
|
SharedState m_state;
|
|
|
|
Store& m_store;
|
|
Journal m_journal;
|
|
|
|
// The chosen set of trusted validators (formerly the "UNL")
|
|
//
|
|
bool m_rebuildChosenList;
|
|
ChosenList::Ptr m_chosenList;
|
|
|
|
// Holds the list of sources
|
|
//
|
|
typedef std::vector <SourceDesc> SourceTable;
|
|
SourceTable m_sources;
|
|
|
|
// Holds the internal list of trusted validators
|
|
//
|
|
typedef boost::unordered_map <
|
|
RipplePublicKey, Validator,
|
|
RipplePublicKey::hasher> ValidatorTable;
|
|
ValidatorTable m_validators;
|
|
|
|
// Filters duplicate validations
|
|
//
|
|
typedef CycledSet <ReceivedValidation,
|
|
ReceivedValidationHash,
|
|
ReceivedValidationKeyEqual> SeenValidations;
|
|
SeenValidations m_seenValidations;
|
|
|
|
// Filters duplicate ledger hashes
|
|
//
|
|
typedef CycledSet <RippleLedgerHash,
|
|
RippleLedgerHash::hasher,
|
|
RippleLedgerHash::key_equal> SeenLedgerHashes;
|
|
SeenLedgerHashes m_seenLedgerHashes;
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
explicit Logic (Store& store, Journal journal = Journal ())
|
|
: m_store (store)
|
|
, m_journal (journal)
|
|
, m_rebuildChosenList (false)
|
|
, m_seenValidations (seenValidationsCacheSize)
|
|
, m_seenLedgerHashes (seenLedgersCacheSize)
|
|
{
|
|
m_sources.reserve (16);
|
|
}
|
|
|
|
/** Stop the logic.
|
|
This will cancel the current fetch and set the stopping flag
|
|
to `true` to prevent further fetches.
|
|
Thread safety:
|
|
Safe to call from any thread.
|
|
*/
|
|
void stop ()
|
|
{
|
|
SharedState::Access state (m_state);
|
|
state->stopping = true;
|
|
if (state->fetchSource != nullptr)
|
|
state->fetchSource->cancel ();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
void load ()
|
|
{
|
|
// load data from the database
|
|
}
|
|
|
|
// Returns `true` if a Source with the same unique ID already exists
|
|
//
|
|
bool findSourceByID (String id)
|
|
{
|
|
for (SourceTable::const_iterator iter (m_sources.begin());
|
|
iter != m_sources.end(); ++iter)
|
|
if (iter->source->uniqueID() == id)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Add a one-time static source.
|
|
// Fetch is called right away, this call blocks.
|
|
//
|
|
void addStatic (SharedPtr <Source> source)
|
|
{
|
|
if (findSourceByID (source->uniqueID()))
|
|
{
|
|
m_journal.error << "Duplicate static " << source->name();
|
|
return;
|
|
}
|
|
|
|
m_journal.info << "Addding static " << source->name();
|
|
|
|
Source::Results results;
|
|
source->fetch (results, m_journal);
|
|
|
|
if (results.success)
|
|
{
|
|
std::size_t const numAdded (merge (results.list, source));
|
|
m_journal.info << "Added " << numAdded
|
|
<< " trusted validators from " << source->name();
|
|
}
|
|
else
|
|
{
|
|
// TODO: Report the error
|
|
}
|
|
}
|
|
|
|
// Add a live source to the list of sources.
|
|
//
|
|
void add (SharedPtr <Source> source)
|
|
{
|
|
if (findSourceByID (source->uniqueID()))
|
|
{
|
|
ScopedPointer <Source> object (source);
|
|
m_journal.error << "Duplicate " << source->name();
|
|
return;
|
|
}
|
|
|
|
m_journal.info << "Adding " << source->name();
|
|
|
|
{
|
|
m_sources.resize (m_sources.size() + 1);
|
|
SourceDesc& desc (m_sources.back());
|
|
desc.source = source;
|
|
m_store.insert (desc);
|
|
merge (desc.results.list, desc.source);
|
|
}
|
|
}
|
|
|
|
// Add each entry in the list to the map, incrementing the
|
|
// reference count if it already exists, and updating fields.
|
|
//
|
|
std::size_t merge (std::vector <Source::Item> const& list, Source* source)
|
|
{
|
|
std::size_t numAdded (0);
|
|
for (std::size_t i = 0; i < list.size (); ++i)
|
|
{
|
|
Source::Item const& item (list [i]);
|
|
std::pair <ValidatorTable::iterator, bool> results (
|
|
m_validators.emplace (item.publicKey, Validator ()));
|
|
Validator& validatorInfo (results.first->second);
|
|
validatorInfo.addRef();
|
|
if (results.second)
|
|
{
|
|
// This is a new one
|
|
++numAdded;
|
|
dirtyChosen ();
|
|
}
|
|
}
|
|
|
|
return numAdded;
|
|
}
|
|
|
|
// Decrement the reference count of each item in the list
|
|
// in the map.
|
|
//
|
|
std::size_t remove (std::vector <Source::Item> const& list,
|
|
Source* source)
|
|
{
|
|
std::size_t numRemoved (0);
|
|
for (std::size_t i = 0; i < list.size (); ++i)
|
|
{
|
|
Source::Item const& item (list [i]);
|
|
ValidatorTable::iterator iter (m_validators.find (item.publicKey));
|
|
bassert (iter != m_validators.end ());
|
|
Validator& validatorInfo (iter->second);
|
|
if (validatorInfo.release())
|
|
{
|
|
// Last reference removed
|
|
++numRemoved;
|
|
m_validators.erase (iter);
|
|
dirtyChosen ();
|
|
}
|
|
}
|
|
|
|
return numRemoved;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Chosen
|
|
//
|
|
|
|
/** Rebuild the Chosen List. */
|
|
void buildChosen ()
|
|
{
|
|
ChosenList::Ptr list (new ChosenList (m_validators.size ()));
|
|
|
|
for (ValidatorTable::const_iterator iter = m_validators.begin ();
|
|
iter != m_validators.end (); ++iter)
|
|
{
|
|
ChosenList::Info item;
|
|
list->insert (iter->first, item);
|
|
}
|
|
|
|
// This is thread safe
|
|
m_chosenList = list;
|
|
|
|
m_journal.debug <<
|
|
"Rebuilt chosen list with " <<
|
|
String::fromNumber (m_chosenList->size()) << " entries";
|
|
}
|
|
|
|
/** Mark the Chosen List for a rebuild. */
|
|
void dirtyChosen ()
|
|
{
|
|
m_rebuildChosenList = true;
|
|
}
|
|
|
|
/** Rebuild the Chosen List if necessary. */
|
|
void checkChosen ()
|
|
{
|
|
if (m_rebuildChosenList)
|
|
{
|
|
buildChosen ();
|
|
m_rebuildChosenList = false;
|
|
}
|
|
}
|
|
|
|
/** Returns the current Chosen list.
|
|
This can be called from any thread at any time.
|
|
*/
|
|
ChosenList::Ptr getChosen ()
|
|
{
|
|
return m_chosenList;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Fetching
|
|
//
|
|
|
|
/** Perform a fetch on the source. */
|
|
void fetch (SourceDesc& desc)
|
|
{
|
|
SharedPtr <Source> const& source (desc.source);
|
|
Source::Results results;
|
|
|
|
{
|
|
{
|
|
SharedState::Access state (m_state);
|
|
if (state->stopping)
|
|
return;
|
|
state->fetchSource = source;
|
|
}
|
|
|
|
source->fetch (results, m_journal);
|
|
|
|
{
|
|
SharedState::Access state (m_state);
|
|
if (state->stopping)
|
|
return;
|
|
state->fetchSource = nullptr;
|
|
}
|
|
}
|
|
|
|
// Reset fetch timer for the source->
|
|
desc.whenToFetch = Time::getCurrentTime () +
|
|
RelativeTime (secondsBetweenFetches);
|
|
|
|
if (results.success)
|
|
{
|
|
// Count the number fetched
|
|
std::size_t const numFetched (
|
|
results.list.size());
|
|
|
|
// Add the new source item to the map
|
|
std::size_t const numAdded (
|
|
merge (results.list, source));
|
|
|
|
// Swap lists
|
|
std::swap (desc.results, results);
|
|
|
|
// Remove the old source item from the map
|
|
std::size_t const numRemoved (remove (results.list, source));
|
|
|
|
// Report
|
|
if (numAdded > numRemoved)
|
|
{
|
|
m_journal.info <<
|
|
"Fetched " << numFetched <<
|
|
"(" << (numAdded - numRemoved) << " new) " <<
|
|
" trusted validators from " << source->name();
|
|
}
|
|
else if (numRemoved > numAdded)
|
|
{
|
|
m_journal.info <<
|
|
"Fetched " << numFetched <<
|
|
"(" << numRemoved - numAdded << " removed) " <<
|
|
" trusted validators from " << source->name();
|
|
}
|
|
else
|
|
{
|
|
m_journal.info <<
|
|
"Fetched " << numFetched <<
|
|
" trusted validators from " << source->name();
|
|
}
|
|
|
|
// See if we need to rebuild
|
|
checkChosen ();
|
|
|
|
// Reset failure status
|
|
desc.numberOfFailures = 0;
|
|
desc.status = SourceDesc::statusFetched;
|
|
|
|
// Update the source's list in the store
|
|
m_store.update (desc, true);
|
|
}
|
|
else
|
|
{
|
|
m_journal.error << "Failed to fetch " << source->name();
|
|
|
|
++desc.numberOfFailures;
|
|
desc.status = SourceDesc::statusFailed;
|
|
// Record the failure in the Store
|
|
m_store.update (desc);
|
|
}
|
|
}
|
|
|
|
/** Expire a source's list of validators. */
|
|
void expire (SourceDesc& desc)
|
|
{
|
|
// Decrement reference count on each validator
|
|
remove (desc.results.list, desc.source);
|
|
|
|
m_store.update (desc);
|
|
}
|
|
|
|
/** Process up to one source that needs fetching.
|
|
@return The number of sources that were fetched.
|
|
*/
|
|
std::size_t fetch_one ()
|
|
{
|
|
std::size_t n (0);
|
|
Time const currentTime (Time::getCurrentTime ());
|
|
|
|
for (SourceTable::iterator iter = m_sources.begin ();
|
|
(n == 0) && iter != m_sources.end (); ++iter)
|
|
{
|
|
SourceDesc& desc (*iter);
|
|
|
|
// See if we should fetch
|
|
//
|
|
if (desc.whenToFetch <= currentTime)
|
|
{
|
|
fetch (desc);
|
|
++n;
|
|
}
|
|
|
|
// See if we need to expire
|
|
//
|
|
if (desc.expirationTime.isNotNull () &&
|
|
desc.expirationTime <= currentTime)
|
|
{
|
|
expire (desc);
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// RPC Handlers
|
|
//
|
|
|
|
// Return the current ChosenList as JSON
|
|
Json::Value rpcPrint (Json::Value const& args)
|
|
{
|
|
Json::Value results (Json::objectValue);
|
|
|
|
Json::Value entries (Json::arrayValue);
|
|
{
|
|
results ["count"] = int(m_validators.size());
|
|
for (ValidatorTable::const_iterator iter (m_validators.begin());
|
|
iter != m_validators.end(); ++iter)
|
|
{
|
|
Validator const& v (iter->second);
|
|
Json::Value entry (Json::objectValue);
|
|
Count const count (v.count ());
|
|
|
|
entry ["public"] = iter->first.to_string();
|
|
entry ["received"] = int(count.received);
|
|
entry ["expected"] = int(count.expected);
|
|
entry ["closed"] = int(count.closed);
|
|
entry ["percent"] = count.percent();
|
|
|
|
entries.append (entry);
|
|
}
|
|
}
|
|
results ["validators"] = entries;
|
|
|
|
return results;
|
|
}
|
|
|
|
// Returns the list of sources
|
|
Json::Value rpcSources (Json::Value const& arg)
|
|
{
|
|
Json::Value results (Json::objectValue);
|
|
|
|
Json::Value entries (Json::arrayValue);
|
|
for (SourceTable::const_iterator iter (m_sources.begin());
|
|
iter != m_sources.end(); ++iter)
|
|
{
|
|
Json::Value entry (Json::objectValue);
|
|
SourceDesc const& desc (*iter);
|
|
entry ["name"] = desc.source->name();
|
|
entry ["param"] = desc.source->createParam();
|
|
|
|
Json::Value results (Json::arrayValue);
|
|
for (int i = 0; i < desc.results.list.size(); ++i)
|
|
{
|
|
Json::Value info (Json::objectValue);
|
|
info ["key"] = "publicKey";
|
|
info ["label"] = desc.results.list[i].label;
|
|
results.append (info);
|
|
}
|
|
entry ["results"] = results;
|
|
|
|
entries.append (entry);
|
|
}
|
|
results ["sources"] = entries;
|
|
|
|
return results;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// Ripple interface
|
|
//
|
|
|
|
// Called when we receive a signed validation
|
|
//
|
|
void receiveValidation (ReceivedValidation const& rv)
|
|
{
|
|
// Accept validation from the trusted list
|
|
ValidatorTable::iterator iter (m_validators.find (rv.publicKey));
|
|
if (iter != m_validators.end ())
|
|
{
|
|
// Filter duplicates (defensive programming)
|
|
if (! m_seenValidations.insert (rv))
|
|
return;
|
|
|
|
iter->second.receiveValidation (rv.ledgerHash);
|
|
}
|
|
}
|
|
|
|
// Called when a ledger is closed
|
|
//
|
|
void ledgerClosed (RippleLedgerHash const& ledgerHash)
|
|
{
|
|
// Filter duplicates (defensive programming)
|
|
if (! m_seenLedgerHashes.insert (ledgerHash))
|
|
return;
|
|
|
|
for (ValidatorTable::iterator iter (m_validators.begin());
|
|
iter != m_validators.end(); ++iter)
|
|
{
|
|
Validator& v (iter->second);
|
|
v.ledgerClosed (ledgerHash);
|
|
}
|
|
}
|
|
|
|
// Returns `true` if the public key hash is contained in the Chosen List.
|
|
//
|
|
bool isTrustedPublicKeyHash (RipplePublicKeyHash const& publicKeyHash)
|
|
{
|
|
return m_chosenList->containsPublicKeyHash (publicKeyHash);
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------
|
|
};
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|