Add LedgerCleaner:

* Manually invoked to clean ledgers
* Get status with: 'print app.ledgercleaner.*'
* Invoke or control with 'ledger_cleaner' command
This commit is contained in:
JoelKatz
2013-11-14 20:19:39 -08:00
committed by Vinnie Falco
parent b9e2ac38fa
commit ed64c8bb29
11 changed files with 569 additions and 0 deletions

View File

@@ -832,6 +832,12 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple_app\ledger\LedgerCleaner.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple_app\ledger\LedgerMaster.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -2340,6 +2346,7 @@
<ClInclude Include="..\..\src\ripple_app\data\DBInit.h" />
<ClInclude Include="..\..\src\ripple_app\data\SqliteDatabase.h" />
<ClInclude Include="..\..\src\ripple_app\ledger\Ledger.h" />
<ClInclude Include="..\..\src\ripple_app\ledger\LedgerCleaner.h" />
<ClInclude Include="..\..\src\ripple_app\ledger\LedgerMaster.h" />
<ClInclude Include="..\..\src\ripple_app\ledger\LedgerProposal.h" />
<ClInclude Include="..\..\src\ripple_app\ledger\LedgerTiming.h" />

View File

@@ -1401,6 +1401,9 @@
<ClCompile Include="..\..\src\ripple\rocksdb\rocksdb\util\vectorrep.cc">
<Filter>[1] Ripple\rocksdb\rocksdb\util</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple_app\ledger\LedgerCleaner.cpp">
<Filter>[2] Old Ripple\ripple_app\ledger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\src\ripple_basics\containers\KeyCache.h">
@@ -2874,6 +2877,9 @@
<ClInclude Include="..\..\src\ripple\rocksdb\rocksdb\include\utilities\utility_db.h">
<Filter>[1] Ripple\rocksdb\rocksdb\include\utilities</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple_app\ledger\LedgerCleaner.h">
<Filter>[2] Old Ripple\ripple_app\ledger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto">

View File

@@ -0,0 +1,468 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
/*
LedgerCleaner
Cleans up the ledger. Specifically, resolves these issues:
1. Older versions could leave the SQLite account and transaction databases in
an inconsistent state. The cleaner identifies these inconsistencies and
resolves them.
2. Upon request, checks for missing nodes in a ledger and triggers a fetch.
*/
class LedgerCleanerImp
: public LedgerCleaner
, public Thread
, public LeakChecked <LedgerCleanerImp>
{
public:
struct State
{
State()
: minRange (0)
, maxRange (0)
, checkNodes (false)
, fixTxns (false)
, failures (0)
{
}
LedgerIndex minRange; // The lowest ledger in the range we're checking
LedgerIndex maxRange; // The highest ledger in the range we're checking
bool checkNodes; // Check all state/transaction nodes
bool fixTxns; // Rewrite SQL databases
int failures; // Number of errors encountered since last success
};
typedef SharedData <State> SharedState;
SharedState m_state;
Journal m_journal;
//--------------------------------------------------------------------------
LedgerCleanerImp (
Stoppable& stoppable,
Journal journal)
: LedgerCleaner (stoppable)
, Thread ("LedgerCleaner")
, m_journal (journal)
{
}
~LedgerCleanerImp ()
{
stopThread ();
}
//--------------------------------------------------------------------------
//
// Stoppable
//
//--------------------------------------------------------------------------
void onPrepare ()
{
}
void onStart ()
{
startThread();
}
void onStop ()
{
m_journal.info << "Stopping";
signalThreadShouldExit();
notify();
}
//--------------------------------------------------------------------------
//
// PropertyStream
//
//--------------------------------------------------------------------------
void onWrite (PropertyStream::Map& map)
{
SharedState::Access state (m_state);
if (state->maxRange == 0)
map["status"] = "idle";
else
{
map["status"] = "running";
map["ledger_min"] = state->minRange;
map["ledger_max"] = state->maxRange;
map["check_nodes"] = state->checkNodes ? "true" : "false";
map["fix_txns"] = state->fixTxns ? "true" : "false";
if (state->failures > 0)
map["fail_counts"] = state->failures;
}
}
//--------------------------------------------------------------------------
//
// LedgerCleaner
//
//--------------------------------------------------------------------------
void doClean (Json::Value const& params)
{
LedgerIndex minRange;
LedgerIndex maxRange;
getApp().getLedgerMaster().getFullValidatedRange (minRange, maxRange);
{
SharedState::Access state (m_state);
state->maxRange = maxRange;
state->minRange = minRange;
state->checkNodes = false;
state->fixTxns = false;
state->failures = 0;
/*
JSON Parameters:
All parameters are optional. By default the cleaner cleans
things it thinks are necessary. This behavior can be modified
using the following options supplied via JSON RPC:
"ledger"
A single unsigned integer representing an individual
ledger to clean.
"min_ledger", "max_ledger"
Unsigned integers representing the starting and ending
ledger numbers to clean. If unspecified, clean all ledgers.
"full"
A boolean. When set to true, means clean everything possible.
"fix_txns"
A boolean value indicating whether or not to fix the
transactions in the database as well.
"check_nodes"
A boolean, when set to true means check the nodes.
"stop"
A boolean, when set to true informs the cleaner to gracefully
stop its current activities if any cleaning is taking place.
*/
// Quick way to fix a single ledger
if (params.isMember("ledger"))
{
state->maxRange = params["ledger"].asUInt();
state->minRange = params["ledger"].asUInt();
state->fixTxns = true;
state->checkNodes = true;
}
if (params.isMember("max_ledger"))
state->maxRange = params["max_ledger"].asUInt();
if (params.isMember("min_ledger"))
state->minRange = params["min_ledger"].asUInt();
if (params.isMember("full"))
state->fixTxns = state->checkNodes = params["full"].asBool();
if (params.isMember("fix_txns"))
state->fixTxns = params["fix_txns"].asBool();
if (params.isMember("check_nodes"))
state->checkNodes = params["check_nodes"].asBool();
if (params.isMember("stop") && params["stop"].asBool())
state->minRange = state->maxRange = 0;
}
notify();
}
//--------------------------------------------------------------------------
//
// LedgerCleanerImp
//
//--------------------------------------------------------------------------
void init ()
{
m_journal.debug << "Initializing";
}
void run ()
{
m_journal.debug << "Started";
init ();
while (! this->threadShouldExit())
{
this->wait ();
if (! this->threadShouldExit())
{
doLedgerCleaner();
}
}
stopped();
}
LedgerHash getLedgerHash(Ledger::pointer ledger, LedgerIndex index)
{
LedgerHash hash;
try
{
hash = ledger->getLedgerHash(index);
}
catch (SHAMapMissingNode &)
{
m_journal.warning <<
"Node missing from ledger " << ledger->getLedgerSeq();
getApp().getInboundLedgers().findCreate (
ledger->getHash(), ledger->getLedgerSeq(), false);
}
return hash;
}
/** Try to get a ledger, acquiring it if needed. */
Ledger::pointer findAcquireLedger (
LedgerIndex const& ledgerIndex, LedgerHash const& ledgerHash)
{
Ledger::pointer ledger (getApp().getLedgerMaster().getLedgerByHash(
ledgerHash));
if (!ledger)
{
m_journal.info <<
"Trying to acquire ledger " << ledgerIndex;
InboundLedger::pointer inboundLedger =
getApp().getInboundLedgers().findCreate (
ledgerHash, ledgerIndex, false);
if (inboundLedger && inboundLedger->isComplete() &&
! inboundLedger->isFailed())
{
ledger = inboundLedger->getLedger();
m_journal.info <<
"Found ledger " << ledgerIndex << " locally";
}
}
return ledger;
}
/** Process a single ledger
@param ledgerIndex The index of the ledger to process.
@param ledgerHash The known correct hash of the ledger.
@param doNodes Ensure all ledger nodes are in the node db.
@param doTxns Reprocess (account) transactions to SQL databases.
@return `true` if the ledger was cleaned.
*/
bool doLedger(
LedgerIndex const& ledgerIndex,
LedgerHash const& ledgerHash,
bool doNodes,
bool doTxns)
{
Ledger::pointer nodeLedger = findAcquireLedger(ledgerIndex, ledgerHash);
if (!nodeLedger)
{
m_journal.debug << "Ledger " << ledgerIndex << " not available";
return false;
}
Ledger::pointer dbLedger = Ledger::loadByIndex(ledgerIndex);
if (! dbLedger ||
(dbLedger->getHash() != ledgerHash) ||
(dbLedger->getParentHash() != nodeLedger->getParentHash()))
{
// Ideally we'd also check for more than one ledger with that index
m_journal.debug <<
"Ledger " << ledgerIndex << " mismatches SQL DB";
doTxns = true;
}
if(! getApp().getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
{
m_journal.debug << "ledger " << ledgerIndex << " had wrong entry in history";
doTxns = true;
}
if (doNodes && !nodeLedger->walkLedger())
{
m_journal.debug << "Ledger " << ledgerIndex << " is missing nodes";
getApp().getInboundLedgers().findCreate(ledgerHash, ledgerIndex, false);
return false;
}
if (doTxns && !nodeLedger->pendSaveValidated(true, false))
{
m_journal.debug << "Failed to save ledger " << ledgerIndex;
return false;
}
nodeLedger->dropCache();
return true;
}
/** Returns the hash of the specified ledger.
@param ledgerIndex The index of the desired ledger.
@param referenceLedger [out] An optional known good subsequent ledger.
@return The hash of the ledger. This will be all-bits-zero if not found.
*/
LedgerHash getHash(
LedgerIndex const& ledgerIndex,
Ledger::pointer& referenceLedger)
{
LedgerHash ledgerHash;
if (!referenceLedger || (referenceLedger->getLedgerSeq() < ledgerIndex))
{
referenceLedger = getApp().getLedgerMaster().getValidatedLedger();
if (!referenceLedger)
{
m_journal.warning << "No validated ledger";
return ledgerHash; // Nothing we can do. No validated ledger.
}
}
if (referenceLedger->getLedgerSeq() >= ledgerIndex)
{
// See if the hash for the ledger we need is in the reference ledger
ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
if (ledgerHash.isZero())
{
// No, Try to get another ledger that might have the hash we need
// Compute the index and hash of a ledger that will have the hash we need
LedgerIndex refIndex = (ledgerIndex + 255) & (~255);
LedgerHash refHash = getLedgerHash (referenceLedger, refIndex);
if (meets_precondition (refHash.isNonZero ()))
{
// We found the hash and sequence of a better reference ledger
referenceLedger = findAcquireLedger (refIndex, refHash);
if (referenceLedger)
ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
}
}
}
else
m_journal.warning << "Validated ledger is prior to target ledger";
return ledgerHash;
}
/** Run the ledger cleaner. */
void doLedgerCleaner()
{
Ledger::pointer goodLedger;
while (! this->threadShouldExit())
{
LedgerIndex ledgerIndex;
LedgerHash ledgerHash;
bool doNodes;
bool doTxns;
while (getApp().getFeeTrack().isLoadedLocal())
{
m_journal.debug << "Waiting for load to subside";
sleep(5000);
if (this->threadShouldExit ())
return;
}
{
SharedState::Access state (m_state);
if ((state->minRange > state->maxRange) ||
(state->maxRange == 0) || (state->minRange == 0))
{
state->minRange = state->maxRange = 0;
return;
}
ledgerIndex = state->maxRange;
doNodes = state->checkNodes;
doTxns = state->fixTxns;
}
ledgerHash = getHash(ledgerIndex, goodLedger);
bool fail = false;
if (ledgerHash.isZero())
{
m_journal.info << "Unable to get hash for ledger " << ledgerIndex;
fail = true;
}
else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
{
m_journal.info << "Failed to process ledger " << ledgerIndex;
fail = true;
}
if (fail)
{
{
SharedState::Access state (m_state);
++state->failures;
}
sleep(2000); // Wait for acquiring to catch up to us
}
else
{
{
SharedState::Access state (m_state);
if (ledgerIndex == state->minRange)
++state->minRange;
if (ledgerIndex == state->maxRange)
--state->maxRange;
state->failures = 0;
}
sleep(100); // Reduce I/O pressure a bit
}
}
}
};
//------------------------------------------------------------------------------
LedgerCleaner::LedgerCleaner (Stoppable& parent)
: Stoppable ("LedgerCleaner", parent)
, PropertyStream::Source ("ledgercleaner")
{
}
LedgerCleaner::~LedgerCleaner ()
{
}
LedgerCleaner* LedgerCleaner::New (
Stoppable& parent,
Journal journal)
{
return new LedgerCleanerImp (parent, journal);
}

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
/*
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_LEDGERCLEANER_H_INCLUDED
#define RIPPLE_LEDGERCLEANER_H_INCLUDED
/** Check the ledger/transaction databases to make sure they have continuity */
class LedgerCleaner
: public Stoppable
, public PropertyStream::Source
{
protected:
explicit LedgerCleaner (Stoppable& parent);
public:
/** Create a new object.
The caller receives ownership and must delete the object when done.
*/
static LedgerCleaner* New (
Stoppable& parent,
Journal journal);
/** Destroy the object. */
virtual ~LedgerCleaner () = 0;
/** Start a long running task to clean the ledger.
The ledger is cleaned asynchronously, on an implementation defined
thread. This function call does not block. The long running task
will be stopped if the Stoppable stops.
Thread safety:
Safe to call from any thread at any time.
@param parameters A Json object with configurable parameters.
*/
virtual void doClean (Json::Value const& parameters) = 0;
};
#endif

View File

@@ -22,6 +22,9 @@
SETUP_LOG (LedgerMaster)
class LedgerCleanerLog;
template <> char const* LogPartition::getPartitionName <LedgerCleanerLog> () { return "LedgerCleaner"; }
class LedgerMasterImp
: public LedgerMaster
, public LeakChecked <LedgerMasterImp>
@@ -58,6 +61,8 @@ public:
LockType mCompleteLock;
RangeSet mCompleteLedgers;
ScopedPointer<LedgerCleaner> mLedgerCleaner;
int mMinValidations; // The minimum validations to publish a ledger
uint256 mLastValidateHash;
uint32 mLastValidateSeq;
@@ -83,6 +88,7 @@ public:
, mValidLedgerClose (0)
, mValidLedgerSeq (0)
, mHeldTransactions (uint256 ())
, mLedgerCleaner (LedgerCleaner::New(*this, LogPartition::getJournal<LedgerCleanerLog>()))
, mMinValidations (0)
, mLastValidateSeq (0)
, mAdvanceThread (false)
@@ -1158,6 +1164,11 @@ public:
return mLedgerHistory.getLedgerByHash (hash);
}
void doLedgerCleaner(const Json::Value& parameters)
{
mLedgerCleaner->doClean (parameters);
}
void setLedgerRangePresent (uint32 minV, uint32 maxV)
{
ScopedLockType sl (mCompleteLock, __FILE__, __LINE__);
@@ -1182,6 +1193,11 @@ public:
{
mOnValidate.push_back (c);
}
PropertyStream::Source& getPropertySource ()
{
return *mLedgerCleaner;
}
};
//------------------------------------------------------------------------------

View File

@@ -123,6 +123,9 @@ public:
virtual void newOrderBookDB () = 0;
virtual bool fixIndex (LedgerIndex ledgerIndex, LedgerHash const& ledgerHash) = 0;
virtual void doLedgerCleaner(const Json::Value& parameters) = 0;
virtual PropertyStream::Source& getPropertySource () = 0;
static bool shouldAcquire (uint32 currentLedgerID, uint32 ledgerHistory, uint32 targetLedger);
};

View File

@@ -151,6 +151,8 @@ public:
bassert (s_instance == nullptr);
s_instance = this;
add (m_ledgerMaster->getPropertySource ());
// VFALCO TODO remove these once the call is thread safe.
HashMaps::getInstance ().initializeNonce <size_t> ();
}

View File

@@ -108,6 +108,7 @@ namespace ripple {
#include "tx/TransactionEngine.h"
#include "misc/CanonicalTXSet.h"
#include "ledger/LedgerHistory.h"
#include "ledger/LedgerCleaner.h"
#include "ledger/LedgerMaster.h"
#include "ledger/LedgerProposal.h"
#include "misc/NetworkOPs.h"

View File

@@ -44,6 +44,8 @@ namespace ripple
#include "consensus/LedgerConsensus.cpp"
# include "ledger/LedgerCleaner.h"
#include "ledger/LedgerCleaner.cpp"
#include "ledger/LedgerMaster.cpp"
}

View File

@@ -2896,6 +2896,13 @@ Json::Value RPCHandler::doLedgerAccept (Json::Value, Resource::Charge& loadType,
return jvResult;
}
Json::Value RPCHandler::doLedgerCleaner (Json::Value parameters, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock();
getApp().getLedgerMaster().doLedgerCleaner (parameters);
return "Cleaner configured";
}
// {
// ledger_hash : <ledger>,
// ledger_index : <ledger_index>
@@ -3919,6 +3926,7 @@ Json::Value RPCHandler::doCommand (const Json::Value& params, int iRole, Resourc
{ "fetch_info", &RPCHandler::doFetchInfo, true, optNone },
{ "ledger", &RPCHandler::doLedger, false, optNetwork },
{ "ledger_accept", &RPCHandler::doLedgerAccept, true, optCurrent },
{ "ledger_cleaner", &RPCHandler::doLedgerCleaner, true, optNetwork },
{ "ledger_closed", &RPCHandler::doLedgerClosed, false, optClosed },
{ "ledger_current", &RPCHandler::doLedgerCurrent, false, optCurrent },
{ "ledger_entry", &RPCHandler::doLedgerEntry, false, optCurrent },

View File

@@ -111,6 +111,7 @@ private:
Json::Value doInternal (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedger (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerAccept (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerCleaner (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerClosed (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerCurrent (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerEntry (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh);