Merge branch 'develop' of github.com:ripple/rippled into develop

This commit is contained in:
David Schwartz
2013-07-25 09:56:42 -07:00
150 changed files with 1112 additions and 1117 deletions

View File

@@ -0,0 +1,137 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#define TRUST_NETWORK
// Track a peer's yes/no vote on a particular disputed transaction
void DisputedTx::setVote (const uint160& peer, bool votesYes)
{
// VFALCO TODO Simplify this declaration. It doesn't exactly roll off the tongue!
std::pair <boost::unordered_map <const uint160, bool>::iterator, bool> res =
mVotes.insert (std::pair<const uint160, bool> (peer, votesYes));
// new vote
if (res.second)
{
if (votesYes)
{
WriteLog (lsDEBUG, LedgerConsensus) << "Peer " << peer << " votes YES on " << mTransactionID;
++mYays;
}
else
{
WriteLog (lsDEBUG, LedgerConsensus) << "Peer " << peer << " votes NO on " << mTransactionID;
++mNays;
}
}
// changes vote to yes
else if (votesYes && !res.first->second)
{
WriteLog (lsDEBUG, LedgerConsensus) << "Peer " << peer << " now votes YES on " << mTransactionID;
--mNays;
++mYays;
res.first->second = true;
}
// changes vote to no
else if (!votesYes && res.first->second)
{
WriteLog (lsDEBUG, LedgerConsensus) << "Peer " << peer << " now votes NO on " << mTransactionID;
++mNays;
--mYays;
res.first->second = false;
}
}
// Remove a peer's vote on this disputed transasction
void DisputedTx::unVote (const uint160& peer)
{
boost::unordered_map<uint160, bool>::iterator it = mVotes.find (peer);
if (it != mVotes.end ())
{
if (it->second)
--mYays;
else
--mNays;
mVotes.erase (it);
}
}
bool DisputedTx::updateVote (int percentTime, bool proposing)
{
// VFALCO TODO Give the return value a descriptive local variable name
// and don't return from the middle.
if (mOurVote && (mNays == 0))
return false;
if (!mOurVote && (mYays == 0))
return false;
bool newPosition;
int weight;
if (proposing) // give ourselves full weight
{
// This is basically the percentage of nodes voting 'yes' (including us)
weight = (mYays * 100 + (mOurVote ? 100 : 0)) / (mNays + mYays + 1);
// VFALCO TODO Rename these macros and turn them into language constructs.
// consolidate them into a class that collects all these related values.
//
// To prevent avalanche stalls, we increase the needed weight slightly over time
if (percentTime < AV_MID_CONSENSUS_TIME)
newPosition = weight > AV_INIT_CONSENSUS_PCT;
else if (percentTime < AV_LATE_CONSENSUS_TIME)
newPosition = weight > AV_MID_CONSENSUS_PCT;
else if (percentTime < AV_STUCK_CONSENSUS_TIME)
newPosition = weight > AV_LATE_CONSENSUS_PCT;
else
newPosition = weight > AV_STUCK_CONSENSUS_PCT;
}
else // don't let us outweigh a proposing node, just recognize consensus
{
weight = -1;
newPosition = mYays > mNays;
}
if (newPosition == mOurVote)
{
WriteLog (lsINFO, LedgerConsensus) <<
"No change (" << (mOurVote ? "YES" : "NO") << ") : weight " << weight << ", percent " << percentTime;
WriteLog (lsDEBUG, LedgerConsensus) << getJson ();
return false;
}
mOurVote = newPosition;
WriteLog (lsDEBUG, LedgerConsensus) << "We now vote " << (mOurVote ? "YES" : "NO") << " on " << mTransactionID;
WriteLog (lsDEBUG, LedgerConsensus) << getJson ();
return true;
}
Json::Value DisputedTx::getJson ()
{
Json::Value ret (Json::objectValue);
ret["yays"] = mYays;
ret["nays"] = mNays;
ret["our_vote"] = mOurVote;
if (!mVotes.empty ())
{
Json::Value votesj (Json::objectValue);
typedef boost::unordered_map<uint160, bool>::value_type vt;
BOOST_FOREACH (vt & vote, mVotes)
{
votesj[vote.first.GetHex ()] = vote.second;
}
ret["votes"] = votesj;
}
return ret;
}

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_DISPUTEDTX_H
#define RIPPLE_DISPUTEDTX_H
/** A transaction discovered to be in dispute during conensus.
During consensus, a @ref DisputedTx is created when a transaction
is discovered to be disputed. The object persists only as long as
the dispute.
Undisputed transactions have no corresponding @ref DisputedTx object.
*/
class DisputedTx
{
public:
typedef boost::shared_ptr <DisputedTx> pointer;
DisputedTx (uint256 const& txID,
Blob const& tx,
bool ourVote) :
mTransactionID (txID), mYays (0), mNays (0), mOurVote (ourVote), transaction (tx)
{
;
}
uint256 const& getTransactionID () const
{
return mTransactionID;
}
bool getOurVote () const
{
return mOurVote;
}
// VFALCO TODO make this const
Serializer& peekTransaction ()
{
return transaction;
}
void setOurVote (bool o)
{
mOurVote = o;
}
// VFALCO NOTE its not really a peer, its the 160 bit hash of the validator's public key
//
void setVote (uint160 const& peer, bool votesYes);
void unVote (uint160 const& peer);
bool updateVote (int percentTime, bool proposing);
Json::Value getJson ();
private:
uint256 mTransactionID;
int mYays;
int mNays;
bool mOurVote;
Serializer transaction;
boost::unordered_map <uint160, bool> mVotes;
};
// VFALCO TODO Rename and put these in a tidy place
typedef std::map<uint256, DisputedTx::pointer>::value_type u256_lct_pair;
typedef std::map<uint160, LedgerProposal::pointer>::value_type u160_prop_pair;
#define LEDGER_TOTAL_PASSES 8
#define LEDGER_RETRY_PASSES 5
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_LEDGERCONSENSUS_H
#define RIPPLE_LEDGERCONSENSUS_H
/** Manager for achieving consensus on the next ledger.
This object is created when the consensus process starts, and
is destroyed when the process is complete.
*/
class LedgerConsensus
: public boost::enable_shared_from_this <LedgerConsensus>
, public CountedObject <LedgerConsensus>
{
public:
static char const* getCountedObjectName () { return "LedgerConsensus"; }
LedgerConsensus (LedgerHash const & prevLCLHash, Ledger::ref previousLedger, uint32 closeTime);
int startup ();
Json::Value getJson (bool full);
Ledger::ref peekPreviousLedger ()
{
return mPreviousLedger;
}
uint256 getLCL ()
{
return mPrevLedgerHash;
}
SHAMap::pointer getTransactionTree (uint256 const & hash, bool doAcquire);
TransactionAcquire::pointer getAcquiring (uint256 const & hash);
void mapComplete (uint256 const & hash, SHAMap::ref map, bool acquired);
bool stillNeedTXSet (uint256 const & hash);
void checkLCL ();
void handleLCL (uint256 const & lclHash);
void timerEntry ();
// state handlers
void statePreClose ();
void stateEstablish ();
void stateCutoff ();
void stateFinished ();
void stateAccepted ();
bool haveConsensus (bool forReal);
bool peerPosition (LedgerProposal::ref);
bool peerHasSet (Peer::ref peer, uint256 const & set, protocol::TxSetStatus status);
SHAMapAddNode peerGaveNodes (Peer::ref peer, uint256 const & setHash,
const std::list<SHAMapNode>& nodeIDs, const std::list< Blob >& nodeData);
bool isOurPubKey (const RippleAddress & k)
{
return k == mValPublic;
}
// test/debug
void simulate ();
private:
// final accept logic
void accept (SHAMap::ref txSet, LoadEvent::pointer);
void weHave (uint256 const & id, Peer::ref avoidPeer);
void startAcquiring (TransactionAcquire::pointer);
SHAMap::pointer find (uint256 const & hash);
void createDisputes (SHAMap::ref, SHAMap::ref);
void addDisputedTransaction (uint256 const& , Blob const & transaction);
void adjustCount (SHAMap::ref map, const std::vector<uint160>& peers);
void propose ();
void addPosition (LedgerProposal&, bool ours);
void removePosition (LedgerProposal&, bool ours);
void sendHaveTxSet (uint256 const & set, bool direct);
void applyTransactions (SHAMap::ref transactionSet, Ledger::ref targetLedger,
Ledger::ref checkLedger, CanonicalTXSet & failedTransactions, bool openLgr);
int applyTransaction (TransactionEngine & engine, SerializedTransaction::ref txn, Ledger::ref targetLedger,
bool openLgr, bool retryAssured);
uint32 roundCloseTime (uint32 closeTime);
// manipulating our own position
void statusChange (protocol::NodeEvent, Ledger & ledger);
void takeInitialPosition (Ledger & initialLedger);
void updateOurPositions ();
void playbackProposals ();
int getThreshold ();
void closeLedger ();
void checkOurValidation ();
void beginAccept (bool synchronous);
void endConsensus ();
void addLoad (SerializedValidation::ref val);
private:
// VFALCO TODO Rename these to look pretty
enum LCState
{
lcsPRE_CLOSE, // We haven't closed our ledger yet, but others might have
lcsESTABLISH, // Establishing consensus
lcsFINISHED, // We have closed on a transaction set
lcsACCEPTED, // We have accepted/validated a new last closed ledger
};
LCState mState;
uint32 mCloseTime; // The wall time this ledger closed
uint256 mPrevLedgerHash, mNewLedgerHash;
Ledger::pointer mPreviousLedger;
InboundLedger::pointer mAcquiringLedger;
LedgerProposal::pointer mOurPosition;
RippleAddress mValPublic, mValPrivate;
bool mProposing, mValidating, mHaveCorrectLCL, mConsensusFail;
int mCurrentMSeconds, mClosePercent, mCloseResolution;
bool mHaveCloseTimeConsensus;
boost::posix_time::ptime mConsensusStartTime;
int mPreviousProposers;
int mPreviousMSeconds;
// Convergence tracking, trusted peers indexed by hash of public key
boost::unordered_map<uint160, LedgerProposal::pointer> mPeerPositions;
// Transaction Sets, indexed by hash of transaction tree
boost::unordered_map<uint256, SHAMap::pointer> mAcquired;
boost::unordered_map<uint256, TransactionAcquire::pointer> mAcquiring;
// Peer sets
boost::unordered_map<uint256, std::vector< boost::weak_ptr<Peer> > > mPeerData;
// Disputed transactions
boost::unordered_map<uint256, DisputedTx::pointer> mDisputes;
// Close time estimates
std::map<uint32, int> mCloseTimes;
// nodes that have bowed out of this consensus process
boost::unordered_set<uint160> mDeadNodes;
};
#endif

View File

@@ -0,0 +1,178 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
bool ParameterNode::setValue (const std::string& name, const Json::Value& value, Json::Value& error)
{
if (name.empty ()) // this node
return setValue (value, error);
size_t dot = name.find ('.');
if (dot == std::string::npos) // a child of this node
{
std::map<std::string, Parameter::pointer>::iterator it = mChildren.find (name);
if (it == mChildren.end ())
{
error = Json::objectValue;
error["error"] = "Name not found";
error["name"] = name;
return false;
}
return it->second->setValue (value, error);
}
std::map<std::string, Parameter::pointer>::iterator it = mChildren.find (name.substr (0, dot));
if (it == mChildren.end ())
{
error = Json::objectValue;
error["error"] = "Name not found";
error["name"] = name;
return false;
}
ParameterNode* n = dynamic_cast<ParameterNode*> (it->second.get ());
if (!n)
{
error = Json::objectValue;
error["error"] = "Node has no children";
error["name"] = it->second->getName ();
return false;
}
return n->setValue (name.substr (dot + 1), value, error);
}
bool ParameterNode::addNode (const std::string& name, Parameter::ref node)
{
if (name.empty ()) // this node
return false;
size_t dot = name.find ('.');
if (dot == std::string::npos) // a child of this node
{
std::map<std::string, Parameter::pointer>::iterator it = mChildren.find (name);
if (it != mChildren.end ())
return false;
mChildren[name] = node;
return true;
}
std::map<std::string, Parameter::pointer>::iterator it = mChildren.find (name.substr (0, dot));
ParameterNode* n;
if (it == mChildren.end ())
{
// create a new inner node
ParameterNode::pointer node = boost::make_shared<ParameterNode> (getShared (), name.substr (0, dot));
n = dynamic_cast<ParameterNode*> (node.get ());
assert (n);
mChildren[name] = node;
}
else
{
// existing node passed through must be inner
ParameterNode* n = dynamic_cast<ParameterNode*> (it->second.get ());
if (!n)
return false;
}
return n->addNode (name.substr (dot + 1), node);
}
Json::Value ParameterNode::getValue (int i) const
{
Json::Value v (Json::objectValue);
typedef std::map<std::string, Parameter::pointer>::value_type string_ref_pair;
BOOST_FOREACH (const string_ref_pair & it, mChildren)
{
v[it.first] = it.second->getValue (i);
}
return v;
}
bool ParameterNode::setValue (const Json::Value& value, Json::Value& error)
{
error = Json::objectValue;
error["error"] = "Cannot end on an inner node";
Json::Value nodes (Json::arrayValue);
typedef std::map<std::string, Parameter::pointer>::value_type string_ref_pair;
BOOST_FOREACH (const string_ref_pair & it, mChildren)
{
nodes.append (it.first);
}
error["legal_nodes"] = nodes;
return false;
}
ParameterString::ParameterString (Parameter::ref parent, const std::string& name, const std::string& value)
: Parameter (parent, name), mValue (value)
{
;
}
Json::Value ParameterString::getValue (int) const
{
return Json::Value (mValue);
}
bool ParameterString::setValue (const Json::Value& value, Json::Value& error)
{
if (!value.isConvertibleTo (Json::stringValue))
{
error = Json::objectValue;
error["error"] = "Cannot convert to string";
error["value"] = value;
return false;
}
mValue = value.asString ();
return true;
}
ParameterInt::ParameterInt (Parameter::ref parent, const std::string& name, int value)
: Parameter (parent, name), mValue (value)
{
;
}
Json::Value ParameterInt::getValue (int) const
{
return Json::Value (mValue);
}
bool ParameterInt::setValue (const Json::Value& value, Json::Value& error)
{
if (value.isConvertibleTo (Json::intValue))
{
mValue = value.asInt ();
return true;
}
if (value.isConvertibleTo (Json::stringValue))
{
try
{
mValue = lexical_cast_st<int> (value.asString ());
}
catch (...)
{
}
}
error = Json::objectValue;
error["error"] = "Cannot convert to integer";
error["value"] = value;
return false;
}

View File

@@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PARAMETERTABLE_H
#define RIPPLE_PARAMETERTABLE_H
class Parameter : public boost::enable_shared_from_this<Parameter>
{
// abstract base class parameters are derived from
public:
typedef boost::shared_ptr<Parameter> pointer;
typedef const boost::shared_ptr<Parameter>& ref;
public:
Parameter (Parameter::ref parent, const std::string& name) : mParent (parent), mName (name)
{
;
}
virtual ~Parameter ()
{
;
}
const std::string& getName () const
{
return mName;
}
virtual Json::Value getValue (int) const = 0;
virtual bool setValue (const Json::Value& value, Json::Value& error) = 0;
Parameter::pointer getShared ()
{
return shared_from_this ();
}
private:
pointer mParent;
std::string mName;
};
class ParameterNode : public Parameter
{
public:
ParameterNode (Parameter::ref parent, const std::string& name) : Parameter (parent, name)
{
;
}
bool addChildNode (Parameter::ref node);
bool setValue (const std::string& name, const Json::Value& value, Json::Value& error);
bool addNode (const std::string& name, Parameter::ref node);
virtual Json::Value getValue (int) const;
virtual bool setValue (const Json::Value& value, Json::Value& error);
private:
std::map<std::string, Parameter::pointer> mChildren;
};
class ParameterString : public Parameter
{
public:
ParameterString (Parameter::ref parent, const std::string& name, const std::string& value);
virtual Json::Value getValue (int) const;
virtual bool setValue (const Json::Value& value, Json::Value& error);
private:
std::string mValue;
};
class ParameterInt : public Parameter
{
public:
ParameterInt (Parameter::ref parent, const std::string& name, int value);
virtual Json::Value getValue (int) const;
virtual bool setValue (const Json::Value& value, Json::Value& error);
private:
int mValue;
};
#endif

View File

@@ -0,0 +1,994 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// VFALCO TODO Clean this global up
volatile bool doShutdown = false;
class Application;
SETUP_LOG (Application)
// VFALCO TODO Move the function definitions into the class declaration
class ApplicationImp
: public Application
, public SharedSingleton <ApplicationImp>
, public Validators::Listener
, public NodeStore::Scheduler
, LeakChecked <ApplicationImp>
{
public:
static ApplicationImp* createInstance ()
{
return new ApplicationImp;
}
class Holder;
ApplicationImp ()
//
// VFALCO NOTE Change this to control whether or not the Application
// object is destroyed on exit
//
#if 1
// Application object will be deleted on exit. If the code doesn't exit
// cleanly this could cause hangs or crashes on exit.
//
: SharedSingleton <ApplicationImp> (SingletonLifetime::persistAfterCreation)
#else
// This will make it so that the Application object is not deleted on exit.
//
: SharedSingleton <Application> (SingletonLifetime::neverDestroyed)
#endif
, mIOService ((theConfig.NODE_SIZE >= 2) ? 2 : 1)
, mIOWork (mIOService)
, mNetOps (new NetworkOPs (&mLedgerMaster))
, m_rpcServerHandler (*mNetOps)
, mTempNodeCache ("NodeCache", 16384, 90)
, mSLECache ("LedgerEntryCache", 4096, 120)
, mSNTPClient (mAuxService)
, mJobQueue (mIOService)
// VFALCO New stuff
, m_nodeStore (NodeStore::New (
theConfig.nodeDatabase,
theConfig.ephemeralNodeDatabase,
*this))
, m_validators (Validators::New (this))
, mFeatures (IFeatures::New (2 * 7 * 24 * 60 * 60, 200)) // two weeks, 200/256
, mFeeVote (IFeeVote::New (10, 50 * SYSTEM_CURRENCY_PARTS, 12.5 * SYSTEM_CURRENCY_PARTS))
, mFeeTrack (ILoadFeeTrack::New ())
, mHashRouter (IHashRouter::New (IHashRouter::getDefaultHoldTime ()))
, mValidations (IValidations::New ())
, mUNL (UniqueNodeList::New ())
, mProofOfWorkFactory (IProofOfWorkFactory::New ())
, mPeers (IPeers::New (mIOService))
, m_loadManager (ILoadManager::New ())
// VFALCO End new stuff
// VFALCO TODO replace all NULL with nullptr
, mRpcDB (NULL)
, mTxnDB (NULL)
, mLedgerDB (NULL)
, mWalletDB (NULL) // VFALCO NOTE are all these 'NULL' ctor params necessary?
, mPeerDoor (NULL)
, mRPCDoor (NULL)
, mWSPublicDoor (NULL)
, mWSPrivateDoor (NULL)
, mSweepTimer (mAuxService)
, mShutdown (false)
{
// VFALCO TODO remove these once the call is thread safe.
HashMaps::getInstance ().initializeNonce <size_t> ();
}
~ApplicationImp ()
{
mNetOps = nullptr;
// VFALCO TODO Wrap these in ScopedPointer
delete mTxnDB;
delete mLedgerDB;
delete mWalletDB;
}
//--------------------------------------------------------------------------
static void callScheduledTask (NodeStore::Scheduler::Task* task, Job&)
{
task->performScheduledTask ();
}
void scheduleTask (NodeStore::Scheduler::Task* task)
{
getJobQueue ().addJob (
jtWRITE,
"NodeObject::store",
BIND_TYPE (&ApplicationImp::callScheduledTask, task, P_1));
}
//--------------------------------------------------------------------------
LocalCredentials& getLocalCredentials ()
{
return m_localCredentials ;
}
NetworkOPs& getOPs ()
{
return *mNetOps;
}
boost::asio::io_service& getIOService ()
{
return mIOService;
}
LedgerMaster& getLedgerMaster ()
{
return mLedgerMaster;
}
InboundLedgers& getInboundLedgers ()
{
return m_inboundLedgers;
}
TransactionMaster& getMasterTransaction ()
{
return mMasterTransaction;
}
NodeCache& getTempNodeCache ()
{
return mTempNodeCache;
}
NodeStore& getNodeStore ()
{
return *m_nodeStore;
}
JobQueue& getJobQueue ()
{
return mJobQueue;
}
MasterLockType& getMasterLock ()
{
return mMasterLock;
}
ILoadManager& getLoadManager ()
{
return *m_loadManager;
}
TXQueue& getTxnQueue ()
{
return mTxnQueue;
}
PeerDoor& getPeerDoor ()
{
return *mPeerDoor;
}
OrderBookDB& getOrderBookDB ()
{
return mOrderBookDB;
}
SLECache& getSLECache ()
{
return mSLECache;
}
Validators& getValidators ()
{
return *m_validators;
}
IFeatures& getFeatureTable ()
{
return *mFeatures;
}
ILoadFeeTrack& getFeeTrack ()
{
return *mFeeTrack;
}
IFeeVote& getFeeVote ()
{
return *mFeeVote;
}
IHashRouter& getHashRouter ()
{
return *mHashRouter;
}
IValidations& getValidations ()
{
return *mValidations;
}
UniqueNodeList& getUNL ()
{
return *mUNL;
}
IProofOfWorkFactory& getProofOfWorkFactory ()
{
return *mProofOfWorkFactory;
}
IPeers& getPeers ()
{
return *mPeers;
}
// VFALCO TODO Move these to the .cpp
bool running ()
{
return mTxnDB != NULL; // VFALCO TODO replace with nullptr when beast is available
}
bool getSystemTimeOffset (int& offset)
{
return mSNTPClient.getOffset (offset);
}
DatabaseCon* getRpcDB ()
{
return mRpcDB;
}
DatabaseCon* getTxnDB ()
{
return mTxnDB;
}
DatabaseCon* getLedgerDB ()
{
return mLedgerDB;
}
DatabaseCon* getWalletDB ()
{
return mWalletDB;
}
bool isShutdown ()
{
return mShutdown;
}
void setup ();
void run ();
void stop ();
void sweep ();
void doSweep (Job&);
private:
void updateTables ();
void startNewLedger ();
bool loadOldLedger (const std::string&, bool);
private:
boost::asio::io_service mIOService;
boost::asio::io_service mAuxService;
// The lifetime of the io_service::work object informs the io_service
// of when the work starts and finishes. io_service::run() will not exit
// while the work object exists.
//
boost::asio::io_service::work mIOWork;
MasterLockType mMasterLock;
LocalCredentials m_localCredentials;
LedgerMaster mLedgerMaster;
InboundLedgers m_inboundLedgers;
TransactionMaster mMasterTransaction;
ScopedPointer <NetworkOPs> mNetOps;
RPCServerHandler m_rpcServerHandler;
NodeCache mTempNodeCache;
SLECache mSLECache;
SNTPClient mSNTPClient;
JobQueue mJobQueue;
TXQueue mTxnQueue;
OrderBookDB mOrderBookDB;
// VFALCO Clean stuff
ScopedPointer <NodeStore> m_nodeStore;
ScopedPointer <Validators> m_validators;
ScopedPointer <IFeatures> mFeatures;
ScopedPointer <IFeeVote> mFeeVote;
ScopedPointer <ILoadFeeTrack> mFeeTrack;
ScopedPointer <IHashRouter> mHashRouter;
ScopedPointer <IValidations> mValidations;
ScopedPointer <UniqueNodeList> mUNL;
ScopedPointer <IProofOfWorkFactory> mProofOfWorkFactory;
ScopedPointer <IPeers> mPeers;
ScopedPointer <ILoadManager> m_loadManager;
// VFALCO End Clean stuff
DatabaseCon* mRpcDB;
DatabaseCon* mTxnDB;
DatabaseCon* mLedgerDB;
DatabaseCon* mWalletDB;
ScopedPointer <PeerDoor> mPeerDoor;
ScopedPointer <RPCDoor> mRPCDoor;
ScopedPointer <WSDoor> mWSPublicDoor;
ScopedPointer <WSDoor> mWSPrivateDoor;
boost::asio::deadline_timer mSweepTimer;
bool volatile mShutdown;
};
// VFALCO TODO Why do we even have this function?
// It could just be handled in the destructor.
//
void ApplicationImp::stop ()
{
WriteLog (lsINFO, Application) << "Received shutdown request";
StopSustain ();
mShutdown = true;
mIOService.stop ();
m_nodeStore = nullptr;
mValidations->flush ();
mAuxService.stop ();
mJobQueue.shutdown ();
WriteLog (lsINFO, Application) << "Stopped: " << mIOService.stopped ();
mShutdown = false;
}
static void InitDB (DatabaseCon** dbCon, const char* fileName, const char* dbInit[], int dbCount)
{
*dbCon = new DatabaseCon (fileName, dbInit, dbCount);
}
#ifdef SIGINT
void sigIntHandler (int)
{
doShutdown = true;
}
#endif
// VFALCO TODO Figure this out it looks like the wrong tool
static void runAux (boost::asio::io_service& svc)
{
setCallingThreadName ("aux");
svc.run ();
}
static void runIO (boost::asio::io_service& io)
{
setCallingThreadName ("io");
io.run ();
}
// VFALCO TODO Break this function up into many small initialization segments.
// Or better yet refactor these initializations into RAII classes
// which are members of the Application object.
//
void ApplicationImp::setup ()
{
// VFALCO NOTE: 0 means use heuristics to determine the thread count.
mJobQueue.setThreadCount (0, theConfig.RUN_STANDALONE);
mSweepTimer.expires_from_now (boost::posix_time::seconds (10));
mSweepTimer.async_wait (BIND_TYPE (&ApplicationImp::sweep, this));
m_loadManager->startThread ();
#if ! BEAST_WIN32
#ifdef SIGINT
if (!theConfig.RUN_STANDALONE)
{
struct sigaction sa;
memset (&sa, 0, sizeof (sa));
sa.sa_handler = sigIntHandler;
sigaction (SIGINT, &sa, NULL);
}
#endif
#endif
assert (mTxnDB == NULL);
if (!theConfig.DEBUG_LOGFILE.empty ())
{
// Let debug messages go to the file but only WARNING or higher to regular output (unless verbose)
Log::setLogFile (theConfig.DEBUG_LOGFILE);
if (Log::getMinSeverity () > lsDEBUG)
LogPartition::setSeverity (lsDEBUG);
}
boost::thread (BIND_TYPE (runAux, boost::ref (mAuxService))).detach ();
if (!theConfig.RUN_STANDALONE)
mSNTPClient.init (theConfig.SNTP_SERVERS);
//
// Construct databases.
//
boost::thread t1 (BIND_TYPE (&InitDB, &mRpcDB, "rpc.db", RpcDBInit, RpcDBCount));
boost::thread t2 (BIND_TYPE (&InitDB, &mTxnDB, "transaction.db", TxnDBInit, TxnDBCount));
boost::thread t3 (BIND_TYPE (&InitDB, &mLedgerDB, "ledger.db", LedgerDBInit, LedgerDBCount));
boost::thread t4 (BIND_TYPE (&InitDB, &mWalletDB, "wallet.db", WalletDBInit, WalletDBCount));
t1.join ();
t2.join ();
t3.join ();
t4.join ();
leveldb::Options options;
options.create_if_missing = true;
options.block_cache = leveldb::NewLRUCache (theConfig.getSize (siHashNodeDBCache) * 1024 * 1024);
getApp().getLedgerDB ()->getDB ()->executeSQL (boost::str (boost::format ("PRAGMA cache_size=-%d;") %
(theConfig.getSize (siLgrDBCache) * 1024)));
getApp().getTxnDB ()->getDB ()->executeSQL (boost::str (boost::format ("PRAGMA cache_size=-%d;") %
(theConfig.getSize (siTxnDBCache) * 1024)));
mTxnDB->getDB ()->setupCheckpointing (&mJobQueue);
mLedgerDB->getDB ()->setupCheckpointing (&mJobQueue);
if (!theConfig.RUN_STANDALONE)
updateTables ();
mFeatures->addInitialFeatures ();
if (theConfig.START_UP == Config::FRESH)
{
WriteLog (lsINFO, Application) << "Starting new Ledger";
startNewLedger ();
}
else if ((theConfig.START_UP == Config::LOAD) || (theConfig.START_UP == Config::REPLAY))
{
WriteLog (lsINFO, Application) << "Loading specified Ledger";
if (!loadOldLedger (theConfig.START_LEDGER, theConfig.START_UP == Config::REPLAY))
{
getApp().stop ();
exit (-1);
}
}
else if (theConfig.START_UP == Config::NETWORK)
{
// This should probably become the default once we have a stable network
if (!theConfig.RUN_STANDALONE)
mNetOps->needNetworkLedger ();
startNewLedger ();
}
else
startNewLedger ();
mOrderBookDB.setup (getApp().getLedgerMaster ().getCurrentLedger ());
//
// Begin validation and ip maintenance.
// - LocalCredentials maintains local information: including identity and network connection persistence information.
//
m_localCredentials.start ();
//
// Set up UNL.
//
if (!theConfig.RUN_STANDALONE)
getUNL ().nodeBootstrap ();
mValidations->tune (theConfig.getSize (siValidationsSize), theConfig.getSize (siValidationsAge));
m_nodeStore->tune (theConfig.getSize (siNodeCacheSize), theConfig.getSize (siNodeCacheAge));
mLedgerMaster.tune (theConfig.getSize (siLedgerSize), theConfig.getSize (siLedgerAge));
mSLECache.setTargetSize (theConfig.getSize (siSLECacheSize));
mSLECache.setTargetAge (theConfig.getSize (siSLECacheAge));
mLedgerMaster.setMinValidations (theConfig.VALIDATION_QUORUM);
//
// Allow peer connections.
//
if (!theConfig.RUN_STANDALONE)
{
try
{
mPeerDoor = PeerDoor::New (
theConfig.PEER_IP,
theConfig.PEER_PORT,
theConfig.PEER_SSL_CIPHER_LIST,
mIOService);
}
catch (const std::exception& e)
{
// Must run as directed or exit.
WriteLog (lsFATAL, Application) << boost::str (boost::format ("Can not open peer service: %s") % e.what ());
exit (3);
}
}
else
{
WriteLog (lsINFO, Application) << "Peer interface: disabled";
}
//
// Allow RPC connections.
//
if (! theConfig.getRpcIP().empty () && theConfig.getRpcPort() != 0)
{
try
{
mRPCDoor = new RPCDoor (mIOService, m_rpcServerHandler);
}
catch (const std::exception& e)
{
// Must run as directed or exit.
WriteLog (lsFATAL, Application) << boost::str (boost::format ("Can not open RPC service: %s") % e.what ());
exit (3);
}
}
else
{
WriteLog (lsINFO, Application) << "RPC interface: disabled";
}
//
// Allow private WS connections.
//
if (!theConfig.WEBSOCKET_IP.empty () && theConfig.WEBSOCKET_PORT)
{
try
{
mWSPrivateDoor = WSDoor::createWSDoor (theConfig.WEBSOCKET_IP, theConfig.WEBSOCKET_PORT, false);
}
catch (const std::exception& e)
{
// Must run as directed or exit.
WriteLog (lsFATAL, Application) << boost::str (boost::format ("Can not open private websocket service: %s") % e.what ());
exit (3);
}
}
else
{
WriteLog (lsINFO, Application) << "WS private interface: disabled";
}
//
// Allow public WS connections.
//
if (!theConfig.WEBSOCKET_PUBLIC_IP.empty () && theConfig.WEBSOCKET_PUBLIC_PORT)
{
try
{
mWSPublicDoor = WSDoor::createWSDoor (theConfig.WEBSOCKET_PUBLIC_IP, theConfig.WEBSOCKET_PUBLIC_PORT, true);
}
catch (const std::exception& e)
{
// Must run as directed or exit.
WriteLog (lsFATAL, Application) << boost::str (boost::format ("Can not open public websocket service: %s") % e.what ());
exit (3);
}
}
else
{
WriteLog (lsINFO, Application) << "WS public interface: disabled";
}
//
// Begin connecting to network.
//
if (!theConfig.RUN_STANDALONE)
mPeers->start ();
if (theConfig.RUN_STANDALONE)
{
WriteLog (lsWARNING, Application) << "Running in standalone mode";
mNetOps->setStandAlone ();
}
else
{
// VFALCO NOTE the state timer resets the deadlock detector.
//
mNetOps->setStateTimer ();
}
}
void ApplicationImp::run ()
{
if (theConfig.NODE_SIZE >= 2)
{
boost::thread (BIND_TYPE (runIO, boost::ref (mIOService))).detach ();
}
if (!theConfig.RUN_STANDALONE)
{
// VFALCO NOTE This seems unnecessary. If we properly refactor the load
// manager then the deadlock detector can just always be "armed"
//
getApp().getLoadManager ().activateDeadlockDetector ();
}
mIOService.run (); // This blocks
if (mWSPublicDoor)
mWSPublicDoor->stop ();
if (mWSPrivateDoor)
mWSPrivateDoor->stop ();
// VFALCO TODO Try to not have to do this early, by using observers to
// eliminate LoadManager's dependency inversions.
//
// This deletes the object and therefore, stops the thread.
m_loadManager = nullptr;
mSweepTimer.cancel();
WriteLog (lsINFO, Application) << "Done.";
// VFALCO NOTE This is a sign that something is wrong somewhere, it
// shouldn't be necessary to sleep until some flag is set.
while (mShutdown)
boost::this_thread::sleep (boost::posix_time::milliseconds (100));
}
void ApplicationImp::sweep ()
{
boost::filesystem::space_info space = boost::filesystem::space (theConfig.DATA_DIR);
// VFALCO TODO Give this magic constant a name and move it into a well documented header
//
if (space.available < (512 * 1024 * 1024))
{
WriteLog (lsFATAL, Application) << "Remaining free disk space is less than 512MB";
getApp().stop ();
}
mJobQueue.addJob(jtSWEEP, "sweep",
BIND_TYPE(&ApplicationImp::doSweep, this, P_1));
}
void ApplicationImp::doSweep(Job& j)
{
// VFALCO NOTE Does the order of calls matter?
// VFALCO TODO fix the dependency inversion using an observer,
// have listeners register for "onSweep ()" notification.
//
mMasterTransaction.sweep ();
m_nodeStore->sweep ();
mLedgerMaster.sweep ();
mTempNodeCache.sweep ();
mValidations->sweep ();
getInboundLedgers ().sweep ();
mSLECache.sweep ();
AcceptedLedger::sweep (); // VFALCO NOTE AcceptedLedger is/has a singleton?
SHAMap::sweep (); // VFALCO NOTE SHAMap is/has a singleton?
mNetOps->sweepFetchPack ();
// VFALCO NOTE does the call to sweep() happen on another thread?
mSweepTimer.expires_from_now (boost::posix_time::seconds (theConfig.getSize (siSweepInterval)));
mSweepTimer.async_wait (BIND_TYPE (&ApplicationImp::sweep, this));
}
void ApplicationImp::startNewLedger ()
{
// New stuff.
RippleAddress rootSeedMaster = RippleAddress::createSeedGeneric ("masterpassphrase");
RippleAddress rootGeneratorMaster = RippleAddress::createGeneratorPublic (rootSeedMaster);
RippleAddress rootAddress = RippleAddress::createAccountPublic (rootGeneratorMaster, 0);
// Print enough information to be able to claim root account.
WriteLog (lsINFO, Application) << "Root master seed: " << rootSeedMaster.humanSeed ();
WriteLog (lsINFO, Application) << "Root account: " << rootAddress.humanAccountID ();
{
Ledger::pointer firstLedger = boost::make_shared<Ledger> (rootAddress, SYSTEM_CURRENCY_START);
assert (!!firstLedger->getAccountState (rootAddress));
// WRITEME: Add any default features
// WRITEME: Set default fee/reserve
firstLedger->updateHash ();
firstLedger->setClosed ();
firstLedger->setAccepted ();
mLedgerMaster.pushLedger (firstLedger);
Ledger::pointer secondLedger = boost::make_shared<Ledger> (true, boost::ref (*firstLedger));
secondLedger->setClosed ();
secondLedger->setAccepted ();
mLedgerMaster.pushLedger (secondLedger, boost::make_shared<Ledger> (true, boost::ref (*secondLedger)), false);
assert (!!secondLedger->getAccountState (rootAddress));
mNetOps->setLastCloseTime (secondLedger->getCloseTimeNC ());
}
}
bool ApplicationImp::loadOldLedger (const std::string& l, bool bReplay)
{
try
{
Ledger::pointer loadLedger, replayLedger;
if (l.empty () || (l == "latest"))
loadLedger = Ledger::getLastFullLedger ();
else if (l.length () == 64)
{
// by hash
uint256 hash;
hash.SetHex (l);
loadLedger = Ledger::loadByHash (hash);
}
else // assume by sequence
loadLedger = Ledger::loadByIndex (boost::lexical_cast<uint32> (l));
if (!loadLedger)
{
WriteLog (lsFATAL, Application) << "No Ledger found?" << std::endl;
return false;
}
if (bReplay)
{ // Replay a ledger close with same prior ledger and transactions
replayLedger = loadLedger; // this ledger holds the transactions we want to replay
loadLedger = Ledger::loadByIndex (replayLedger->getLedgerSeq() - 1); // this is the prior ledger
if (!loadLedger || (replayLedger->getParentHash() != loadLedger->getHash()))
{
WriteLog (lsFATAL, Application) << "Replay ledger missing/damaged";
assert (false);
return false;
}
}
loadLedger->setClosed ();
WriteLog (lsINFO, Application) << "Loading ledger " << loadLedger->getHash () << " seq:" << loadLedger->getLedgerSeq ();
if (loadLedger->getAccountHash ().isZero ())
{
WriteLog (lsFATAL, Application) << "Ledger is empty.";
assert (false);
return false;
}
if (!loadLedger->walkLedger ())
{
WriteLog (lsFATAL, Application) << "Ledger is missing nodes.";
return false;
}
if (!loadLedger->assertSane ())
{
WriteLog (lsFATAL, Application) << "Ledger is not sane.";
return false;
}
mLedgerMaster.setLedgerRangePresent (loadLedger->getLedgerSeq (), loadLedger->getLedgerSeq ());
Ledger::pointer openLedger = boost::make_shared<Ledger> (false, boost::ref (*loadLedger));
mLedgerMaster.switchLedgers (loadLedger, openLedger);
mLedgerMaster.forceValid(loadLedger);
mNetOps->setLastCloseTime (loadLedger->getCloseTimeNC ());
if (bReplay)
{ // inject transaction from replayLedger into consensus set
SHAMap::ref txns = replayLedger->peekTransactionMap();
Ledger::ref cur = getLedgerMaster().getCurrentLedger();
for (SHAMapItem::pointer it = txns->peekFirstItem(); it != nullptr; it = txns->peekNextItem(it->getTag()))
{
Transaction::pointer txn = replayLedger->getTransaction(it->getTag());
WriteLog (lsINFO, Application) << txn->getJson(0);
Serializer s;
txn->getSTransaction()->add(s);
if (!cur->addTransaction(it->getTag(), s))
{
WriteLog (lsWARNING, Application) << "Unable to add transaction " << it->getTag();
}
}
}
}
catch (SHAMapMissingNode&)
{
WriteLog (lsFATAL, Application) << "Data is missing for selected ledger";
return false;
}
catch (boost::bad_lexical_cast&)
{
WriteLog (lsFATAL, Application) << "Ledger specified '" << l << "' is not valid";
return false;
}
return true;
}
bool serverOkay (std::string& reason)
{
if (!theConfig.ELB_SUPPORT)
return true;
if (getApp().isShutdown ())
{
reason = "Server is shutting down";
return false;
}
if (getApp().getOPs ().isNeedNetworkLedger ())
{
reason = "Not synchronized with network yet";
return false;
}
if (getApp().getOPs ().getOperatingMode () < NetworkOPs::omSYNCING)
{
reason = "Not synchronized with network";
return false;
}
if (!getApp().getLedgerMaster().isCaughtUp(reason))
return false;
if (getApp().getFeeTrack ().isLoadedLocal ())
{
reason = "Too much load";
return false;
}
if (getApp().getOPs ().isFeatureBlocked ())
{
reason = "Server version too old";
return false;
}
return true;
}
//VFALCO TODO clean this up since it is just a file holding a single member function definition
static std::vector<std::string> getSchema (DatabaseCon* dbc, const std::string& dbName)
{
std::vector<std::string> schema;
std::string sql = "SELECT sql FROM sqlite_master WHERE tbl_name='";
sql += dbName;
sql += "';";
SQL_FOREACH (dbc->getDB (), sql)
{
dbc->getDB ()->getStr ("sql", sql);
schema.push_back (sql);
}
return schema;
}
static bool schemaHas (DatabaseCon* dbc, const std::string& dbName, int line, const std::string& content)
{
std::vector<std::string> schema = getSchema (dbc, dbName);
if (static_cast<int> (schema.size ()) <= line)
{
Log (lsFATAL) << "Schema for " << dbName << " has too few lines";
throw std::runtime_error ("bad schema");
}
return schema[line].find (content) != std::string::npos;
}
static void addTxnSeqField ()
{
if (schemaHas (getApp().getTxnDB (), "AccountTransactions", 0, "TxnSeq"))
return;
Log (lsWARNING) << "Transaction sequence field is missing";
Database* db = getApp().getTxnDB ()->getDB ();
std::vector< std::pair<uint256, int> > txIDs;
txIDs.reserve (300000);
Log (lsINFO) << "Parsing transactions";
int i = 0;
uint256 transID;
SQL_FOREACH (db, "SELECT TransID,TxnMeta FROM Transactions;")
{
Blob rawMeta;
int metaSize = 2048;
rawMeta.resize (metaSize);
metaSize = db->getBinary ("TxnMeta", &*rawMeta.begin (), rawMeta.size ());
if (metaSize > static_cast<int> (rawMeta.size ()))
{
rawMeta.resize (metaSize);
db->getBinary ("TxnMeta", &*rawMeta.begin (), rawMeta.size ());
}
else rawMeta.resize (metaSize);
std::string tid;
db->getStr ("TransID", tid);
transID.SetHex (tid, true);
if (rawMeta.size () == 0)
{
txIDs.push_back (std::make_pair (transID, -1));
Log (lsINFO) << "No metadata for " << transID;
}
else
{
TransactionMetaSet m (transID, 0, rawMeta);
txIDs.push_back (std::make_pair (transID, m.getIndex ()));
}
if ((++i % 1000) == 0)
Log (lsINFO) << i << " transactions read";
}
Log (lsINFO) << "All " << i << " transactions read";
db->executeSQL ("BEGIN TRANSACTION;");
Log (lsINFO) << "Dropping old index";
db->executeSQL ("DROP INDEX AcctTxIndex;");
Log (lsINFO) << "Altering table";
db->executeSQL ("ALTER TABLE AccountTransactions ADD COLUMN TxnSeq INTEGER;");
typedef std::pair<uint256, int> u256_int_pair_t;
boost::format fmt ("UPDATE AccountTransactions SET TxnSeq = %d WHERE TransID = '%s';");
i = 0;
BOOST_FOREACH (u256_int_pair_t & t, txIDs)
{
db->executeSQL (boost::str (fmt % t.second % t.first.GetHex ()));
if ((++i % 1000) == 0)
Log (lsINFO) << i << " transactions updated";
}
Log (lsINFO) << "Building new index";
db->executeSQL ("CREATE INDEX AcctTxIndex ON AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);");
db->executeSQL ("END TRANSACTION;");
}
void ApplicationImp::updateTables ()
{
if (theConfig.nodeDatabase.size () <= 0)
{
Log (lsFATAL) << "The [node_db] configuration setting has been updated and must be set";
StopSustain ();
exit (1);
}
// perform any needed table updates
assert (schemaHas (getApp().getTxnDB (), "AccountTransactions", 0, "TransID"));
assert (!schemaHas (getApp().getTxnDB (), "AccountTransactions", 0, "foobar"));
addTxnSeqField ();
if (schemaHas (getApp().getTxnDB (), "AccountTransactions", 0, "PRIMARY"))
{
Log (lsFATAL) << "AccountTransactions database should not have a primary key";
StopSustain ();
exit (1);
}
if (theConfig.importNodeDatabase.size () > 0)
{
ScopedPointer <NodeStore> source (NodeStore::New (theConfig.importNodeDatabase));
WriteLog (lsWARNING, NodeObject) <<
"Node import from '" << source->getName () << "' to '"
<< getApp().getNodeStore().getName () << "'.";
getApp().getNodeStore().import (*source);
}
}
//------------------------------------------------------------------------------
Application& getApp ()
{
return *ApplicationImp::getInstance ();
}

View File

@@ -0,0 +1,208 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IAPPLICATION_H
#define RIPPLE_IAPPLICATION_H
// VFALCO TODO Fix forward declares required for header dependency loops
class IFeatures;
class IFeeVote;
class IHashRouter;
class ILoadFeeTrack;
class IPeers;
class IProofOfWorkFactory;
class UniqueNodeList;
class IValidations;
class NodeStore;
class JobQueue;
class InboundLedgers;
class LedgerMaster;
class LoadManager;
class NetworkOPs;
class OrderBookDB;
class PeerDoor;
class SerializedLedgerEntry;
class TransactionMaster;
class TXQueue;
class LocalCredentials;
class DatabaseCon;
typedef TaggedCache <uint256, Blob , UptimeTimerAdapter> NodeCache;
typedef TaggedCache <uint256, SerializedLedgerEntry, UptimeTimerAdapter> SLECache;
class Application
{
public:
/* VFALCO NOTE
The master lock protects:
- The open ledger
- Server global state
* What the last closed ledger is
* State of the consensus engine
other things
*/
#if 1
class ScopedLockType;
class MasterLockType
{
public:
MasterLockType ()
: m_fileName ("")
, m_lineNumber (0)
{
}
// Note that these are not exactly thread safe.
char const* getFileName () const noexcept
{
return m_fileName.get ();
}
int getLineNumber () const noexcept
{
return m_lineNumber.get ();
}
private:
friend class ScopedLockType;
void setOwner (char const* fileName, int lineNumber)
{
m_fileName.set (fileName);
m_lineNumber.set (lineNumber);
}
void resetOwner ()
{
m_fileName.set ("");
m_lineNumber.set (0);
}
boost::recursive_mutex m_mutex;
Atomic <char const*> m_fileName;
Atomic <int> m_lineNumber;
};
class ScopedLockType
{
public:
explicit ScopedLockType (MasterLockType& mutex,
char const* fileName,
int lineNumber)
: m_mutex (mutex)
, m_lock (mutex.m_mutex)
{
mutex.setOwner (fileName, lineNumber);
}
~ScopedLockType ()
{
if (m_lock.owns_lock ())
m_mutex.resetOwner ();
}
void unlock ()
{
if (m_lock.owns_lock ())
m_mutex.resetOwner ();
m_lock.unlock ();
}
private:
MasterLockType& m_mutex;
boost::recursive_mutex::scoped_lock m_lock;
};
#else
typedef boost::recursive_mutex MasterLockType;
typedef boost::recursive_mutex::scoped_lock ScopedLockType;
#endif
virtual MasterLockType& getMasterLock () = 0;
public:
struct State
{
// Stuff in here is accessed concurrently and requires a WriteAccess
};
typedef SharedData <State> SharedState;
SharedState& getSharedState () noexcept { return m_sharedState; }
SharedState const& getSharedState () const noexcept { return m_sharedState; }
private:
SharedState m_sharedState;
public:
virtual ~Application () { }
virtual boost::asio::io_service& getIOService () = 0;
virtual NodeCache& getTempNodeCache () = 0;
virtual SLECache& getSLECache () = 0;
virtual Validators& getValidators () = 0;
virtual IFeatures& getFeatureTable () = 0;
virtual IFeeVote& getFeeVote () = 0;
virtual IHashRouter& getHashRouter () = 0;
virtual ILoadFeeTrack& getFeeTrack () = 0;
virtual ILoadManager& getLoadManager () = 0;
virtual IPeers& getPeers () = 0;
virtual IProofOfWorkFactory& getProofOfWorkFactory () = 0;
virtual UniqueNodeList& getUNL () = 0;
virtual IValidations& getValidations () = 0;
virtual NodeStore& getNodeStore () = 0;
virtual JobQueue& getJobQueue () = 0;
virtual InboundLedgers& getInboundLedgers () = 0;
virtual LedgerMaster& getLedgerMaster () = 0;
virtual NetworkOPs& getOPs () = 0;
virtual OrderBookDB& getOrderBookDB () = 0;
virtual PeerDoor& getPeerDoor () = 0;
virtual TransactionMaster& getMasterTransaction () = 0;
virtual TXQueue& getTxnQueue () = 0;
virtual LocalCredentials& getLocalCredentials () = 0;
virtual DatabaseCon* getRpcDB () = 0;
virtual DatabaseCon* getTxnDB () = 0;
virtual DatabaseCon* getLedgerDB () = 0;
/** Retrieve the "wallet database"
It looks like this is used to store the unique node list.
*/
// VFALCO TODO Rename, document this
// NOTE This will be replaced by class Validators
//
virtual DatabaseCon* getWalletDB () = 0;
virtual bool getSystemTimeOffset (int& offset) = 0;
virtual bool isShutdown () = 0;
virtual bool running () = 0;
virtual void setup () = 0;
virtual void run () = 0;
virtual void stop () = 0;
virtual void sweep () = 0;
};
extern Application& getApp ();
#endif

View File

@@ -0,0 +1,276 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_ILOADMANAGER_H
#define RIPPLE_ILOADMANAGER_H
// types of load that can be placed on the server
// VFALCO TODO replace LT_ with loadType in constants
enum LoadType
{
// Bad things
LT_InvalidRequest, // A request that we can immediately tell is invalid
LT_RequestNoReply, // A request that we cannot satisfy
LT_InvalidSignature, // An object whose signature we had to check and it failed
LT_UnwantedData, // Data we have no use for
LT_BadPoW, // Proof of work not valid
LT_BadData, // Data we have to verify before rejecting
// RPC loads
LT_RPCInvalid, // An RPC request that we can immediately tell is invalid.
LT_RPCReference, // A default "reference" unspecified load
LT_RPCException, // An RPC load that causes an exception
LT_RPCBurden, // A particularly burdensome RPC load
// Good things
LT_NewTrusted, // A new transaction/validation/proposal we trust
LT_NewTransaction, // A new, valid transaction
LT_NeededData, // Data we requested
// Requests
LT_RequestData, // A request that is hard to satisfy, disk access
LT_CheapQuery, // A query that is trivial, cached data
LT_MAX // MUST BE LAST
};
//------------------------------------------------------------------------------
/** Tracks the consumption of resources at an endpoint.
To prevent monopolization of server resources or attacks on servers,
resource consumption is monitored at each endpoint. When consumption
exceeds certain thresholds, costs are imposed. Costs include charging
additional XRP for transactions, requiring a proof of work to be
performed, or simply disconnecting the endpoint.
Currently, consumption endpoints include websocket connections used to
service clients, and peer connections used to create the peer to peer
overlay network implementing the Ripple protcool.
The current "balance" of a LoadSource represents resource consumption
debt or credit. Debt is accrued when bad loads are imposed. Credit is
granted when good loads are imposed. When the balance crosses heuristic
thresholds, costs are increased on the endpoint.
The balance is represented as a unitless relative quantity.
@note Although RPC connections consume resources, they are transient and
cannot be rate limited. It is advised not to expose RPC interfaces
to the general public.
*/
class LoadSource
{
public:
// VFALCO TODO Use these dispositions
/*
enum Disposition
{
none,
shouldWarn,
shouldDrop,
};
*/
/** Construct a load source.
Sources with admin privileges have relaxed or no restrictions
on resource consumption.
@param admin `true` if the source should have admin privileges.
*/
// VFALCO TODO See who is constructing this with a parameter
explicit LoadSource (bool admin)
: mBalance (0)
, mFlags (admin ? lsfPrivileged : 0)
, mLastUpdate (UptimeTimer::getInstance ().getElapsedSeconds ())
, mLastWarning (0)
, mLogged (false)
{
}
/** Construct a load source with a given name.
The endpoint is considered non-privileged.
*/
explicit LoadSource (std::string const& name)
: mName (name)
, mBalance (0)
, mFlags (0)
, mLastUpdate (UptimeTimer::getInstance ().getElapsedSeconds ())
, mLastWarning (0)
, mLogged (false)
{
}
/** Change the name of the source.
An endpoint can be created before it's name is known. For example,
on an incoming connection before the IP and port have been determined.
*/
// VFALCO TODO Figure out a way to construct the LoadSource object with
// the proper name instead of renaming it later.
//
void rename (std::string const& name)
{
mName = name;
}
/** Retrieve the name of this endpoint.
*/
std::string const& getName () const
{
return mName;
}
/** Determine if this endpoint is privileged.
*/
bool isPrivileged () const
{
return (mFlags & lsfPrivileged) != 0;
}
/** Grant the privileged attribute on this endpoint.
*/
void setPrivileged ()
{
mFlags |= lsfPrivileged;
}
/** Retrieve the load debit or credit associated with the endpoint.
The balance is represented in a unitless relative quantity
indicating the heuristically weighted amount of resource consumption.
*/
int getBalance () const
{
return mBalance;
}
/** Returns true if the endpoint received a log warning.
*/
bool isLogged () const
{
return mLogged;
}
/** Reset the flag indicating the endpoint received a log warning.
*/
void clearLogged ()
{
mLogged = false;
}
/** Indicate that this endpoint is an outgoing connection.
*/
void setOutbound ()
{
mFlags |= lsfOutbound;
}
/** Returns true if this endpoint is an outgoing connection.
*/
bool isOutbound () const
{
return (mFlags & lsfOutbound) != 0;
}
private:
// VFALCO Make this not a friend
friend class LoadManager;
static const int lsfPrivileged = 1;
static const int lsfOutbound = 2;
private:
std::string mName;
int mBalance;
int mFlags;
int mLastUpdate;
int mLastWarning;
bool mLogged;
};
//------------------------------------------------------------------------------
/** Manages load sources.
This object creates an associated thread to maintain a clock.
When the server is overloaded by a particular peer it issues a warning
first. This allows friendly peers to reduce their consumption of resources,
or disconnect from the server.
The warning system is used instead of merely dropping, because hostile
peers can just reconnect anyway.
@see LoadSource, LoadType
*/
class ILoadManager
{
public:
/** Create a new manager.
The manager thread begins running immediately.
@note The thresholds for warnings and punishments are in
the ctor-initializer
*/
static ILoadManager* New ();
/** Destroy the manager.
The destructor returns only after the thread has stopped.
*/
virtual ~ILoadManager () { }
/** Start the associated thread.
This is here to prevent the deadlock detector from activating during
a lengthy program initialization.
*/
// VFALCO TODO Simplify the two stage initialization to one stage (construction).
// NOTE In stand-alone mode the load manager thread isn't started
virtual void startThread () = 0;
/** Turn on deadlock detection.
The deadlock detector begins in a disabled state. After this function
is called, it will report deadlocks using a separate thread whenever
the reset function is not called at least once per 10 seconds.
@see resetDeadlockDetector
*/
// VFALCO NOTE it seems that the deadlock detector has an "armed" state to prevent it
// from going off during program startup if there's a lengthy initialization
// operation taking place?
//
virtual void activateDeadlockDetector () = 0;
/** Reset the deadlock detection timer.
A dedicated thread monitors the deadlock timer, and if too much
time passes it will produce log warnings.
*/
virtual void resetDeadlockDetector () = 0;
/** Update an endpoint to reflect an imposed load.
The balance of the endpoint is adjusted based on the heuristic cost
of the indicated load.
@return `true` if the endpoint should be warned or punished.
*/
virtual bool applyLoadCharge (LoadSource& sourceToAdjust, LoadType loadToImpose) const = 0;
// VFALCO TODO Eliminate these two functions and just make applyLoadCharge()
// return a LoadSource::Disposition
//
virtual bool shouldWarn (LoadSource&) const = 0;
virtual bool shouldCutoff (LoadSource&) const = 0;
};
#endif

View File

@@ -0,0 +1,426 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (LoadManager)
//------------------------------------------------------------------------------
class LoadManager
: public ILoadManager
, public beast::InterruptibleThread::EntryPoint
{
private:
/* Entry mapping utilization to cost.
The cost is expressed as a unitless relative quantity. These
mappings are statically loaded at startup with heuristic values.
*/
class Cost
{
public:
// VFALCO TODO Eliminate this default ctor somehow
Cost ()
: m_loadType ()
, m_cost (0)
, m_resourceFlags (0)
{
}
Cost (LoadType loadType, int cost, int resourceFlags)
: m_loadType (loadType)
, m_cost (cost)
, m_resourceFlags (resourceFlags)
{
}
LoadType getLoadType () const
{
return m_loadType;
}
int getCost () const
{
return m_cost;
}
int getResourceFlags () const
{
return m_resourceFlags;
}
public:
// VFALCO TODO Make these private and const
LoadType m_loadType;
int m_cost;
int m_resourceFlags;
};
public:
LoadManager ()
: m_thread ("loadmgr")
, m_logThread ("loadmgr_log")
, mCreditRate (100)
, mCreditLimit (500)
, mDebitWarn (-500)
, mDebitLimit (-1000)
, mArmed (false)
, mDeadLock (0)
, mCosts (LT_MAX)
{
m_logThread.start ();
/** Flags indicating the type of load.
Utilization may include any combination of:
- CPU
- Storage space
- Network transfer
*/
// VFALCO NOTE These flags are not used...
enum
{
flagDisk = 1,
flagCpu = 2,
flagNet = 4
};
// VFALCO TODO Replace this with a function that uses a simple switch statement...
//
addCost (Cost (LT_InvalidRequest, -10, flagCpu | flagNet));
addCost (Cost (LT_RequestNoReply, -1, flagCpu | flagDisk));
addCost (Cost (LT_InvalidSignature, -100, flagCpu));
addCost (Cost (LT_UnwantedData, -5, flagCpu | flagNet));
addCost (Cost (LT_BadData, -20, flagCpu));
addCost (Cost (LT_RPCInvalid, -10, flagCpu | flagNet));
addCost (Cost (LT_RPCReference, -10, flagCpu | flagNet));
addCost (Cost (LT_RPCException, -20, flagCpu | flagNet));
addCost (Cost (LT_RPCBurden, -50, flagCpu | flagNet));
// VFALCO NOTE Why do these supposedly "good" load types still have a negative cost?
//
addCost (Cost (LT_NewTrusted, -10, 0));
addCost (Cost (LT_NewTransaction, -2, 0));
addCost (Cost (LT_NeededData, -10, 0));
addCost (Cost (LT_RequestData, -5, flagDisk | flagNet));
addCost (Cost (LT_CheapQuery, -1, flagCpu));
UptimeTimer::getInstance ().beginManualUpdates ();
}
private:
~LoadManager ()
{
UptimeTimer::getInstance ().endManualUpdates ();
m_thread.interrupt ();
}
void startThread ()
{
m_thread.start (this);
}
void canonicalize (LoadSource& source, int now) const
{
if (source.mLastUpdate != now)
{
if (source.mLastUpdate < now)
{
source.mBalance += mCreditRate * (now - source.mLastUpdate);
if (source.mBalance > mCreditLimit)
{
source.mBalance = mCreditLimit;
source.mLogged = false;
}
}
source.mLastUpdate = now;
}
}
bool shouldWarn (LoadSource& source) const
{
{
boost::mutex::scoped_lock sl (mLock);
int now = UptimeTimer::getInstance ().getElapsedSeconds ();
canonicalize (source, now);
if (source.isPrivileged () || (source.mBalance > mDebitWarn) || (source.mLastWarning == now))
return false;
source.mLastWarning = now;
}
logWarning (source.getName ());
return true;
}
bool shouldCutoff (LoadSource& source) const
{
{
boost::mutex::scoped_lock sl (mLock);
int now = UptimeTimer::getInstance ().getElapsedSeconds ();
canonicalize (source, now);
if (source.isPrivileged () || (source.mBalance > mDebitLimit))
return false;
if (source.mLogged)
return true;
source.mLogged = true;
}
logDisconnect (source.getName ());
return true;
}
bool applyLoadCharge (LoadSource& source, LoadType loadType) const
{
// FIXME: Scale by category
Cost cost = mCosts[static_cast<int> (loadType)];
return adjust (source, cost.m_cost);
}
bool adjust (LoadSource& source, int credits) const
{
// return: true = need to warn/cutoff
// We do it this way in case we want to add exponential decay later
int now = UptimeTimer::getInstance ().getElapsedSeconds ();
boost::mutex::scoped_lock sl (mLock);
canonicalize (source, now);
source.mBalance += credits;
if (source.mBalance > mCreditLimit)
source.mBalance = mCreditLimit;
if (source.isPrivileged ()) // privileged sources never warn/cutoff
return false;
if ( (source.mBalance >= mDebitWarn) ||
((source.mBalance >= mDebitLimit) && (source.mLastWarning == now)))
return false;
return true;
}
void logWarning (const std::string& source) const
{
if (source.empty ())
WriteLog (lsDEBUG, LoadManager) << "Load warning from empty source";
else
WriteLog (lsINFO, LoadManager) << "Load warning: " << source;
}
void logDisconnect (const std::string& source) const
{
if (source.empty ())
WriteLog (lsINFO, LoadManager) << "Disconnect for empty source";
else
WriteLog (lsWARNING, LoadManager) << "Disconnect for: " << source;
}
// VFALCO TODO Implement this and stop accessing the vector directly
//Cost const& getCost (LoadType loadType) const;
int getCost (LoadType t) const
{
return mCosts [static_cast <int> (t)].getCost ();
}
void resetDeadlockDetector ()
{
boost::mutex::scoped_lock sl (mLock);
mDeadLock = UptimeTimer::getInstance ().getElapsedSeconds ();
}
void activateDeadlockDetector ()
{
mArmed = true;
}
static void logDeadlock (int dlTime)
{
WriteLog (lsWARNING, LoadManager) << "Server stalled for " << dlTime << " seconds.";
char const* fileName = getApp ().getMasterLock ().getFileName ();
int lineNumber = getApp ().getMasterLock ().getLineNumber ();
WriteLog (lsWARNING, LoadManager) << "Master lock owned by " << File (fileName).getFileName ().toStdString () << ", line " << lineNumber;
}
private:
// VFALCO TODO These used to be public but are apparently not used. Find out why.
/*
int getCreditRate () const
{
boost::mutex::scoped_lock sl (mLock);
return mCreditRate;
}
int getCreditLimit () const
{
boost::mutex::scoped_lock sl (mLock);
return mCreditLimit;
}
int getDebitWarn () const
{
boost::mutex::scoped_lock sl (mLock);
return mDebitWarn;
}
int getDebitLimit () const
{
boost::mutex::scoped_lock sl (mLock);
return mDebitLimit;
}
void setCreditRate (int r)
{
boost::mutex::scoped_lock sl (mLock);
mCreditRate = r;
}
void setCreditLimit (int r)
{
boost::mutex::scoped_lock sl (mLock);
mCreditLimit = r;
}
void setDebitWarn (int r)
{
boost::mutex::scoped_lock sl (mLock);
mDebitWarn = r;
}
void setDebitLimit (int r)
{
boost::mutex::scoped_lock sl (mLock);
mDebitLimit = r;
}
*/
private:
void addCost (const Cost& c)
{
mCosts [static_cast <int> (c.getLoadType ())] = c;
}
// VFALCO NOTE Where's the thread object? It's not a data member...
//
void threadRun ()
{
// VFALCO TODO replace this with a beast Time object?
//
// Initialize the clock to the current time.
boost::posix_time::ptime t = boost::posix_time::microsec_clock::universal_time ();
while (! m_thread.interruptionPoint ())
{
{
// VFALCO NOTE What is this lock protecting?
boost::mutex::scoped_lock sl (mLock);
// VFALCO NOTE I think this is to reduce calls to the operating system
// for retrieving the current time.
//
// TODO Instead of incrementing can't we just retrieve the current
// time again?
//
// Manually update the timer.
UptimeTimer::getInstance ().incrementElapsedTime ();
// Measure the amount of time we have been deadlocked, in seconds.
//
// VFALCO NOTE mDeadLock is a canary for detecting the condition.
int const timeSpentDeadlocked = UptimeTimer::getInstance ().getElapsedSeconds () - mDeadLock;
// VFALCO NOTE I think that "armed" refers to the deadlock detector
//
if (mArmed && (timeSpentDeadlocked >= 10))
{
// Report the deadlocked condition every 10 seconds
if ((timeSpentDeadlocked % 10) == 0)
{
// VFALCO TODO Replace this with a dedicated thread with call queue.
//
m_logThread.call (&logDeadlock, timeSpentDeadlocked);
}
// If we go over 500 seconds spent deadlocked, it means that the
// deadlock resolution code has failed, which qualifies as undefined
// behavior.
//
assert (timeSpentDeadlocked < 500);
}
}
bool change;
// VFALCO TODO Eliminate the dependence on the Application object.
// Choices include constructing with the job queue / feetracker.
// Another option is using an observer pattern to invert the dependency.
if (getApp().getJobQueue ().isOverloaded ())
{
WriteLog (lsINFO, LoadManager) << getApp().getJobQueue ().getJson (0);
change = getApp().getFeeTrack ().raiseLocalFee ();
}
else
{
change = getApp().getFeeTrack ().lowerLocalFee ();
}
if (change)
{
// VFALCO TODO replace this with a Listener / observer and subscribe in NetworkOPs or Application
getApp().getOPs ().reportFeeChange ();
}
t += boost::posix_time::seconds (1);
boost::posix_time::time_duration when = t - boost::posix_time::microsec_clock::universal_time ();
if ((when.is_negative ()) || (when.total_seconds () > 1))
{
WriteLog (lsWARNING, LoadManager) << "time jump";
t = boost::posix_time::microsec_clock::universal_time ();
}
else
{
boost::this_thread::sleep (when);
}
}
}
private:
beast::InterruptibleThread m_thread;
beast::ThreadWithCallQueue m_logThread;
int mCreditRate; // credits gained/lost per second
int mCreditLimit; // the most credits a source can have
int mDebitWarn; // when a source drops below this, we warn
int mDebitLimit; // when a source drops below this, we cut it off (should be negative)
bool mArmed;
int mDeadLock; // Detect server deadlocks
mutable boost::mutex mLock; // VFALCO TODO Replace with juce::Mutex and remove the mutable attribute
std::vector <Cost> mCosts;
};
//------------------------------------------------------------------------------
ILoadManager* ILoadManager::New ()
{
return new LoadManager;
}

View File

@@ -0,0 +1,170 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
LocalCredentials::LocalCredentials () : mDh512 (NULL), mDh1024 (NULL), mLedger (0)
{
;
}
void LocalCredentials::start ()
{
// We need our node identity before we begin networking.
// - Allows others to identify if they have connected multiple times.
// - Determines our CAS routing and responsibilities.
// - This is not our validation identity.
if (!nodeIdentityLoad ())
{
nodeIdentityCreate ();
if (!nodeIdentityLoad ())
throw std::runtime_error ("unable to retrieve new node identity.");
}
if (!theConfig.QUIET)
Log::out() << "NodeIdentity: " << mNodePublicKey.humanNodePublic ();
getApp().getUNL ().start ();
}
// Retrieve network identity.
bool LocalCredentials::nodeIdentityLoad ()
{
Database* db = getApp().getWalletDB ()->getDB ();
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
bool bSuccess = false;
if (db->executeSQL ("SELECT * FROM NodeIdentity;") && db->startIterRows ())
{
std::string strPublicKey, strPrivateKey;
db->getStr ("PublicKey", strPublicKey);
db->getStr ("PrivateKey", strPrivateKey);
mNodePublicKey.setNodePublic (strPublicKey);
mNodePrivateKey.setNodePrivate (strPrivateKey);
mDh512 = DH_der_load (db->getStrBinary ("Dh512"));
mDh1024 = DH_der_load (db->getStrBinary ("Dh1024"));
db->endIterRows ();
bSuccess = true;
}
if (theConfig.NODE_PUB.isValid () && theConfig.NODE_PRIV.isValid ())
{
mNodePublicKey = theConfig.NODE_PUB;
mNodePrivateKey = theConfig.NODE_PRIV;
}
return bSuccess;
}
// Create and store a network identity.
bool LocalCredentials::nodeIdentityCreate ()
{
if (!theConfig.QUIET)
Log::out() << "NodeIdentity: Creating.";
//
// Generate the public and private key
//
RippleAddress naSeed = RippleAddress::createSeedRandom ();
RippleAddress naNodePublic = RippleAddress::createNodePublic (naSeed);
RippleAddress naNodePrivate = RippleAddress::createNodePrivate (naSeed);
// Make new key.
#ifdef CREATE_NEW_DH_PARAMS
std::string strDh512 = DH_der_gen (512);
#else
static const unsigned char dh512Param[] =
{
0x30, 0x46, 0x02, 0x41, 0x00, 0x98, 0x15, 0xd2, 0xd0, 0x08, 0x32, 0xda,
0xaa, 0xac, 0xc4, 0x71, 0xa3, 0x1b, 0x11, 0xf0, 0x6c, 0x62, 0xb2, 0x35,
0x8a, 0x10, 0x92, 0xc6, 0x0a, 0xa3, 0x84, 0x7e, 0xaf, 0x17, 0x29, 0x0b,
0x70, 0xef, 0x07, 0x4f, 0xfc, 0x9d, 0x6d, 0x87, 0x99, 0x19, 0x09, 0x5b,
0x6e, 0xdb, 0x57, 0x72, 0x4a, 0x7e, 0xcd, 0xaf, 0xbd, 0x3a, 0x97, 0x55,
0x51, 0x77, 0x5a, 0x34, 0x7c, 0xe8, 0xc5, 0x71, 0x63, 0x02, 0x01, 0x02
};
std::string strDh512 (reinterpret_cast<const char*> (dh512Param), sizeof (dh512Param));
#endif
#if 1
std::string strDh1024 = strDh512; // For testing and most cases 512 is fine.
#else
std::string strDh1024 = DH_der_gen (1024);
#endif
//
// Store the node information
//
Database* db = getApp().getWalletDB ()->getDB ();
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
db->executeSQL (str (boost::format ("INSERT INTO NodeIdentity (PublicKey,PrivateKey,Dh512,Dh1024) VALUES ('%s','%s',%s,%s);")
% naNodePublic.humanNodePublic ()
% naNodePrivate.humanNodePrivate ()
% sqlEscape (strDh512)
% sqlEscape (strDh1024)));
// XXX Check error result.
if (!theConfig.QUIET)
Log::out() << "NodeIdentity: Created.";
return true;
}
bool LocalCredentials::dataDelete (const std::string& strKey)
{
Database* db = getApp().getRpcDB ()->getDB ();
ScopedLock sl (getApp().getRpcDB ()->getDBLock ());
return db->executeSQL (str (boost::format ("DELETE FROM RPCData WHERE Key=%s;")
% sqlEscape (strKey)));
}
bool LocalCredentials::dataFetch (const std::string& strKey, std::string& strValue)
{
Database* db = getApp().getRpcDB ()->getDB ();
ScopedLock sl (getApp().getRpcDB ()->getDBLock ());
bool bSuccess = false;
if (db->executeSQL (str (boost::format ("SELECT Value FROM RPCData WHERE Key=%s;")
% sqlEscape (strKey))) && db->startIterRows ())
{
Blob vucData = db->getBinary ("Value");
strValue.assign (vucData.begin (), vucData.end ());
db->endIterRows ();
bSuccess = true;
}
return bSuccess;
}
bool LocalCredentials::dataStore (const std::string& strKey, const std::string& strValue)
{
Database* db = getApp().getRpcDB ()->getDB ();
ScopedLock sl (getApp().getRpcDB ()->getDBLock ());
bool bSuccess = false;
return (db->executeSQL (str (boost::format ("REPLACE INTO RPCData (Key, Value) VALUES (%s,%s);")
% sqlEscape (strKey)
% sqlEscape (strValue)
)));
return bSuccess;
}
// vim:ts=4

View File

@@ -0,0 +1,67 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_LOCALCREDENTIALS_H
#define RIPPLE_LOCALCREDENTIALS_H
/** Holds the cryptographic credentials identifying this instance of the server.
*/
class LocalCredentials : Uncopyable
{
public:
LocalCredentials ();
// Begin processing.
// - Maintain peer connectivity through validation and peer management.
void start ();
RippleAddress const& getNodePublic () const
{
return mNodePublicKey;
}
RippleAddress const& getNodePrivate () const
{
return mNodePrivateKey;
}
DH* getDh512 () const
{
return DHparams_dup (mDh512);
}
DH* getDh1024 () const
{
return DHparams_dup (mDh1024);
}
// Local persistence of RPC clients
bool dataDelete (std::string const& strKey);
// VFALCO NOTE why is strValue non-const?
bool dataFetch (std::string const& strKey, std::string& strValue);
bool dataStore (std::string const& strKey, std::string const& strValue);
private:
LocalCredentials (LocalCredentials const&); // disallowed
LocalCredentials& operator= (const LocalCredentials&); // disallowed
bool nodeIdentityLoad ();
bool nodeIdentityCreate ();
private:
boost::recursive_mutex mLock;
RippleAddress mNodePublicKey;
RippleAddress mNodePrivateKey;
DH* mDh512;
DH* mDh1024;
LedgerIndex mLedger; // ledger we last synched to
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,450 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
namespace po = boost::program_options;
void setupServer ()
{
#ifdef RLIMIT_NOFILE
struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
{
if (rl.rlim_cur != rl.rlim_max)
{
rl.rlim_cur = rl.rlim_max;
setrlimit(RLIMIT_NOFILE, &rl);
}
}
#endif
getApp().setup ();
}
void startServer ()
{
//
// Execute start up rpc commands.
//
if (theConfig.RPC_STARTUP.isArray ())
{
for (int i = 0; i != theConfig.RPC_STARTUP.size (); ++i)
{
const Json::Value& jvCommand = theConfig.RPC_STARTUP[i];
if (!theConfig.QUIET)
Log::out() << "Startup RPC: " << jvCommand;
RPCHandler rhHandler (&getApp().getOPs ());
// VFALCO TODO Clean up this magic number
LoadType loadType = LT_RPCReference;
Json::Value jvResult = rhHandler.doCommand (jvCommand, RPCHandler::ADMIN, &loadType);
if (!theConfig.QUIET)
Log::out() << "Result: " << jvResult;
}
}
getApp().run (); // Blocks till we get a stop RPC.
}
void setupConfigForUnitTests (Config* config)
{
config->nodeDatabase = parseDelimitedKeyValueString ("type=memory");
config->ephemeralNodeDatabase = StringPairArray ();
config->importNodeDatabase = StringPairArray ();
}
bool init_unit_test ()
{
setupConfigForUnitTests (&theConfig);
getApp ();
return true;
}
void printHelp (const po::options_description& desc)
{
using namespace std;
cerr << SYSTEM_NAME "d [options] <command> <params>" << endl;
cerr << desc << endl;
cerr << "Commands: " << endl;
cerr << " account_info <account>|<nickname>|<seed>|<pass_phrase>|<key> [<ledger>] [strict]" << endl;
cerr << " account_lines <account> <account>|\"\" [<ledger>]" << endl;
cerr << " account_offers <account>|<nickname>|<account_public_key> [<ledger>]" << endl;
cerr << " account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary] [count] [descending]" << endl;
cerr << " book_offers <taker_pays> <taker_gets> [<taker [<ledger> [<limit> [<proof> [<marker>]]]]]" << endl;
cerr << " connect <ip> [<port>]" << endl;
cerr << " consensus_info" << endl;
#if ENABLE_INSECURE
cerr << " data_delete <key>" << endl;
cerr << " data_fetch <key>" << endl;
cerr << " data_store <key> <value>" << endl;
#endif
cerr << " get_counts" << endl;
cerr << " json <method> <json>" << endl;
cerr << " ledger [<id>|current|closed|validated] [full]" << endl;
cerr << " ledger_accept" << endl;
cerr << " ledger_closed" << endl;
cerr << " ledger_current" << endl;
cerr << " ledger_header <ledger>" << endl;
cerr << " logrotate " << endl;
cerr << " peers" << endl;
cerr << " proof_create [<difficulty>] [<secret>]" << endl;
cerr << " proof_solve <token>" << endl;
cerr << " proof_verify <token> <solution> [<difficulty>] [<secret>]" << endl;
cerr << " random" << endl;
cerr << " ripple ..." << endl;
cerr << " ripple_path_find <json> [<ledger>]" << endl;
// cerr << " send <seed> <paying_account> <account_id> <amount> [<currency>] [<send_max>] [<send_currency>]" << endl;
cerr << " stop" << endl;
cerr << " tx <id>" << endl;
cerr << " unl_add <domain>|<public> [<comment>]" << endl;
cerr << " unl_delete <domain>|<public_key>" << endl;
cerr << " unl_list" << endl;
cerr << " unl_load" << endl;
cerr << " unl_network" << endl;
cerr << " unl_reset" << endl;
cerr << " validation_create [<seed>|<pass_phrase>|<key>]" << endl;
cerr << " validation_seed [<seed>|<pass_phrase>|<key>]" << endl;
cerr << " wallet_add <regular_seed> <paying_account> <master_seed> [<initial_funds>] [<account_annotation>]" << endl;
cerr << " wallet_accounts <seed>" << endl;
cerr << " wallet_claim <master_seed> <regular_seed> [<source_tag>] [<account_annotation>]" << endl;
cerr << " wallet_seed [<seed>|<passphrase>|<passkey>]" << endl;
cerr << " wallet_propose [<passphrase>]" << endl;
// Transaction helpers (that were removed):
// cerr << " account_domain_set <seed> <paying_account> [<domain>]" << endl;
// cerr << " account_email_set <seed> <paying_account> [<email_address>]" << endl;
// cerr << " account_rate_set <seed> <paying_account> <rate>" << endl;
// cerr << " account_wallet_set <seed> <paying_account> [<wallet_hash>]" << endl;
// cerr << " nickname_info <nickname>" << endl;
// cerr << " nickname_set <seed> <paying_account> <nickname> [<offer_minimum>] [<authorization>]" << endl;
// cerr << " offer_create <seed> <paying_account> <taker_pays_amount> <taker_pays_currency> <taker_pays_issuer> <takers_gets_amount> <takers_gets_currency> <takers_gets_issuer> <expires> [passive]" << endl;
// cerr << " offer_cancel <seed> <paying_account> <sequence>" << endl;
// cerr << " password_fund <seed> <paying_account> [<account>]" << endl;
// cerr << " password_set <master_seed> <regular_seed> [<account>]" << endl;
// cerr << " trust_set <seed> <paying_account> <destination_account> <limit_amount> <currency> [<quality_in>] [<quality_out>]" << endl;
}
//------------------------------------------------------------------------------
// OUr custom unit test runner
class RippleUnitTests : public UnitTests
{
public:
void logMessage (String const& message)
{
Log::out () << message.toStdString ();
}
};
//------------------------------------------------------------------------------
/** Run the Beast unit tests.
*/
static void runBeastUnitTests (std::string const& individualTest = "")
{
RippleUnitTests tr;
tr.setAssertOnFailure (false);
tr.setPassesAreLogged (false);
if (individualTest.empty ())
{
tr.runAllTests ();
}
else
{
tr.runTest (individualTest.c_str ());
}
}
//------------------------------------------------------------------------------
/** Run the Boost unit tests.
@note These are deprecated. We want to migrate to using only
the Beast unit testing framework. Please do not add more
Boost based unit tests.
*/
// VFALCO NOTE What are argc and argv for?
// Where does the boost unit test framework write its output?
//
static void runBoostUnitTests (int argc, char* argv [])
{
// DEPRECATED
boost::unit_test::unit_test_main (init_unit_test, argc, argv);
}
//------------------------------------------------------------------------------
int rippleMain (int argc, char** argv)
{
//
// These debug heap calls do nothing in release or non Visual Studio builds.
//
// Checks the heap at every allocation and deallocation (slow).
//
//Debug::setAlwaysCheckHeap (false);
// Keeps freed memory blocks and fills them with a guard value.
//
//Debug::setHeapDelayedFree (false);
// At exit, reports all memory blocks which have not been freed.
//
#if 1
Debug::setHeapReportLeaks (false);
#else
// This is some temporary leak checking test code
//
Debug::setHeapReportLeaks (false);
//malloc (512); // Any leaks before this line in the output are from static initializations.
ThreadWithCallQueue t ("test");
GlobalPagedFreeStore::getInstance ();
t.start ();
return 0;
#endif
using namespace std;
setCallingThreadName ("main");
int iResult = 0;
po::variables_map vm; // Map of options.
String importDescription;
{
importDescription <<
"Import an existing node database (specified in the " <<
"[" << ConfigSection::importNodeDatabase () << "] configuration file section) "
"into the current node database (specified in the " <<
"[" << ConfigSection::nodeDatabase () << "] configuration file section). ";
}
// VFALCO TODO Replace boost program options with something from Beast.
//
// Set up option parsing.
//
po::options_description desc ("General Options");
desc.add_options ()
("help,h", "Display this message.")
("conf", po::value<std::string> (), "Specify the configuration file.")
("rpc", "Perform rpc command (default).")
("rpc_ip", po::value <std::string> (), "Specify the IP address for RPC command. Format: <ip-address>[':'<port-number>]")
("rpc_port", po::value <int> (), "Specify the port number for RPC command.")
("standalone,a", "Run with no peers.")
("testnet,t", "Run in test net mode.")
("unittest,u", "Perform unit tests.")
("unittest2", po::value <std::string> ()->implicit_value (""), "Perform new unit tests.")
("parameters", po::value< vector<string> > (), "Specify comma separated parameters.")
("quiet,q", "Reduce diagnotics.")
("verbose,v", "Verbose logging.")
("load", "Load the current ledger from the local DB.")
("replay","Replay a ledger close.")
("ledger", po::value<std::string> (), "Load the specified ledger and start from .")
("start", "Start from a fresh Ledger.")
("net", "Get the initial ledger from the network.")
("fg", "Run in the foreground.")
("import", importDescription.toStdString ().c_str ())
;
// Interpret positional arguments as --parameters.
po::positional_options_description p;
p.add ("parameters", -1);
// These must be added before the Application object is created
NodeStore::addBackendFactory (KeyvaDBBackendFactory::getInstance ());
NodeStore::addBackendFactory (LevelDBBackendFactory::getInstance ());
NodeStore::addBackendFactory (MemoryBackendFactory::getInstance ());
NodeStore::addBackendFactory (NullBackendFactory::getInstance ());
NodeStore::addBackendFactory (SqliteBackendFactory::getInstance ());
#if RIPPLE_HYPERLEVELDB_AVAILABLE
NodeStore::addBackendFactory (HyperLevelDBBackendFactory::getInstance ());
#endif
#if RIPPLE_MDB_AVAILABLE
NodeStore::addBackendFactory (MdbBackendFactory::getInstance ());
#endif
if (! RandomNumbers::getInstance ().initialize ())
{
Log::out() << "Unable to add system entropy";
iResult = 2;
}
if (iResult)
{
nothing ();
}
else
{
// Parse options, if no error.
try
{
po::store (po::command_line_parser (argc, argv)
.options (desc) // Parse options.
.positional (p) // Remainder as --parameters.
.run (),
vm);
po::notify (vm); // Invoke option notify functions.
}
catch (...)
{
iResult = 1;
}
}
if (iResult)
{
nothing ();
}
else if (vm.count ("help"))
{
iResult = 1;
}
if (HaveSustain () &&
!iResult && !vm.count ("parameters") && !vm.count ("fg") && !vm.count ("standalone") && !vm.count ("unittest"))
{
std::string logMe = DoSustain (theConfig.DEBUG_LOGFILE.string());
if (!logMe.empty ())
Log (lsWARNING) << logMe;
}
if (vm.count ("quiet"))
{
Log::setMinSeverity (lsFATAL, true);
}
else if (vm.count ("verbose"))
{
Log::setMinSeverity (lsTRACE, true);
}
else
{
Log::setMinSeverity (lsINFO, true);
}
// Run the unit tests if requested.
//
if (vm.count ("unittest"))
{
runBeastUnitTests ();
// DEPRECATED
runBoostUnitTests (argc, argv);
return 0;
}
if (vm.count ("unittest2"))
{
std::string const test = vm ["unittest2"].as <std::string> ();
runBeastUnitTests (test);
return 0;
}
if (!iResult)
{
theConfig.setup (
vm.count ("conf") ? vm["conf"].as<std::string> () : "", // Config file.
!!vm.count ("testnet"), // Testnet flag.
!!vm.count ("quiet")); // Quiet flag.
if (vm.count ("standalone"))
{
theConfig.RUN_STANDALONE = true;
theConfig.LEDGER_HISTORY = 0;
}
}
if (vm.count ("start")) theConfig.START_UP = Config::FRESH;
// Handle a one-time import option
//
if (vm.count ("import"))
{
String const optionString (vm ["import"].as <std::string> ());
theConfig.importNodeDatabase = parseDelimitedKeyValueString (optionString);
}
if (vm.count ("ledger"))
{
theConfig.START_LEDGER = vm["ledger"].as<std::string> ();
if (vm.count("replay"))
theConfig.START_UP = Config::REPLAY;
else
theConfig.START_UP = Config::LOAD;
}
else if (vm.count ("load"))
{
theConfig.START_UP = Config::LOAD;
}
else if (vm.count ("net"))
{
theConfig.START_UP = Config::NETWORK;
if (theConfig.VALIDATION_QUORUM < 2)
theConfig.VALIDATION_QUORUM = 2;
}
if (iResult == 0)
{
// These overrides must happen after the config file is loaded.
// Override the RPC destination IP address
//
if (vm.count ("rpc_ip"))
{
theConfig.setRpcIpAndOptionalPort (vm ["rpc_ip"].as <std::string> ());
}
// Override the RPC destination port number
//
if (vm.count ("rpc_port"))
{
// VFALCO TODO This should be a short.
theConfig.setRpcPort (vm ["rpc_port"].as <int> ());
}
}
if (iResult)
{
nothing ();
}
else if (!vm.count ("parameters"))
{
// No arguments. Run server.
setupServer ();
setCallingThreadName ("io");
startServer ();
}
else
{
// Have a RPC command.
setCallingThreadName ("rpc");
std::vector<std::string> vCmd = vm["parameters"].as<std::vector<std::string> > ();
iResult = commandLineRPC (vCmd);
}
if (1 == iResult && !vm.count ("quiet"))
printHelp (desc);
return iResult;
}
// vim:ts=4

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,444 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_NETWORKOPS_H
#define RIPPLE_NETWORKOPS_H
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
class Peer;
class LedgerConsensus;
class NetworkOPs
: public DeadlineTimer::Listener
, LeakChecked <NetworkOPs>
{
public:
enum Fault
{
// exceptions these functions can throw
IO_ERROR = 1,
NO_NETWORK = 2,
};
enum OperatingMode
{
// how we process transactions or account balance requests
omDISCONNECTED = 0, // not ready to process requests
omCONNECTED = 1, // convinced we are talking to the network
omSYNCING = 2, // fallen slightly behind
omTRACKING = 3, // convinced we agree with the network
omFULL = 4 // we have the ledger and can even validate
};
#if 0
// VFALCO TODO Make this happen
/** Subscription data interface.
*/
class Subscriber
{
public:
typedef boost::weak_ptr <Subscriber> WeakPtr;
/** Called every time new JSON data is available.
*/
virtual void onSubscriberReceiveJSON (Json::Value const& json) { }
};
typedef boost::unordered_map <uint64, Subscriber::WeakPtr> SubMapType;
#endif
typedef boost::unordered_map <uint64, InfoSub::wptr> SubMapType;
public:
// VFALCO TODO Make LedgerMaster a SharedObjectPtr or a reference.
//
explicit NetworkOPs (LedgerMaster* pLedgerMaster);
virtual ~NetworkOPs () { }
// network information
uint32 getNetworkTimeNC (); // Our best estimate of wall time in seconds from 1/1/2000
uint32 getCloseTimeNC (); // Our best estimate of current ledger close time
uint32 getValidationTimeNC (); // Use *only* to timestamp our own validation
void closeTimeOffset (int);
boost::posix_time::ptime getNetworkTimePT ();
uint32 getLedgerID (uint256 const& hash);
uint32 getCurrentLedgerID ();
OperatingMode getOperatingMode ()
{
return mMode;
}
std::string strOperatingMode ();
Ledger::ref getClosedLedger ()
{
return mLedgerMaster->getClosedLedger ();
}
Ledger::ref getValidatedLedger ()
{
return mLedgerMaster->getValidatedLedger ();
}
Ledger::ref getPublishedLedger ()
{
return mLedgerMaster->getPublishedLedger ();
}
Ledger::ref getCurrentLedger ()
{
return mLedgerMaster->getCurrentLedger ();
}
Ledger::ref getCurrentSnapshot ()
{
return mLedgerMaster->getCurrentSnapshot ();
}
Ledger::pointer getLedgerByHash (uint256 const& hash)
{
return mLedgerMaster->getLedgerByHash (hash);
}
Ledger::pointer getLedgerBySeq (const uint32 seq);
void missingNodeInLedger (const uint32 seq);
uint256 getClosedLedgerHash ()
{
return mLedgerMaster->getClosedLedger ()->getHash ();
}
// Do we have this inclusive range of ledgers in our database
bool haveLedgerRange (uint32 from, uint32 to);
bool haveLedger (uint32 seq);
uint32 getValidatedSeq ();
bool isValidated (uint32 seq);
bool isValidated (uint32 seq, uint256 const& hash);
bool isValidated (Ledger::ref l)
{
return isValidated (l->getLedgerSeq (), l->getHash ());
}
bool getValidatedRange (uint32& minVal, uint32& maxVal)
{
return mLedgerMaster->getValidatedRange (minVal, maxVal);
}
SerializedValidation::ref getLastValidation ()
{
return mLastValidation;
}
void setLastValidation (SerializedValidation::ref v)
{
mLastValidation = v;
}
SLE::pointer getSLE (Ledger::pointer lpLedger, uint256 const& uHash)
{
return lpLedger->getSLE (uHash);
}
SLE::pointer getSLEi (Ledger::pointer lpLedger, uint256 const& uHash)
{
return lpLedger->getSLEi (uHash);
}
//
// Transaction operations
//
typedef FUNCTION_TYPE<void (Transaction::pointer, TER)> stCallback; // must complete immediately
void submitTransaction (Job&, SerializedTransaction::pointer, stCallback callback = stCallback ());
Transaction::pointer submitTransactionSync (Transaction::ref tpTrans, bool bAdmin, bool bFailHard, bool bSubmit);
void runTransactionQueue ();
Transaction::pointer processTransaction (Transaction::pointer, bool bAdmin, bool bFailHard, stCallback);
Transaction::pointer processTransaction (Transaction::pointer transaction, bool bAdmin, bool bFailHard)
{
return processTransaction (transaction, bAdmin, bFailHard, stCallback ());
}
Transaction::pointer findTransactionByID (uint256 const& transactionID);
#if 0
int findTransactionsBySource (uint256 const& uLedger, std::list<Transaction::pointer>&, const RippleAddress& sourceAccount,
uint32 minSeq, uint32 maxSeq);
#endif
int findTransactionsByDestination (std::list<Transaction::pointer>&, const RippleAddress& destinationAccount,
uint32 startLedgerSeq, uint32 endLedgerSeq, int maxTransactions);
//
// Account functions
//
AccountState::pointer getAccountState (Ledger::ref lrLedger, const RippleAddress& accountID);
SLE::pointer getGenerator (Ledger::ref lrLedger, const uint160& uGeneratorID);
//
// Directory functions
//
STVector256 getDirNodeInfo (Ledger::ref lrLedger, uint256 const& uRootIndex,
uint64& uNodePrevious, uint64& uNodeNext);
#if 0
//
// Nickname functions
//
NicknameState::pointer getNicknameState (uint256 const& uLedger, const std::string& strNickname);
#endif
//
// Owner functions
//
Json::Value getOwnerInfo (Ledger::pointer lpLedger, const RippleAddress& naAccount);
//
// Book functions
//
void getBookPage (Ledger::pointer lpLedger,
const uint160& uTakerPaysCurrencyID,
const uint160& uTakerPaysIssuerID,
const uint160& uTakerGetsCurrencyID,
const uint160& uTakerGetsIssuerID,
const uint160& uTakerID,
const bool bProof,
const unsigned int iLimit,
const Json::Value& jvMarker,
Json::Value& jvResult);
// raw object operations
bool findRawLedger (uint256 const& ledgerHash, Blob& rawLedger);
bool findRawTransaction (uint256 const& transactionHash, Blob& rawTransaction);
bool findAccountNode (uint256 const& nodeHash, Blob& rawAccountNode);
bool findTransactionNode (uint256 const& nodeHash, Blob& rawTransactionNode);
// tree synchronization operations
bool getTransactionTreeNodes (uint32 ledgerSeq, uint256 const& myNodeID,
Blob const& myNode, std::list< Blob >& newNodes);
bool getAccountStateNodes (uint32 ledgerSeq, uint256 const& myNodeId,
Blob const& myNode, std::list< Blob >& newNodes);
// ledger proposal/close functions
void processTrustedProposal (LedgerProposal::pointer proposal, boost::shared_ptr<protocol::TMProposeSet> set,
RippleAddress nodePublic, uint256 checkLedger, bool sigGood);
SHAMapAddNode gotTXData (const boost::shared_ptr<Peer>& peer, uint256 const& hash,
const std::list<SHAMapNode>& nodeIDs, const std::list< Blob >& nodeData);
bool recvValidation (SerializedValidation::ref val, const std::string& source);
void takePosition (int seq, SHAMap::ref position);
SHAMap::pointer getTXMap (uint256 const& hash);
bool hasTXSet (const boost::shared_ptr<Peer>& peer, uint256 const& set, protocol::TxSetStatus status);
void mapComplete (uint256 const& hash, SHAMap::ref map);
bool stillNeedTXSet (uint256 const& hash);
void makeFetchPack (Job&, boost::weak_ptr<Peer> peer, boost::shared_ptr<protocol::TMGetObjectByHash> request,
Ledger::pointer wantLedger, Ledger::pointer haveLedger, uint32 uUptime);
bool shouldFetchPack (uint32 seq);
void gotFetchPack (bool progress, uint32 seq);
void addFetchPack (uint256 const& hash, boost::shared_ptr< Blob >& data);
bool getFetchPack (uint256 const& hash, Blob& data);
int getFetchSize ();
void sweepFetchPack ();
// network state machine
// VFALCO TODO Try to make all these private since they seem to be...private
//
void switchLastClosedLedger (Ledger::pointer newLedger, bool duringConsensus); // Used for the "jump" case
bool checkLastClosedLedger (const std::vector<Peer::pointer>&, uint256& networkClosed);
int beginConsensus (uint256 const& networkClosed, Ledger::pointer closingLedger);
void tryStartConsensus ();
void endConsensus (bool correctLCL);
void setStandAlone ()
{
setMode (omFULL);
}
void setStateTimer ();
void newLCL (int proposers, int convergeTime, uint256 const& ledgerHash);
void needNetworkLedger ()
{
mNeedNetworkLedger = true;
}
void clearNeedNetworkLedger ()
{
mNeedNetworkLedger = false;
}
bool isNeedNetworkLedger ()
{
return mNeedNetworkLedger;
}
bool isFull ()
{
return !mNeedNetworkLedger && (mMode == omFULL);
}
void setProposing (bool p, bool v)
{
mProposing = p;
mValidating = v;
}
bool isProposing ()
{
return mProposing;
}
bool isValidating ()
{
return mValidating;
}
bool isFeatureBlocked ()
{
return mFeatureBlocked;
}
void setFeatureBlocked ();
void consensusViewChange ();
int getPreviousProposers ()
{
return mLastCloseProposers;
}
int getPreviousConvergeTime ()
{
return mLastCloseConvergeTime;
}
uint32 getLastCloseTime ()
{
return mLastCloseTime;
}
void setLastCloseTime (uint32 t)
{
mLastCloseTime = t;
}
Json::Value getConsensusInfo ();
Json::Value getServerInfo (bool human, bool admin);
void clearLedgerFetch ();
Json::Value getLedgerFetchInfo ();
uint32 acceptLedger ();
boost::unordered_map < uint160,
std::list<LedgerProposal::pointer> > & peekStoredProposals ()
{
return mStoredProposals;
}
void storeProposal (LedgerProposal::ref proposal, const RippleAddress& peerPublic);
uint256 getConsensusLCL ();
void reportFeeChange ();
void doClusterReport ();
//Helper function to generate SQL query to get transactions
std::string transactionsSQL (std::string selection, const RippleAddress& account,
int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit,
bool binary, bool count, bool bAdmin);
// client information retrieval functions
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >
getAccountTxs (const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin);
typedef boost::tuple<std::string, std::string, uint32> txnMetaLedgerType;
std::vector<txnMetaLedgerType>
getAccountTxsB (const RippleAddress& account, int32 minLedger, int32 maxLedger, bool descending, uint32 offset, int limit, bool bAdmin);
std::vector<RippleAddress> getLedgerAffectedAccounts (uint32 ledgerSeq);
std::vector<SerializedTransaction> getLedgerTransactions (uint32 ledgerSeq);
uint32 countAccountTxs (const RippleAddress& account, int32 minLedger, int32 maxLedger);
//
// Monitoring: publisher side
//
void pubLedger (Ledger::ref lpAccepted);
void pubProposedTransaction (Ledger::ref lpCurrent, SerializedTransaction::ref stTxn, TER terResult);
//
// Monitoring: subscriber side
//
void subAccount (InfoSub::ref ispListener, const boost::unordered_set<RippleAddress>& vnaAccountIDs, uint32 uLedgerIndex, bool rt);
void unsubAccount (uint64 uListener, const boost::unordered_set<RippleAddress>& vnaAccountIDs, bool rt);
bool subLedger (InfoSub::ref ispListener, Json::Value& jvResult);
bool unsubLedger (uint64 uListener);
bool subServer (InfoSub::ref ispListener, Json::Value& jvResult);
bool unsubServer (uint64 uListener);
bool subBook (InfoSub::ref ispListener, const uint160& currencyPays, const uint160& currencyGets,
const uint160& issuerPays, const uint160& issuerGets);
bool unsubBook (uint64 uListener, const uint160& currencyPays, const uint160& currencyGets,
const uint160& issuerPays, const uint160& issuerGets);
bool subTransactions (InfoSub::ref ispListener);
bool unsubTransactions (uint64 uListener);
bool subRTTransactions (InfoSub::ref ispListener);
bool unsubRTTransactions (uint64 uListener);
InfoSub::pointer findRpcSub (const std::string& strUrl);
InfoSub::pointer addRpcSub (const std::string& strUrl, InfoSub::ref rspEntry);
private:
void processNetTimer ();
void onDeadlineTimer (DeadlineTimer& timer);
void setMode (OperatingMode);
Json::Value transJson (const SerializedTransaction& stTxn, TER terResult, bool bValidated, Ledger::ref lpCurrent);
bool haveConsensusObject ();
Json::Value pubBootstrapAccountInfo (Ledger::ref lpAccepted, const RippleAddress& naAccountID);
void pubValidatedTransaction (Ledger::ref alAccepted, const AcceptedLedgerTx& alTransaction);
void pubAccountTransaction (Ledger::ref lpCurrent, const AcceptedLedgerTx& alTransaction, bool isAccepted);
void pubServer ();
private:
typedef boost::unordered_map <uint160, SubMapType> SubInfoMapType;
typedef boost::unordered_map <uint160, SubMapType>::iterator SubInfoMapIterator;
typedef boost::unordered_map<std::string, InfoSub::pointer> subRpcMapType;
OperatingMode mMode;
bool mNeedNetworkLedger;
bool mProposing, mValidating;
bool mFeatureBlocked;
boost::posix_time::ptime mConnectTime;
DeadlineTimer m_netTimer;
DeadlineTimer m_clusterTimer;
boost::shared_ptr<LedgerConsensus> mConsensus;
boost::unordered_map < uint160,
std::list<LedgerProposal::pointer> > mStoredProposals;
LedgerMaster* mLedgerMaster;
InboundLedger::pointer mAcquiringLedger;
int mCloseTimeOffset;
// last ledger close
int mLastCloseProposers, mLastCloseConvergeTime;
uint256 mLastCloseHash;
uint32 mLastCloseTime;
uint32 mLastValidationTime;
SerializedValidation::pointer mLastValidation;
// Recent positions taken
std::map<uint256, std::pair<int, SHAMap::pointer> > mRecentPositions;
// XXX Split into more locks.
boost::recursive_mutex mMonitorLock;
SubInfoMapType mSubAccount;
SubInfoMapType mSubRTAccount;
subRpcMapType mRpcSubMap;
SubMapType mSubLedger; // accepted ledgers
SubMapType mSubServer; // when server changes connectivity state
SubMapType mSubTransactions; // all accepted transactions
SubMapType mSubRTTransactions; // all proposed and accepted transactions
TaggedCache< uint256, Blob , UptimeTimerAdapter > mFetchPack;
uint32 mLastFetchPack;
// VFALCO TODO Document the special value uint32(-1) for this member
// and replace uint32(-1) with a constant. It is initialized
// in the ctor-initializer list to this constant.
//
uint32 mFetchSeq;
uint32 mLastLoadBase;
uint32 mLastLoadFactor;
};
#endif

View File

@@ -0,0 +1,11 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountItem::AccountItem (SerializedLedgerEntry::ref ledger)
: mLedgerEntry (ledger)
{
}

View File

@@ -0,0 +1,74 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_ACCOUNTITEM_H
#define RIPPLE_ACCOUNTITEM_H
//
// Fetch ledger entries from an account's owner dir.
//
/** Base class representing account items.
Account items include:
- Offers
- Trust Lines
NOTE these are deprecated and will go away, to be replaced with
simple visitor patterns.
*/
class AccountItem
{
public:
typedef boost::shared_ptr <AccountItem> pointer;
typedef const pointer& ref;
public:
AccountItem ()
{ }
/** Construct from a flat ledger entry.
*/
explicit AccountItem (SerializedLedgerEntry::ref ledger);
virtual ~AccountItem ()
{
;
}
virtual AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry) = 0;
// VFALCO TODO Make this const and change derived classes
virtual LedgerEntryType getType () = 0;
// VFALCO TODO Document the int parameter
virtual Json::Value getJson (int) = 0;
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
const SerializedLedgerEntry& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Blob getRaw () const;
// VFALCO TODO Make this private and use the existing accessors
//
protected:
// VFALCO TODO Research making the object pointed to const
SerializedLedgerEntry::pointer mLedgerEntry;
};
#endif

View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountItems::AccountItems (uint160 const& accountID,
Ledger::ref ledger,
AccountItem::pointer ofType)
{
mOfType = ofType;
fillItems (accountID, ledger);
}
void AccountItems::fillItems (const uint160& accountID, Ledger::ref ledger)
{
uint256 const rootIndex = Ledger::getOwnerDirIndex (accountID);
uint256 currentIndex = rootIndex;
// VFALCO TODO Rewrite all infinite loops to have clear terminating
// conditions defined in one location.
//
while (1)
{
SLE::pointer ownerDir = ledger->getDirNode (currentIndex);
// VFALCO TODO Rewrite to not return from the middle of the function
if (!ownerDir)
return;
BOOST_FOREACH (uint256 const & uNode, ownerDir->getFieldV256 (sfIndexes).peekValue ())
{
// VFALCO TODO rename getSLEi() to something legible.
SLE::pointer sleCur = ledger->getSLEi (uNode);
AccountItem::pointer item = mOfType->makeItem (accountID, sleCur);
// VFALCO NOTE Under what conditions would makeItem() return nullptr?
if (item)
{
mItems.push_back (item);
}
}
uint64 uNodeNext = ownerDir->getFieldU64 (sfIndexNext);
// VFALCO TODO Rewrite to not return from the middle of the function
if (!uNodeNext)
return;
currentIndex = Ledger::getDirNodeIndex (rootIndex, uNodeNext);
}
}
Json::Value AccountItems::getJson (int v)
{
Json::Value ret (Json::arrayValue);
BOOST_FOREACH (AccountItem::ref ai, mItems)
{
ret.append (ai->getJson (v));
}
return ret;
}
// vim:ts=4

View File

@@ -0,0 +1,45 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_ACCOUNTITEMS_H
#define RIPPLE_ACCOUNTITEMS_H
/** A set of AccountItem objects.
*/
class AccountItems : LeakChecked <AccountItems>
{
public:
typedef boost::shared_ptr <AccountItems> pointer;
typedef std::vector <AccountItem::pointer> Container;
// VFALCO TODO Create a typedef uint160 AccountID and replace
AccountItems (uint160 const& accountID,
Ledger::ref ledger,
AccountItem::pointer ofType);
// VFALCO TODO rename to getContainer and make this change in every interface
// that exposes the caller to the type of container.
//
Container& getItems ()
{
return mItems;
}
// VFALCO TODO What is the int for?
Json::Value getJson (int);
private:
void fillItems (const uint160& accountID, Ledger::ref ledger);
private:
// VFALCO TODO This looks like its used as an exemplar, rename appropriately
AccountItem::pointer mOfType;
Container mItems;
};
#endif

View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountState::AccountState (RippleAddress const& naAccountID)
: mAccountID (naAccountID)
, mValid (false)
{
if (naAccountID.isValid ())
{
mValid = true;
mLedgerEntry = boost::make_shared <SerializedLedgerEntry> (
ltACCOUNT_ROOT, Ledger::getAccountRootIndex (naAccountID));
mLedgerEntry->setFieldAccount (sfAccount, naAccountID.getAccountID ());
}
}
AccountState::AccountState (SLE::ref ledgerEntry, const RippleAddress& naAccountID) :
mAccountID (naAccountID), mLedgerEntry (ledgerEntry), mValid (false)
{
if (!mLedgerEntry)
return;
if (mLedgerEntry->getType () != ltACCOUNT_ROOT)
return;
mValid = true;
}
// VFALCO TODO Make this a generic utility function of some container class
//
std::string AccountState::createGravatarUrl (uint128 uEmailHash)
{
Blob vucMD5 (uEmailHash.begin (), uEmailHash.end ());
std::string strMD5Lower = strHex (vucMD5);
boost::to_lower (strMD5Lower);
// VFALCO TODO Give a name and move this constant to a more visible location.
// Also shouldn't this be https?
return str (boost::format ("http://www.gravatar.com/avatar/%s") % strMD5Lower);
}
void AccountState::addJson (Json::Value& val)
{
val = mLedgerEntry->getJson (0);
if (mValid)
{
if (mLedgerEntry->isFieldPresent (sfEmailHash))
val["urlgravatar"] = createGravatarUrl (mLedgerEntry->getFieldH128 (sfEmailHash));
}
else
{
val["Invalid"] = true;
}
}
void AccountState::dump ()
{
Json::Value j (Json::objectValue);
addJson (j);
Log (lsINFO) << j;
}
// vim:ts=4

View File

@@ -0,0 +1,77 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_ACCOUNTSTATE_H
#define RIPPLE_ACCOUNTSTATE_H
//
// Provide abstract access to an account's state, such that access to the serialized format is hidden.
//
class AccountState : LeakChecked <AccountState>
{
public:
typedef boost::shared_ptr<AccountState> pointer;
public:
// For new accounts
explicit AccountState (RippleAddress const& naAccountID);
// For accounts in a ledger
AccountState (SLE::ref ledgerEntry, RippleAddress const& naAccountI);
bool haveAuthorizedKey ()
{
return mLedgerEntry->isFieldPresent (sfRegularKey);
}
RippleAddress getAuthorizedKey ()
{
return mLedgerEntry->getFieldAccount (sfRegularKey);
}
STAmount getBalance () const
{
return mLedgerEntry->getFieldAmount (sfBalance);
}
uint32 getSeq () const
{
return mLedgerEntry->getFieldU32 (sfSequence);
}
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
SerializedLedgerEntry const& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Blob getRaw () const;
void addJson (Json::Value& value);
void dump ();
static std::string createGravatarUrl (uint128 uEmailHash);
private:
RippleAddress const mAccountID;
RippleAddress mAuthorizedKey;
SerializedLedgerEntry::pointer mLedgerEntry;
bool mValid;
};
#endif

View File

@@ -0,0 +1,78 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
bool CanonicalTXSet::Key::operator< (Key const& rhs) const
{
if (mAccount < rhs.mAccount) return true;
if (mAccount > rhs.mAccount) return false;
if (mSeq < rhs.mSeq) return true;
if (mSeq > rhs.mSeq) return false;
return mTXid < rhs.mTXid;
}
bool CanonicalTXSet::Key::operator> (Key const& rhs) const
{
if (mAccount > rhs.mAccount) return true;
if (mAccount < rhs.mAccount) return false;
if (mSeq > rhs.mSeq) return true;
if (mSeq < rhs.mSeq) return false;
return mTXid > rhs.mTXid;
}
bool CanonicalTXSet::Key::operator<= (Key const& rhs) const
{
if (mAccount < rhs.mAccount) return true;
if (mAccount > rhs.mAccount) return false;
if (mSeq < rhs.mSeq) return true;
if (mSeq > rhs.mSeq) return false;
return mTXid <= rhs.mTXid;
}
bool CanonicalTXSet::Key::operator>= (Key const& rhs)const
{
if (mAccount > rhs.mAccount) return true;
if (mAccount < rhs.mAccount) return false;
if (mSeq > rhs.mSeq) return true;
if (mSeq < rhs.mSeq) return false;
return mTXid >= rhs.mTXid;
}
void CanonicalTXSet::push_back (SerializedTransaction::ref txn)
{
uint256 effectiveAccount = mSetHash;
effectiveAccount ^= txn->getSourceAccount ().getAccountID ().to256 ();
mMap.insert (std::make_pair (
Key (effectiveAccount, txn->getSequence (), txn->getTransactionID ()),
txn));
}
CanonicalTXSet::iterator CanonicalTXSet::erase (iterator const& it)
{
iterator tmp = it;
++tmp;
mMap.erase (it);
return tmp;
}

View File

@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_CANONICALTXSET_H
#define RIPPLE_CANONICALTXSET_H
/** Holds transactions which were deferred to the next pass of consensus.
"Canonical" refers to the order in which transactions are applied.
- Puts transactions from the same account in sequence order
*/
// VFALCO TODO rename to SortedTxSet
class CanonicalTXSet : LeakChecked <CanonicalTXSet>
{
public:
class Key
{
public:
Key (uint256 const& account, uint32 seq, uint256 const& id)
: mAccount (account)
, mTXid (id)
, mSeq (seq)
{
}
bool operator< (Key const& rhs) const;
bool operator> (Key const& rhs) const;
bool operator<= (Key const& rhs) const;
bool operator>= (Key const& rhs) const;
bool operator== (Key const& rhs) const
{
return mTXid == rhs.mTXid;
}
bool operator!= (Key const& rhs) const
{
return mTXid != rhs.mTXid;
}
uint256 const& getTXID () const
{
return mTXid;
}
private:
uint256 mAccount;
uint256 mTXid;
uint32 mSeq;
};
typedef std::map <Key, SerializedTransaction::pointer>::iterator iterator;
typedef std::map <Key, SerializedTransaction::pointer>::const_iterator const_iterator;
public:
explicit CanonicalTXSet (LedgerHash const& lastClosedLedgerHash)
: mSetHash (lastClosedLedgerHash)
{
}
void push_back (SerializedTransaction::ref txn);
// VFALCO TODO remove this function
void reset (LedgerHash const& newLastClosedLedgerHash)
{
mSetHash = newLastClosedLedgerHash;
mMap.clear ();
}
iterator erase (iterator const& it);
iterator begin ()
{
return mMap.begin ();
}
iterator end ()
{
return mMap.end ();
}
const_iterator begin () const
{
return mMap.begin ();
}
const_iterator end () const
{
return mMap.end ();
}
size_t size () const
{
return mMap.size ();
}
bool empty () const
{
return mMap.empty ();
}
private:
// Used to salt the accounts so people can't mine for low account numbers
uint256 mSetHash;
std::map <Key, SerializedTransaction::pointer> mMap;
};
#endif

View File

@@ -0,0 +1,541 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class Features;
SETUP_LOG (Features)
FeatureState* testFeature = NULL;
// VFALCO TODO Rename this to Features
class Features : public IFeatures
{
protected:
typedef boost::unordered_map<uint256, FeatureState> featureMap_t;
typedef std::pair<const uint256, FeatureState> featureIt_t;
typedef boost::unordered_set<uint256> featureList_t;
boost::mutex mMutex;
featureMap_t mFeatureMap;
int mMajorityTime; // Seconds a feature must hold a majority
int mMajorityFraction; // 256 = 100%
uint32 mFirstReport; // close time of first majority report
uint32 mLastReport; // close time of most recent majority report
FeatureState* getCreateFeature (uint256 const& feature, bool create);
bool shouldEnable (uint32 closeTime, const FeatureState& fs);
void setJson (Json::Value& v, const FeatureState&);
public:
Features (uint32 majorityTime, int majorityFraction)
: mMajorityTime (majorityTime), mMajorityFraction (majorityFraction), mFirstReport (0), mLastReport (0)
{
;
}
void addInitialFeatures ();
FeatureState* addKnownFeature (const char* featureID, const char* friendlyName, bool veto);
uint256 getFeature (const std::string& name);
bool vetoFeature (uint256 const& feature);
bool unVetoFeature (uint256 const& feature);
bool enableFeature (uint256 const& feature);
bool disableFeature (uint256 const& feature);
bool isFeatureEnabled (uint256 const& feature);
bool isFeatureSupported (uint256 const& feature);
void setEnabledFeatures (const std::vector<uint256>& features);
void setSupportedFeatures (const std::vector<uint256>& features);
featureList_t getVetoedFeatures ();
featureList_t getEnabledFeatures ();
featureList_t getFeaturesToEnable (uint32 closeTime); // gets features we would vote to enable
featureList_t getDesiredFeatures (); // features we support, do not veto, are not enabled
void reportValidations (const FeatureSet&);
Json::Value getJson (int);
Json::Value getJson (uint256 const& );
void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation);
void doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition);
};
void Features::addInitialFeatures ()
{
// For each feature this version supports, construct the FeatureState object by calling
// getCreateFeature. Set any vetoes or defaults. A pointer to the FeatureState can be stashed
testFeature = addKnownFeature ("1234", "testFeature", false);
}
FeatureState* Features::getCreateFeature (uint256 const& featureHash, bool create)
{
// call with the mutex held
featureMap_t::iterator it = mFeatureMap.find (featureHash);
if (it == mFeatureMap.end ())
{
if (!create)
return NULL;
FeatureState* feature = & (mFeatureMap[featureHash]);
{
std::string query = "SELECT FirstMajority,LastMajority FROM Features WHERE hash='";
query.append (featureHash.GetHex ());
query.append ("';");
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
if (db->executeSQL (query) && db->startIterRows ())
{
feature->mFirstMajority = db->getBigInt ("FirstMajority");
feature->mLastMajority = db->getBigInt ("LastMajority");
db->endIterRows ();
}
}
return feature;
}
return & (it->second);
}
uint256 Features::getFeature (const std::string& name)
{
if (!name.empty ())
{
BOOST_FOREACH (featureMap_t::value_type & it, mFeatureMap)
{
if (name == it.second.mFriendlyName)
return it.first;
}
}
return uint256 ();
}
FeatureState* Features::addKnownFeature (const char* featureID, const char* friendlyName, bool veto)
{
uint256 hash;
hash.SetHex (featureID);
if (hash.isZero ())
{
assert (false);
return NULL;
}
FeatureState* f = getCreateFeature (hash, true);
if (friendlyName != NULL)
f->setFriendlyName (friendlyName);
f->mVetoed = veto;
f->mSupported = true;
return f;
}
bool Features::vetoFeature (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, true);
if (s->mVetoed)
return false;
s->mVetoed = true;
return true;
}
bool Features::unVetoFeature (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, false);
if (!s || !s->mVetoed)
return false;
s->mVetoed = false;
return true;
}
bool Features::enableFeature (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, true);
if (s->mEnabled)
return false;
s->mEnabled = true;
return true;
}
bool Features::disableFeature (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, false);
if (!s || !s->mEnabled)
return false;
s->mEnabled = false;
return true;
}
bool Features::isFeatureEnabled (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, false);
return s && s->mEnabled;
}
bool Features::isFeatureSupported (uint256 const& feature)
{
boost::mutex::scoped_lock sl (mMutex);
FeatureState* s = getCreateFeature (feature, false);
return s && s->mSupported;
}
Features::featureList_t Features::getVetoedFeatures ()
{
featureList_t ret;
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
{
if (it.second.mVetoed)
ret.insert (it.first);
}
return ret;
}
Features::featureList_t Features::getEnabledFeatures ()
{
featureList_t ret;
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
{
if (it.second.mEnabled)
ret.insert (it.first);
}
return ret;
}
bool Features::shouldEnable (uint32 closeTime, const FeatureState& fs)
{
if (fs.mVetoed || fs.mEnabled || !fs.mSupported || (fs.mLastMajority != mLastReport))
return false;
if (fs.mFirstMajority == mFirstReport)
{
// had a majority when we first started the server, relaxed check
// WRITEME
}
// didn't have a majority when we first started the server, normal check
return (fs.mLastMajority - fs.mFirstMajority) > mMajorityTime;
}
Features::featureList_t Features::getFeaturesToEnable (uint32 closeTime)
{
featureList_t ret;
boost::mutex::scoped_lock sl (mMutex);
if (mLastReport != 0)
{
BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
{
if (shouldEnable (closeTime, it.second))
ret.insert (it.first);
}
}
return ret;
}
Features::featureList_t Features::getDesiredFeatures ()
{
featureList_t ret;
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
{
if (it.second.mSupported && !it.second.mEnabled && !it.second.mVetoed)
ret.insert (it.first);
}
return ret;
}
void Features::reportValidations (const FeatureSet& set)
{
if (set.mTrustedValidations == 0)
return;
int threshold = (set.mTrustedValidations * mMajorityFraction) / 256;
typedef std::map<uint256, int>::value_type u256_int_pair;
boost::mutex::scoped_lock sl (mMutex);
if (mFirstReport == 0)
mFirstReport = set.mCloseTime;
std::vector<uint256> changedFeatures;
changedFeatures.resize (set.mVotes.size ());
BOOST_FOREACH (const u256_int_pair & it, set.mVotes)
{
FeatureState& state = mFeatureMap[it.first];
WriteLog (lsDEBUG, Features) << "Feature " << it.first.GetHex () << " has " << it.second << " votes, needs " << threshold;
if (it.second >= threshold)
{
// we have a majority
state.mLastMajority = set.mCloseTime;
if (state.mFirstMajority == 0)
{
WriteLog (lsWARNING, Features) << "Feature " << it.first << " attains a majority vote";
state.mFirstMajority = set.mCloseTime;
changedFeatures.push_back (it.first);
}
}
else // we have no majority
{
if (state.mFirstMajority != 0)
{
WriteLog (lsWARNING, Features) << "Feature " << it.first << " loses majority vote";
state.mFirstMajority = 0;
state.mLastMajority = 0;
changedFeatures.push_back (it.first);
}
}
}
mLastReport = set.mCloseTime;
if (!changedFeatures.empty ())
{
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
db->executeSQL ("BEGIN TRANSACTION;");
BOOST_FOREACH (uint256 const & hash, changedFeatures)
{
FeatureState& fState = mFeatureMap[hash];
db->executeSQL (boost::str (boost::format (
"UPDATE Features SET FirstMajority = %d WHERE Hash = '%s';"
) % fState.mFirstMajority % hash.GetHex ()));
db->executeSQL (boost::str (boost::format (
"UPDATE Features SET LastMajority = %d WHERE Hash = '%s';"
) % fState.mLastMajority % hash.GetHex ()));
}
db->executeSQL ("END TRANSACTION;");
changedFeatures.clear ();
}
}
void Features::setEnabledFeatures (const std::vector<uint256>& features)
{
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (featureIt_t & it, mFeatureMap)
{
it.second.mEnabled = false;
}
BOOST_FOREACH (uint256 const & it, features)
{
mFeatureMap[it].mEnabled = true;
}
}
void Features::setSupportedFeatures (const std::vector<uint256>& features)
{
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (featureIt_t & it, mFeatureMap)
{
it.second.mSupported = false;
}
BOOST_FOREACH (uint256 const & it, features)
{
mFeatureMap[it].mSupported = true;
}
}
void Features::doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation)
{
featureList_t lFeatures = getDesiredFeatures ();
if (lFeatures.empty ())
return;
STVector256 vFeatures (sfFeatures);
BOOST_FOREACH (uint256 const & uFeature, lFeatures)
{
vFeatures.addValue (uFeature);
}
vFeatures.sort ();
baseValidation.setFieldV256 (sfFeatures, vFeatures);
}
void Features::doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition)
{
featureList_t lFeatures = getFeaturesToEnable (lastClosedLedger->getCloseTimeNC ());
if (lFeatures.empty ())
return;
BOOST_FOREACH (uint256 const & uFeature, lFeatures)
{
WriteLog (lsWARNING, Features) << "Voting for feature: " << uFeature;
SerializedTransaction trans (ttFEATURE);
trans.setFieldAccount (sfAccount, uint160 ());
trans.setFieldH256 (sfFeature, uFeature);
uint256 txID = trans.getTransactionID ();
WriteLog (lsWARNING, Features) << "Vote ID: " << txID;
Serializer s;
trans.add (s, true);
SHAMapItem::pointer tItem = boost::make_shared<SHAMapItem> (txID, s.peekData ());
if (!initialPosition->addGiveItem (tItem, true, false))
{
WriteLog (lsWARNING, Features) << "Ledger already had feature transaction";
}
}
}
Json::Value Features::getJson (int)
{
Json::Value ret (Json::objectValue);
{
boost::mutex::scoped_lock sl (mMutex);
BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
{
setJson (ret[it.first.GetHex ()] = Json::objectValue, it.second);
}
}
return ret;
}
void Features::setJson (Json::Value& v, const FeatureState& fs)
{
if (!fs.mFriendlyName.empty ())
v["name"] = fs.mFriendlyName;
v["supported"] = fs.mSupported;
v["vetoed"] = fs.mVetoed;
if (fs.mEnabled)
v["enabled"] = true;
else
{
v["enabled"] = false;
if (mLastReport != 0)
{
if (fs.mLastMajority == 0)
{
v["majority"] = false;
}
else
{
if (fs.mFirstMajority != 0)
{
if (fs.mFirstMajority == mFirstReport)
v["majority_start"] = "start";
else
v["majority_start"] = fs.mFirstMajority;
}
if (fs.mLastMajority != 0)
{
if (fs.mLastMajority == mLastReport)
v["majority_until"] = "now";
else
v["majority_until"] = fs.mLastMajority;
}
}
}
}
if (fs.mVetoed)
v["veto"] = true;
}
Json::Value Features::getJson (uint256 const& feature)
{
Json::Value ret = Json::objectValue;
boost::mutex::scoped_lock sl (mMutex);
setJson (ret[feature.GetHex ()] = Json::objectValue, *getCreateFeature (feature, true));
return ret;
}
template<typename INT> class VotableInteger
{
protected:
INT mCurrent; // The current setting
INT mTarget; // The setting we want
std::map<INT, int> mVoteMap;
public:
VotableInteger (INT current, INT target) : mCurrent (current), mTarget (target)
{
++mVoteMap[mTarget]; // Add our vote
}
bool mayVote ()
{
return mCurrent != mTarget; // If we love the current setting, we will not vote
}
void addVote (INT vote)
{
++mVoteMap[vote];
}
void noVote ()
{
addVote (mCurrent);
}
INT getVotes ()
{
INT ourVote = mCurrent;
int weight = 0;
typedef typename std::map<INT, int>::value_type mapVType;
BOOST_FOREACH (const mapVType & value, mVoteMap)
{
// Take most voted value between current and target, inclusive
// FIXME: Should take best value that can get a significant majority
if ((value.first <= std::max (mTarget, mCurrent)) &&
(value.first >= std::min (mTarget, mCurrent)) &&
(value.second > weight))
{
ourVote = value.first;
weight = value.second;
}
}
return ourVote;
}
};
IFeatures* IFeatures::New (uint32 majorityTime, int majorityFraction)
{
return new Features (majorityTime, majorityFraction);
}
// vim:ts=4

View File

@@ -0,0 +1,194 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class Features;
//------------------------------------------------------------------------------
class FeeVote : public IFeeVote
{
private:
// VFALCO TODO rename template parameter (wtf, looks like a macro)
template <typename INT>
class VotableInteger
{
public:
VotableInteger (INT current, INT target) : mCurrent (current), mTarget (target)
{
++mVoteMap[mTarget]; // Add our vote
}
bool mayVote ()
{
return mCurrent != mTarget; // If we love the current setting, we will not vote
}
void addVote (INT vote)
{
++mVoteMap[vote];
}
void noVote ()
{
addVote (mCurrent);
}
INT getVotes ()
{
INT ourVote = mCurrent;
int weight = 0;
typedef typename std::map<INT, int>::value_type mapVType;
BOOST_FOREACH (const mapVType & value, mVoteMap)
{
// Take most voted value between current and target, inclusive
if ((value.first <= std::max (mTarget, mCurrent)) &&
(value.first >= std::min (mTarget, mCurrent)) &&
(value.second > weight))
{
ourVote = value.first;
weight = value.second;
}
}
return ourVote;
}
private:
INT mCurrent; // The current setting
INT mTarget; // The setting we want
std::map<INT, int> mVoteMap;
};
public:
FeeVote (uint64 targetBaseFee, uint32 targetReserveBase, uint32 targetReserveIncrement)
: mTargetBaseFee (targetBaseFee)
, mTargetReserveBase (targetReserveBase)
, mTargetReserveIncrement (targetReserveIncrement)
{
}
//--------------------------------------------------------------------------
void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation)
{
if (lastClosedLedger->getBaseFee () != mTargetBaseFee)
{
WriteLog (lsINFO, Features) << "Voting for base fee of " << mTargetBaseFee;
baseValidation.setFieldU64 (sfBaseFee, mTargetBaseFee);
}
if (lastClosedLedger->getReserve (0) != mTargetReserveBase)
{
WriteLog (lsINFO, Features) << "Voting for base resrve of " << mTargetReserveBase;
baseValidation.setFieldU32 (sfReserveBase, mTargetReserveBase);
}
if (lastClosedLedger->getReserveInc () != mTargetReserveIncrement)
{
WriteLog (lsINFO, Features) << "Voting for reserve increment of " << mTargetReserveIncrement;
baseValidation.setFieldU32 (sfReserveIncrement, mTargetReserveIncrement);
}
}
//--------------------------------------------------------------------------
void doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition)
{
// LCL must be flag ledger
assert ((lastClosedLedger->getLedgerSeq () % 256) == 0);
VotableInteger<uint64> baseFeeVote (lastClosedLedger->getBaseFee (), mTargetBaseFee);
VotableInteger<uint32> baseReserveVote (lastClosedLedger->getReserve (0), mTargetReserveBase);
VotableInteger<uint32> incReserveVote (lastClosedLedger->getReserveInc (), mTargetReserveIncrement);
// get validations for ledger before flag
ValidationSet set = getApp().getValidations ().getValidations (lastClosedLedger->getParentHash ());
BOOST_FOREACH (ValidationSet::value_type const & value, set)
{
SerializedValidation const& val = *value.second;
if (val.isTrusted ())
{
if (val.isFieldPresent (sfBaseFee))
{
baseFeeVote.addVote (val.getFieldU64 (sfBaseFee));
}
else
{
baseFeeVote.noVote ();
}
if (val.isFieldPresent (sfReserveBase))
{
baseReserveVote.addVote (val.getFieldU32 (sfReserveBase));
}
else
{
baseReserveVote.noVote ();
}
if (val.isFieldPresent (sfReserveIncrement))
{
incReserveVote.addVote (val.getFieldU32 (sfReserveIncrement));
}
else
{
incReserveVote.noVote ();
}
}
}
// choose our positions
uint64 baseFee = baseFeeVote.getVotes ();
uint32 baseReserve = baseReserveVote.getVotes ();
uint32 incReserve = incReserveVote.getVotes ();
// add transactions to our position
if ((baseFee != lastClosedLedger->getBaseFee ()) ||
(baseReserve != lastClosedLedger->getReserve (0)) ||
(incReserve != lastClosedLedger->getReserveInc ()))
{
WriteLog (lsWARNING, Features) << "We are voting for a fee change: " << baseFee << "/" << baseReserve << "/" << incReserve;
SerializedTransaction trans (ttFEE);
trans.setFieldAccount (sfAccount, uint160 ());
trans.setFieldU64 (sfBaseFee, baseFee);
trans.setFieldU32 (sfReferenceFeeUnits, 10);
trans.setFieldU32 (sfReserveBase, baseReserve);
trans.setFieldU32 (sfReserveIncrement, incReserve);
uint256 txID = trans.getTransactionID ();
WriteLog (lsWARNING, Features) << "Vote: " << txID;
Serializer s;
trans.add (s, true);
SHAMapItem::pointer tItem = boost::make_shared<SHAMapItem> (txID, s.peekData ());
if (!initialPosition->addGiveItem (tItem, true, false))
{
WriteLog (lsWARNING, Features) << "Ledger already had fee change";
}
}
}
private:
uint64 mTargetBaseFee;
uint32 mTargetReserveBase;
uint32 mTargetReserveIncrement;
};
//------------------------------------------------------------------------------
IFeeVote* IFeeVote::New (uint64 targetBaseFee,
uint32 targetReserveBase,
uint32 targetReserveIncrement)
{
return new FeeVote (targetBaseFee, targetReserveBase, targetReserveIncrement);
}
// vim:ts=4

View File

@@ -0,0 +1,226 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// VFALCO TODO Inline the function definitions
class HashRouter : public IHashRouter
{
private:
/** An entry in the routing table.
*/
class Entry : public CountedObject <Entry>
{
public:
static char const* getCountedObjectName () { return "HashRouterEntry"; }
Entry ()
: mFlags (0)
{
}
std::set <uint64> const& peekPeers () const
{
return mPeers;
}
void addPeer (uint64 peer)
{
if (peer != 0)
mPeers.insert (peer);
}
bool hasPeer (uint64 peer) const
{
return mPeers.count (peer) > 0;
}
int getFlags (void) const
{
return mFlags;
}
bool hasFlag (int mask) const
{
return (mFlags & mask) != 0;
}
void setFlag (int flagsToSet)
{
mFlags |= flagsToSet;
}
void clearFlag (int flagsToClear)
{
mFlags &= ~flagsToClear;
}
void swapSet (std::set <uint64>& other)
{
mPeers.swap (other);
}
private:
int mFlags;
std::set <uint64> mPeers;
};
public:
explicit HashRouter (int holdTime)
: mHoldTime (holdTime)
{
}
bool addSuppression (uint256 const& index);
bool addSuppressionPeer (uint256 const& index, uint64 peer);
bool addSuppressionPeer (uint256 const& index, uint64 peer, int& flags);
bool addSuppressionFlags (uint256 const& index, int flag);
bool setFlag (uint256 const& index, int flag);
int getFlags (uint256 const& index);
bool swapSet (uint256 const& index, std::set<uint64>& peers, int flag);
private:
Entry getEntry (uint256 const& );
Entry& findCreateEntry (uint256 const& , bool& created);
boost::mutex mSuppressionMutex;
// Stores all suppressed hashes and their expiration time
boost::unordered_map <uint256, Entry> mSuppressionMap;
// Stores all expiration times and the hashes indexed for them
std::map< int, std::list<uint256> > mSuppressionTimes;
int mHoldTime;
};
//------------------------------------------------------------------------------
HashRouter::Entry& HashRouter::findCreateEntry (uint256 const& index, bool& created)
{
boost::unordered_map<uint256, Entry>::iterator fit = mSuppressionMap.find (index);
if (fit != mSuppressionMap.end ())
{
created = false;
return fit->second;
}
created = true;
int now = UptimeTimer::getInstance ().getElapsedSeconds ();
int expireTime = now - mHoldTime;
// See if any supressions need to be expired
std::map< int, std::list<uint256> >::iterator it = mSuppressionTimes.begin ();
if ((it != mSuppressionTimes.end ()) && (it->first <= expireTime))
{
BOOST_FOREACH (uint256 const & lit, it->second)
mSuppressionMap.erase (lit);
mSuppressionTimes.erase (it);
}
mSuppressionTimes[now].push_back (index);
return mSuppressionMap.emplace (index, Entry ()).first->second;
}
bool HashRouter::addSuppression (uint256 const& index)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
findCreateEntry (index, created);
return created;
}
HashRouter::Entry HashRouter::getEntry (uint256 const& index)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
return findCreateEntry (index, created);
}
bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
findCreateEntry (index, created).addPeer (peer);
return created;
}
bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer, int& flags)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
Entry& s = findCreateEntry (index, created);
s.addPeer (peer);
flags = s.getFlags ();
return created;
}
int HashRouter::getFlags (uint256 const& index)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
return findCreateEntry (index, created).getFlags ();
}
bool HashRouter::addSuppressionFlags (uint256 const& index, int flag)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
findCreateEntry (index, created).setFlag (flag);
return created;
}
bool HashRouter::setFlag (uint256 const& index, int flag)
{
// VFALCO NOTE Comments like this belong in the HEADER file,
// and more importantly in a Javadoc comment so
// they appear in the generated documentation.
//
// return: true = changed, false = unchanged
assert (flag != 0);
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
Entry& s = findCreateEntry (index, created);
if ((s.getFlags () & flag) == flag)
return false;
s.setFlag (flag);
return true;
}
bool HashRouter::swapSet (uint256 const& index, std::set<uint64>& peers, int flag)
{
boost::mutex::scoped_lock sl (mSuppressionMutex);
bool created;
Entry& s = findCreateEntry (index, created);
if ((s.getFlags () & flag) == flag)
return false;
s.swapSet (peers);
s.setFlag (flag);
return true;
}
IHashRouter* IHashRouter::New (int holdTime)
{
return new HashRouter (holdTime);
}

View File

@@ -0,0 +1,130 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IFEATURES_H
#define RIPPLE_IFEATURES_H
class FeatureSet
{
// the status of all features requested in a given window
public:
uint32 mCloseTime;
int mTrustedValidations; // number of trusted validations
boost::unordered_map<uint256, int> mVotes; // yes votes by feature
FeatureSet (uint32 ct, int tv) : mCloseTime (ct), mTrustedValidations (tv)
{
;
}
void addVote (uint256 const& feature)
{
++mVotes[feature];
}
};
class FeatureState
{
public:
bool mVetoed; // We don't want this feature enabled
bool mEnabled;
bool mSupported;
bool mDefault; // Include in genesis ledger
uint32 mFirstMajority; // First time we saw a majority (close time)
uint32 mLastMajority; // Most recent time we saw a majority (close time)
std::string mFriendlyName;
FeatureState ()
: mVetoed (false), mEnabled (false), mSupported (false), mDefault (false),
mFirstMajority (0), mLastMajority (0)
{
;
}
void setVeto ()
{
mVetoed = true;
}
void setDefault ()
{
mDefault = true;
}
bool isDefault ()
{
return mDefault;
}
bool isSupported ()
{
return mSupported;
}
bool isVetoed ()
{
return mVetoed;
}
bool isEnabled ()
{
return mEnabled;
}
const std::string& getFiendlyName ()
{
return mFriendlyName;
}
void setFriendlyName (const std::string& n)
{
mFriendlyName = n;
}
};
/** Feature table interface.
The feature table stores the list of enabled and potential features.
Individuals features are voted on by validators during the consensus
process.
*/
class IFeatures
{
public:
static IFeatures* New (uint32 majorityTime, int majorityFraction);
virtual ~IFeatures () { }
virtual void addInitialFeatures () = 0;
virtual FeatureState* addKnownFeature (const char* featureID, const char* friendlyName, bool veto) = 0;
virtual uint256 getFeature (const std::string& name) = 0;
virtual bool vetoFeature (uint256 const& feature) = 0;
virtual bool unVetoFeature (uint256 const& feature) = 0;
virtual bool enableFeature (uint256 const& feature) = 0;
virtual bool disableFeature (uint256 const& feature) = 0;
virtual bool isFeatureEnabled (uint256 const& feature) = 0;
virtual bool isFeatureSupported (uint256 const& feature) = 0;
virtual void setEnabledFeatures (const std::vector<uint256>& features) = 0;
virtual void setSupportedFeatures (const std::vector<uint256>& features) = 0;
// VFALCO NOTE these can't possibly be used since featureList_t was/is private.
/*
featureList_t getVetoedFeatures() = 0;
featureList_t getEnabledFeatures() = 0;
featureList_t getFeaturesToEnable(uint32 closeTime) = 0; // gets features we would vote to enable
featureList_t getDesiredFeatures() = 0; // features we support, do not veto, are not enabled
*/
virtual void reportValidations (const FeatureSet&) = 0;
virtual Json::Value getJson (int) = 0;
virtual Json::Value getJson (uint256 const& ) = 0;
virtual void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation) = 0;
virtual void doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition) = 0;
};
#endif

View File

@@ -0,0 +1,44 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IFEEVOTE_H
#define RIPPLE_IFEEVOTE_H
/** Manager to process fee votes.
*/
class IFeeVote
{
public:
/** Create a new fee vote manager.
@param targetBaseFee
@param targetReserveBase
@param targetReserveIncrement
*/
static IFeeVote* New (uint64 targetBaseFee,
uint32 targetReserveBase,
uint32 targetReserveIncrement);
virtual ~IFeeVote () { }
/** Add local fee preference to validation.
@param lastClosedLedger
@param baseValidation
*/
virtual void doValidation (Ledger::ref lastClosedLedger,
STObject& baseValidation) = 0;
/** Cast our local vote on the fee.
@param lastClosedLedger
@param initialPosition
*/
virtual void doVoting (Ledger::ref lastClosedLedger,
SHAMap::ref initialPosition) = 0;
};
#endif

View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_HASHROUTER_RIPPLEHEADER
#define RIPPLE_HASHROUTER_RIPPLEHEADER
// VFALCO NOTE Are these the flags?? Why aren't we using a packed struct?
// VFALCO TODO convert these macros to int constants
#define SF_RELAYED 0x01 // Has already been relayed to other nodes
// VFALCO NOTE How can both bad and good be set on a hash?
#define SF_BAD 0x02 // Signature/format is bad
#define SF_SIGGOOD 0x04 // Signature is good
#define SF_SAVED 0x08
#define SF_RETRY 0x10 // Transaction can be retried
#define SF_TRUSTED 0x20 // comes from trusted source
/** Routing table for objects identified by hash.
This table keeps track of which hashes have been received by which peers.
It is used to manage the routing and broadcasting of messages in the peer
to peer overlay.
*/
class IHashRouter
{
public:
// VFALCO NOTE this preferred alternative to default parameters makes
// behavior clear.
//
static inline int getDefaultHoldTime ()
{
return 300;
}
// VFALCO TODO rename the parameter to entryHoldTimeInSeconds
static IHashRouter* New (int holdTime);
virtual ~IHashRouter () { }
// VFALCO TODO Replace "Supression" terminology with something more semantically meaningful.
virtual bool addSuppression (uint256 const& index) = 0;
virtual bool addSuppressionPeer (uint256 const& index, uint64 peer) = 0;
virtual bool addSuppressionPeer (uint256 const& index, uint64 peer, int& flags) = 0;
virtual bool addSuppressionFlags (uint256 const& index, int flag) = 0;
/** Set the flags on a hash.
@return `true` if the flags were changed.
*/
// VFALCO TODO Rename to setFlags since it works with multiple flags.
virtual bool setFlag (uint256 const& index, int mask) = 0;
virtual int getFlags (uint256 const& index) = 0;
virtual bool swapSet (uint256 const& index, std::set<uint64>& peers, int flag) = 0;
// VFALCO TODO This appears to be unused!
//
// virtual Entry getEntry (uint256 const&) = 0;
};
#endif

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IPROOFOFWORKFACTORY_H
#define RIPPLE_IPROOFOFWORKFACTORY_H
enum POWResult
{
powOK = 0,
powREUSED = 1, // already submitted
powBADNONCE = 2, // you didn't solve it
powEXPIRED = 3, // time is up
powCORRUPT = 4,
powTOOEASY = 5, // the difficulty increased too much while you solved it
};
// VFALCO TODO move this to the class as a static member and rename it
bool powResultInfo (POWResult powCode, std::string& strToken, std::string& strHuman);
class IProofOfWorkFactory : LeakChecked <IProofOfWorkFactory>
{
public:
typedef boost::bimap< boost::bimaps::multiset_of<time_t>, boost::bimaps::unordered_set_of<uint256> > powMap_t;
typedef powMap_t::value_type powMap_vt;
public:
static IProofOfWorkFactory* New ();
virtual ~IProofOfWorkFactory () { }
// VFALCO TODO which members can be const?
virtual ProofOfWork getProof () = 0;
virtual POWResult checkProof (const std::string& token, uint256 const& solution) = 0;
virtual uint64 getDifficulty () = 0;
virtual void setDifficulty (int i) = 0;
virtual void loadHigh () = 0;
virtual void loadLow () = 0;
virtual void sweep () = 0;
virtual uint256 const& getSecret () const = 0;
virtual void setSecret (uint256 const& secret) = 0;
public:
static int getPowEntry (uint256 const& target, int iterations);
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IVALIDATIONS_H
#define RIPPLE_IVALIDATIONS_H
// VFALCO TODO rename and move these typedefs into the IValidations interface
typedef boost::unordered_map<uint160, SerializedValidation::pointer> ValidationSet;
typedef std::pair<int, uint160> currentValidationCount; // nodes validating and highest node ID validating
class IValidations : LeakChecked <IValidations>
{
public:
static IValidations* New ();
virtual ~IValidations () { }
virtual bool addValidation (SerializedValidation::ref, const std::string& source) = 0;
virtual ValidationSet getValidations (uint256 const& ledger) = 0;
virtual void getValidationCount (uint256 const& ledger, bool currentOnly, int& trusted, int& untrusted) = 0;
virtual void getValidationTypes (uint256 const& ledger, int& full, int& partial) = 0;
virtual int getTrustedValidationCount (uint256 const& ledger) = 0;
virtual int getFeeAverage(uint256 const& ledger, uint64 ref, uint64& fee) = 0;
virtual int getNodesAfter (uint256 const& ledger) = 0;
virtual int getLoadRatio (bool overLoaded) = 0;
// VFALCO TODO make a typedef for this ugly return value!
virtual boost::unordered_map<uint256, currentValidationCount> getCurrentValidations (
uint256 currentLedger, uint256 previousLedger) = 0;
virtual std::list <SerializedValidation::pointer> getCurrentTrustedValidations () = 0;
virtual void tune (int size, int age) = 0;
virtual void flush () = 0;
virtual void sweep () = 0;
};
#endif

View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// This is the primary interface into the "client" portion of the program.
// Code that wants to do normal operations on the network such as
// creating and monitoring accounts, creating transactions, and so on
// should use this interface. The RPC code will primarily be a light wrapper
// over this code.
// Eventually, it will check the node's operating mode (synched, unsynched,
// etectera) and defer to the correct means of processing. The current
// code assumes this node is synched (and will continue to do so until
// there's a functional network.
// VFALCO TODO Figure out how to clean up these globals
uint64 InfoSub::sSeq = 0;
boost::mutex InfoSub::sSeqLock;
InfoSub::InfoSub ()
{
boost::mutex::scoped_lock sl (sSeqLock);
mSeq = ++sSeq;
}
InfoSub::~InfoSub ()
{
NetworkOPs& ops = getApp().getOPs ();
ops.unsubTransactions (mSeq);
ops.unsubRTTransactions (mSeq);
ops.unsubLedger (mSeq);
ops.unsubServer (mSeq);
ops.unsubAccount (mSeq, mSubAccountInfo, true);
ops.unsubAccount (mSeq, mSubAccountInfo, false);
}
void InfoSub::send (const Json::Value& jvObj, const std::string& sObj, bool broadcast)
{
send (jvObj, broadcast);
}
uint64 InfoSub::getSeq ()
{
return mSeq;
}
void InfoSub::onSendEmpty ()
{
}
void InfoSub::insertSubAccountInfo (RippleAddress addr, uint32 uLedgerIndex)
{
boost::mutex::scoped_lock sl (mLockInfo);
mSubAccountInfo.insert (addr);
}
void InfoSub::clearPathRequest ()
{
mPathRequest.reset ();
}
void InfoSub::setPathRequest (const boost::shared_ptr<PathRequest>& req)
{
mPathRequest = req;
}
const boost::shared_ptr<PathRequest>& InfoSub::getPathRequest ()
{
return mPathRequest;
}

View File

@@ -0,0 +1,66 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_INFOSUB_H
#define RIPPLE_INFOSUB_H
// Operations that clients may wish to perform against the network
// Master operational handler, server sequencer, network tracker
class PathRequest;
class InfoSub
: public CountedObject <InfoSub>
{
public:
static char const* getCountedObjectName () { return "InfoSub"; }
typedef boost::shared_ptr<InfoSub> pointer;
// VFALCO TODO Standardize on the names of weak / strong pointer typedefs.
typedef boost::weak_ptr<InfoSub> wptr;
typedef const boost::shared_ptr<InfoSub>& ref;
public:
InfoSub ();
virtual ~InfoSub ();
virtual void send (const Json::Value & jvObj, bool broadcast) = 0;
// VFALCO NOTE Why is this virtual?
virtual void send (const Json::Value & jvObj, const std::string & sObj, bool broadcast);
uint64 getSeq ();
void onSendEmpty ();
void insertSubAccountInfo (RippleAddress addr, uint32 uLedgerIndex);
void clearPathRequest ();
void setPathRequest (const boost::shared_ptr<PathRequest>& req);
boost::shared_ptr <PathRequest> const& getPathRequest ();
protected:
// VFALCO TODO make accessor for this member
boost::mutex mLockInfo;
private:
// VFALCO TODO Move these globals to class instance
static uint64 sSeq;
static boost::mutex sSeqLock;
boost::unordered_set <RippleAddress> mSubAccountInfo;
boost::unordered_set <RippleAddress> mSubAccountTransaction;
boost::shared_ptr <PathRequest> mPathRequest;
uint64 mSeq;
};
#endif

View File

@@ -0,0 +1,33 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
NicknameState::NicknameState (SerializedLedgerEntry::pointer ledgerEntry) :
mLedgerEntry (ledgerEntry)
{
if (!mLedgerEntry || mLedgerEntry->getType () != ltNICKNAME) return;
}
bool NicknameState::haveMinimumOffer () const
{
return mLedgerEntry->isFieldPresent (sfMinimumOffer);
}
STAmount NicknameState::getMinimumOffer () const
{
return mLedgerEntry->isFieldPresent (sfMinimumOffer)
? mLedgerEntry->getFieldAmount (sfMinimumOffer)
: STAmount ();
}
RippleAddress NicknameState::getAccountID () const
{
return mLedgerEntry->getFieldAccount (sfAccount);
}
void NicknameState::addJson (Json::Value& val)
{
val = mLedgerEntry->getJson (0);
}

View File

@@ -0,0 +1,49 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_NICKNAMESTATE_H
#define RIPPLE_NICKNAMESTATE_H
//
// State of a nickname node.
// - Isolate ledger entry format.
//
class NicknameState
{
public:
typedef boost::shared_ptr <NicknameState> pointer;
public:
explicit NicknameState (SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger
bool haveMinimumOffer () const;
STAmount getMinimumOffer () const;
RippleAddress getAccountID () const;
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
const SerializedLedgerEntry& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Blob getRaw () const;
void addJson (Json::Value& value);
private:
SerializedLedgerEntry::pointer mLedgerEntry;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,33 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountItem::pointer Offer::makeItem (const uint160& , SerializedLedgerEntry::ref ledgerEntry)
{
if (!ledgerEntry || ledgerEntry->getType () != ltOFFER) return (AccountItem::pointer ());
Offer* offer = new Offer (ledgerEntry);
return (AccountItem::pointer (offer));
}
Offer::Offer (SerializedLedgerEntry::pointer ledgerEntry) : AccountItem (ledgerEntry),
mAccount (mLedgerEntry->getFieldAccount (sfAccount)),
mTakerGets (mLedgerEntry->getFieldAmount (sfTakerGets)),
mTakerPays (mLedgerEntry->getFieldAmount (sfTakerPays)),
mSeq (mLedgerEntry->getFieldU32 (sfSequence))
{
;
}
Json::Value Offer::getJson (int)
{
Json::Value ret (Json::objectValue);
ret["account"] = mAccount.humanAccountID ();
ret["taker_gets"] = getTakerGets ().getFullText ();
ret["taker_pays"] = getTakerPays ().getFullText ();
return ret;
}
// vim:ts=4

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_OFFER_H
#define RIPPLE_OFFER_H
class Offer : public AccountItem
{
public:
Offer () {}
virtual ~Offer () {}
AccountItem::pointer makeItem (const uint160&, SerializedLedgerEntry::ref ledgerEntry);
LedgerEntryType getType ()
{
return (ltOFFER);
}
const STAmount& getTakerPays ()
{
return (mTakerPays);
}
const STAmount& getTakerGets ()
{
return (mTakerGets);
}
const RippleAddress& getAccount ()
{
return (mAccount);
}
int getSeq ()
{
return (mSeq);
}
Json::Value getJson (int);
private:
// For accounts in a ledger
explicit Offer (SerializedLedgerEntry::pointer ledgerEntry);
private:
RippleAddress mAccount;
STAmount mTakerGets;
STAmount mTakerPays;
int mSeq;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,44 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
OrderBook::OrderBook (uint256 const& index,
uint160 const& currencyIn,
uint160 const& currencyOut,
uint160 const& issuerIn,
uint160 const& issuerOut)
: mBookBase (index)
, mCurrencyIn (currencyIn)
, mCurrencyOut (currencyOut)
, mIssuerIn (issuerIn)
, mIssuerOut (issuerOut)
{
}
uint256 const& OrderBook::getBookBase () const
{
return mBookBase;
}
uint160 const& OrderBook::getCurrencyIn () const
{
return mCurrencyIn;
}
uint160 const& OrderBook::getCurrencyOut () const
{
return mCurrencyOut;
}
uint160 const& OrderBook::getIssuerIn () const
{
return mIssuerIn;
}
uint160 const& OrderBook::getIssuerOut () const
{
return mIssuerOut;
}

View File

@@ -0,0 +1,57 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_ORDERBOOK_H
#define RIPPLE_ORDERBOOK_H
/** Describes a serialized ledger entry for an order book.
*/
class OrderBook : LeakChecked <OrderBook>
{
public:
typedef boost::shared_ptr <OrderBook> pointer;
typedef boost::shared_ptr <OrderBook> const& ref;
public:
/** Construct from a currency specification.
@param index ???
@param currencyIn The base currency.
@param currencyOut The destination currency.
@param issuerIn The base issuer.
@param issuerOut The destination issuer.
*/
// VFALCO NOTE what is the meaning of the index parameter?
// VFALCO TODO group the issuer and currency parameters together.
// VFALCO TODO give typedef names to uint256 / uint160 params
OrderBook (uint256 const& index,
uint160 const& currencyIn,
uint160 const& currencyOut,
uint160 const& issuerIn,
uint160 const& issuerOut);
uint256 const& getBookBase () const;
uint160 const& getCurrencyIn () const;
uint160 const& getCurrencyOut () const;
uint160 const& getIssuerIn () const;
uint160 const& getIssuerOut () const;
private:
uint256 const mBookBase;
uint160 const mCurrencyIn;
uint160 const mCurrencyOut;
uint160 const mIssuerIn;
uint160 const mIssuerOut;
};
#endif

View File

@@ -0,0 +1,181 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (ProofOfWork)
bool powResultInfo (POWResult powCode, std::string& strToken, std::string& strHuman)
{
static struct
{
POWResult powCode;
const char* cpToken;
const char* cpHuman;
} powResultInfoA[] =
{
{ powREUSED, "powREUSED", "Proof-of-work has already been used." },
{ powBADNONCE, "powBADNONCE", "The solution does not meet the required difficulty." },
{ powEXPIRED, "powEXPIRED", "Token is expired." },
{ powCORRUPT, "powCORRUPT", "Invalid token." },
{ powTOOEASY, "powTOOEASY", "Difficulty has increased since token was issued." },
{ powOK, "powOK", "Valid proof-of-work." },
};
int iIndex = NUMBER (powResultInfoA);
while (iIndex-- && powResultInfoA[iIndex].powCode != powCode)
;
if (iIndex >= 0)
{
strToken = powResultInfoA[iIndex].cpToken;
strHuman = powResultInfoA[iIndex].cpHuman;
}
return iIndex >= 0;
}
// VFALCO TODO Move these to a header because they are used by ripple_ProofOfWorkFactory.cpp
const uint256 ProofOfWork::sMinTarget ("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
const int ProofOfWork::sMaxIterations (1 << 23);
const int ProofOfWork::sMaxDifficulty (30);
ProofOfWork::ProofOfWork (const std::string& token,
int iterations,
uint256 const& challenge,
uint256 const& target)
: mToken (token)
, mChallenge (challenge)
, mTarget (target)
, mIterations (iterations)
{
}
ProofOfWork::ProofOfWork (const std::string& token)
{
std::vector<std::string> fields;
boost::split (fields, token, boost::algorithm::is_any_of ("-"));
if (fields.size () != 5)
throw std::runtime_error ("invalid token");
mToken = token;
mChallenge.SetHex (fields[0]);
mTarget.SetHex (fields[1]);
mIterations = lexical_cast_s<int> (fields[2]);
}
bool ProofOfWork::isValid () const
{
if ((mIterations <= sMaxIterations) && (mTarget >= sMinTarget))
return true;
WriteLog (lsWARNING, ProofOfWork) << "Invalid PoW: " << mIterations << ", " << mTarget;
return false;
}
uint64 ProofOfWork::getDifficulty (uint256 const& target, int iterations)
{
// calculate the approximate number of hashes required to solve this proof of work
if ((iterations > sMaxIterations) || (target < sMinTarget))
{
WriteLog (lsINFO, ProofOfWork) << "Iterations:" << iterations;
WriteLog (lsINFO, ProofOfWork) << "MaxIterat: " << sMaxIterations;
WriteLog (lsINFO, ProofOfWork) << "Target: " << target;
WriteLog (lsINFO, ProofOfWork) << "MinTarget: " << sMinTarget;
throw std::runtime_error ("invalid proof of work target/iteration");
}
// more iterations means more hashes per iteration but also a larger final hash
uint64 difficulty = iterations + (iterations / 8);
// Multiply the number of hashes needed by 256 for each leading zero byte in the difficulty
const unsigned char* ptr = target.begin ();
while (*ptr == 0)
{
difficulty *= 256;
++ptr;
}
difficulty = (difficulty * 256) / (*ptr + 1);
return difficulty;
}
static uint256 getSHA512Half (const std::vector<uint256>& vec)
{
return Serializer::getSHA512Half (vec.front ().begin (), vec.size () * (256 / 8));
}
uint256 ProofOfWork::solve (int maxIterations) const
{
if (!isValid ())
throw std::runtime_error ("invalid proof of work target/iteration");
uint256 nonce;
RandomNumbers::getInstance ().fill (&nonce);
std::vector<uint256> buf2;
buf2.resize (mIterations);
std::vector<uint256> buf1;
buf1.resize (3);
buf1[0] = mChallenge;
while (maxIterations > 0)
{
buf1[1] = nonce;
buf1[2].zero ();
for (int i = (mIterations - 1); i >= 0; --i)
{
buf1[2] = getSHA512Half (buf1);
buf2[i] = buf1[2];
}
if (getSHA512Half (buf2) <= mTarget)
return nonce;
++nonce;
--maxIterations;
}
return uint256 ();
}
bool ProofOfWork::checkSolution (uint256 const& solution) const
{
if (mIterations > sMaxIterations)
return false;
std::vector<uint256> buf1;
buf1.push_back (mChallenge);
buf1.push_back (solution);
buf1.push_back (uint256 ());
std::vector<uint256> buf2;
buf2.resize (mIterations);
for (int i = (mIterations - 1); i >= 0; --i)
{
buf1[2] = getSHA512Half (buf1);
buf2[i] = buf1[2];
}
return getSHA512Half (buf2) <= mTarget;
}
bool ProofOfWork::validateToken (const std::string& strToken)
{
static boost::regex reToken ("[[:xdigit:]]{64}-[[:xdigit:]]{64}-[[:digit:]]+-[[:digit:]]+-[[:xdigit:]]{64}");
boost::smatch smMatch;
return boost::regex_match (strToken, smMatch, reToken);
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PROOFOFWORK_H
#define RIPPLE_PROOFOFWORK_H
class ProofOfWork : LeakChecked <ProofOfWork>
{
public:
static const int sMaxDifficulty;
typedef boost::shared_ptr <ProofOfWork> pointer;
ProofOfWork (const std::string& token,
int iterations,
uint256 const& challenge,
uint256 const& target);
explicit ProofOfWork (const std::string& token);
bool isValid () const;
uint256 solve (int maxIterations = 2 * sMaxIterations) const;
bool checkSolution (uint256 const& solution) const;
const std::string& getToken () const
{
return mToken;
}
uint256 const& getChallenge () const
{
return mChallenge;
}
uint64 getDifficulty () const
{
return getDifficulty (mTarget, mIterations);
}
// approximate number of hashes needed to solve
static uint64 getDifficulty (uint256 const& target, int iterations);
static bool validateToken (const std::string& strToken);
private:
std::string mToken;
uint256 mChallenge;
uint256 mTarget;
int mIterations;
static const uint256 sMinTarget;
static const int sMaxIterations;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,302 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
ProofOfWorkFactory::ProofOfWorkFactory () : mValidTime (180)
{
setDifficulty (1);
RandomNumbers::getInstance ().fillBytes (mSecret.begin (), mSecret.size ());
}
ProofOfWork ProofOfWorkFactory::getProof ()
{
// challenge - target - iterations - time - validator
static boost::format f ("%s-%s-%d-%d");
int now = static_cast<int> (time (NULL) / 4);
uint256 challenge;
RandomNumbers::getInstance ().fillBytes (challenge.begin (), challenge.size ());
boost::mutex::scoped_lock sl (mLock);
std::string s = boost::str (boost::format (f) % challenge.GetHex () % mTarget.GetHex () % mIterations % now);
std::string c = mSecret.GetHex () + s;
s += "-" + Serializer::getSHA512Half (c).GetHex ();
return ProofOfWork (s, mIterations, challenge, mTarget);
}
POWResult ProofOfWorkFactory::checkProof (const std::string& token, uint256 const& solution)
{
// challenge - target - iterations - time - validator
std::vector<std::string> fields;
boost::split (fields, token, boost::algorithm::is_any_of ("-"));
if (fields.size () != 5)
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " is corrupt";
return powCORRUPT;
}
std::string v = mSecret.GetHex () + fields[0] + "-" + fields[1] + "-" + fields[2] + "-" + fields[3];
if (fields[4] != Serializer::getSHA512Half (v).GetHex ())
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad token";
return powCORRUPT;
}
uint256 challenge, target;
challenge.SetHex (fields[0]);
target.SetHex (fields[1]);
time_t t = lexical_cast_s<time_t> (fields[3]);
time_t now = time (NULL);
int iterations = lexical_cast_s<int> (fields[2]);
{
boost::mutex::scoped_lock sl (mLock);
if ((t * 4) > (now + mValidTime))
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has expired";
return powEXPIRED;
}
if (((iterations != mIterations) || (target != mTarget)) && getPowEntry (target, iterations) < (mPowEntry - 2))
{
// difficulty has increased more than two times since PoW requested
WriteLog (lsINFO, ProofOfWork) << "Difficulty has increased since PoW requested";
return powTOOEASY;
}
}
ProofOfWork pow (token, iterations, challenge, target);
if (!pow.checkSolution (solution))
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has a bad nonce";
return powBADNONCE;
}
{
boost::mutex::scoped_lock sl (mLock);
if (!mSolvedChallenges.insert (powMap_vt (now, challenge)).second)
{
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has been reused";
return powREUSED;
}
}
return powOK;
}
void ProofOfWorkFactory::sweep ()
{
time_t expire = time (NULL) - mValidTime;
boost::mutex::scoped_lock sl (mLock);
do
{
powMap_t::left_map::iterator it = mSolvedChallenges.left.begin ();
if (it == mSolvedChallenges.left.end ())
return;
if (it->first >= expire)
return;
mSolvedChallenges.left.erase (it);
}
while (1);
}
void ProofOfWorkFactory::loadHigh ()
{
time_t now = time (NULL);
boost::mutex::scoped_lock sl (mLock);
if (mLastDifficultyChange == now)
return;
if (mPowEntry == 30)
return;
++mPowEntry;
mLastDifficultyChange = now;
}
void ProofOfWorkFactory::loadLow ()
{
time_t now = time (NULL);
boost::mutex::scoped_lock sl (mLock);
if (mLastDifficultyChange == now)
return;
if (mPowEntry == 0)
return;
--mPowEntry;
mLastDifficultyChange = now;
}
struct PowEntry
{
const char* target;
int iterations;
};
PowEntry PowEntries[ProofOfWork::sMaxDifficulty + 1] =
{
// target iterations hashes memory
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // 1451874, 2 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 2177811, 3 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 3538944, 3 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 4355623, 6 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 4718592, 4 MB
{ "0CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 5807497, 8 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 7077888, 6 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 9437184, 8 MB
{ "07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 14155776, 12MB
{ "03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 28311552, 12MB
{ "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 92919965, 8 MB
{ "00CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 139379948, 12MB
{ "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 150994944, 8 MB
{ "007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 226492416, 12MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 49152 }, // 278759896, 1.5MB
{ "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 301989888, 8 MB
{ "003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 452984832, 12MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 98304 }, // 905969664, 3 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 1115039586, 6 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 1486719448 8 MB
{ "000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 2230079172 12MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 2415919104, 8 MB
{ "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 3623878656, 12MB
{ "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 7247757312, 12MB
{ "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 23787511177, 8 MB
{ "0000CFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 35681266766, 12MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072}, // 38654705664, 4 MB
{ "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 38654705664, 8 MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 196608}, // 57982058496, 6 MB
{ "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 393216}, // 57982058496, 12MB
{ "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144}, // 77309411328, 8 MB
};
int ProofOfWorkFactory::getPowEntry (uint256 const& target, int iterations)
{
for (int i = 0; i < 31; ++i)
if (PowEntries[i].iterations == iterations)
{
uint256 t;
t.SetHex (PowEntries[i].target);
if (t == target)
return i;
}
return -1;
}
void ProofOfWorkFactory::setDifficulty (int i)
{
assert ((i >= 0) && (i <= ProofOfWork::sMaxDifficulty));
time_t now = time (NULL);
boost::mutex::scoped_lock sl (mLock);
mPowEntry = i;
mIterations = PowEntries[i].iterations;
mTarget.SetHex (PowEntries[i].target);
mLastDifficultyChange = now;
}
IProofOfWorkFactory* IProofOfWorkFactory::New ()
{
return new ProofOfWorkFactory;
}
//------------------------------------------------------------------------------
class ProofOfWorkTests : public UnitTest
{
public:
ProofOfWorkTests () : UnitTest ("ProofOfWork", "ripple", UnitTest::runManual)
{
}
void runTest ()
{
using namespace ripple;
ProofOfWorkFactory gen;
ProofOfWork pow = gen.getProof ();
String s;
s << "solve difficulty " << String (pow.getDifficulty ());
beginTest ("solve");
uint256 solution = pow.solve (16777216);
expect (! solution.isZero (), "Should be solved");
expect (pow.checkSolution (solution), "Should be checked");
// Why is this emitted?
//WriteLog (lsDEBUG, ProofOfWork) << "A bad nonce error is expected";
POWResult r = gen.checkProof (pow.getToken (), uint256 ());
expect (r == powBADNONCE, "Should show bad nonce for empty solution");
expect (gen.checkProof (pow.getToken (), solution) == powOK, "Solution should check with issuer");
//WriteLog (lsDEBUG, ProofOfWork) << "A reused nonce error is expected";
expect (gen.checkProof (pow.getToken (), solution) == powREUSED, "Reuse solution should be detected");
#ifdef SOLVE_POWS
for (int i = 0; i < 12; ++i)
{
gen.setDifficulty (i);
ProofOfWork pow = gen.getProof ();
WriteLog (lsINFO, ProofOfWork) << "Level: " << i << ", Estimated difficulty: " << pow.getDifficulty ();
uint256 solution = pow.solve (131072);
if (solution.isZero ())
{
//WriteLog (lsINFO, ProofOfWork) << "Giving up";
}
else
{
//WriteLog (lsINFO, ProofOfWork) << "Solution found";
if (gen.checkProof (pow.getToken (), solution) != powOK)
{
//WriteLog (lsFATAL, ProofOfWork) << "Solution fails";
}
}
}
#endif
}
};
static ProofOfWorkTests proofOfWorkTests;

View File

@@ -0,0 +1,54 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PROOFOFWORKFACTORY_RIPPLEHEADER
#define RIPPLE_PROOFOFWORKFACTORY_RIPPLEHEADER
// PRIVATE HEADER
class ProofOfWorkFactory
: public IProofOfWorkFactory
, LeakChecked <ProofOfWorkFactory>
{
public:
ProofOfWorkFactory ();
ProofOfWork getProof ();
POWResult checkProof (const std::string& token, uint256 const& solution);
uint64 getDifficulty ()
{
return ProofOfWork::getDifficulty (mTarget, mIterations);
}
void setDifficulty (int i);
void loadHigh ();
void loadLow ();
void sweep (void);
uint256 const& getSecret () const
{
return mSecret;
}
void setSecret (uint256 const& secret)
{
mSecret = secret;
}
static int getPowEntry (uint256 const& target, int iterations);
private:
uint256 mSecret;
int mIterations;
uint256 mTarget;
time_t mLastDifficultyChange;
int mValidTime;
int mPowEntry;
powMap_t mSolvedChallenges;
boost::mutex mLock;
};
#endif

View File

@@ -0,0 +1,204 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
struct SerializedLedgerLog; // for Log
SETUP_LOGN (SerializedLedgerLog,"SerializedLedger")
SerializedLedgerEntry::SerializedLedgerEntry (SerializerIterator& sit, uint256 const& index)
: STObject (sfLedgerEntry), mIndex (index), mMutable (true)
{
set (sit);
uint16 type = getFieldU16 (sfLedgerEntryType);
LedgerFormats::Item const* const item =
LedgerFormats::getInstance()->findByType (static_cast <LedgerEntryType> (type));
if (item == nullptr)
throw std::runtime_error ("invalid ledger entry type");
mType = item->getType ();
if (!setType (item->elements))
throw std::runtime_error ("ledger entry not valid for type");
}
SerializedLedgerEntry::SerializedLedgerEntry (const Serializer& s, uint256 const& index)
: STObject (sfLedgerEntry), mIndex (index), mMutable (true)
{
SerializerIterator sit (const_cast<Serializer&> (s)); // we know 's' isn't going away
set (sit);
uint16 type = getFieldU16 (sfLedgerEntryType);
LedgerFormats::Item const* const item =
LedgerFormats::getInstance()->findByType (static_cast <LedgerEntryType> (type));
if (item == nullptr)
throw std::runtime_error ("invalid ledger entry type");
mType = item->getType ();
if (!setType (item->elements))
{
WriteLog (lsWARNING, SerializedLedgerLog) << "Ledger entry not valid for type " << mFormat->getName ();
WriteLog (lsWARNING, SerializedLedgerLog) << getJson (0);
throw std::runtime_error ("ledger entry not valid for type");
}
}
SerializedLedgerEntry::SerializedLedgerEntry (LedgerEntryType type, uint256 const& index) :
STObject (sfLedgerEntry), mIndex (index), mType (type), mMutable (true)
{
LedgerFormats::Item const* const item =
LedgerFormats::getInstance()->findByType (type);
if (item != nullptr)
{
set (item->elements);
setFieldU16 (sfLedgerEntryType, static_cast <uint16> (item->getType ()));
}
else
{
throw std::runtime_error ("invalid ledger entry type");
}
}
SerializedLedgerEntry::pointer SerializedLedgerEntry::getMutable () const
{
SerializedLedgerEntry::pointer ret = boost::make_shared<SerializedLedgerEntry> (boost::cref (*this));
ret->mMutable = true;
return ret;
}
std::string SerializedLedgerEntry::getFullText () const
{
std::string ret = "\"";
ret += mIndex.GetHex ();
ret += "\" = { ";
ret += mFormat->getName ();
ret += ", ";
ret += STObject::getFullText ();
ret += "}";
return ret;
}
std::string SerializedLedgerEntry::getText () const
{
return str (boost::format ("{ %s, %s }")
% mIndex.GetHex ()
% STObject::getText ());
}
Json::Value SerializedLedgerEntry::getJson (int options) const
{
Json::Value ret (STObject::getJson (options));
ret["index"] = mIndex.GetHex ();
return ret;
}
bool SerializedLedgerEntry::isThreadedType ()
{
return getFieldIndex (sfPreviousTxnID) != -1;
}
bool SerializedLedgerEntry::isThreaded ()
{
return isFieldPresent (sfPreviousTxnID);
}
uint256 SerializedLedgerEntry::getThreadedTransaction ()
{
return getFieldH256 (sfPreviousTxnID);
}
uint32 SerializedLedgerEntry::getThreadedLedger ()
{
return getFieldU32 (sfPreviousTxnLgrSeq);
}
bool SerializedLedgerEntry::thread (uint256 const& txID, uint32 ledgerSeq, uint256& prevTxID, uint32& prevLedgerID)
{
uint256 oldPrevTxID = getFieldH256 (sfPreviousTxnID);
WriteLog (lsTRACE, SerializedLedgerLog) << "Thread Tx:" << txID << " prev:" << oldPrevTxID;
if (oldPrevTxID == txID)
{
// this transaction is already threaded
assert (getFieldU32 (sfPreviousTxnLgrSeq) == ledgerSeq);
return false;
}
prevTxID = oldPrevTxID;
prevLedgerID = getFieldU32 (sfPreviousTxnLgrSeq);
setFieldH256 (sfPreviousTxnID, txID);
setFieldU32 (sfPreviousTxnLgrSeq, ledgerSeq);
return true;
}
bool SerializedLedgerEntry::hasOneOwner ()
{
return (mType != ltACCOUNT_ROOT) && (getFieldIndex (sfAccount) != -1);
}
bool SerializedLedgerEntry::hasTwoOwners ()
{
return mType == ltRIPPLE_STATE;
}
RippleAddress SerializedLedgerEntry::getOwner ()
{
return getFieldAccount (sfAccount);
}
RippleAddress SerializedLedgerEntry::getFirstOwner ()
{
return RippleAddress::createAccountID (getFieldAmount (sfLowLimit).getIssuer ());
}
RippleAddress SerializedLedgerEntry::getSecondOwner ()
{
return RippleAddress::createAccountID (getFieldAmount (sfHighLimit).getIssuer ());
}
std::vector<uint256> SerializedLedgerEntry::getOwners ()
{
std::vector<uint256> owners;
uint160 account;
for (int i = 0, fields = getCount (); i < fields; ++i)
{
SField::ref fc = getFieldSType (i);
if ((fc == sfAccount) || (fc == sfOwner))
{
const STAccount* entry = dynamic_cast<const STAccount*> (peekAtPIndex (i));
if ((entry != NULL) && entry->getValueH160 (account))
owners.push_back (Ledger::getAccountRootIndex (account));
}
if ((fc == sfLowLimit) || (fc == sfHighLimit))
{
const STAmount* entry = dynamic_cast<const STAmount*> (peekAtPIndex (i));
if ((entry != NULL))
{
uint160 issuer = entry->getIssuer ();
if (issuer.isNonZero ())
owners.push_back (Ledger::getAccountRootIndex (issuer));
}
}
}
return owners;
}
// vim:ts=4

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SERIALIZEDLEDGER_H
#define RIPPLE_SERIALIZEDLEDGER_H
// VFALCO NOTE
//
// This looks like a central class for Ripple. Almost everything that
// does anything of interest deals with SLE objects. Any documentation
// effort should start with a complete description of this object and
// all of its operations.
//
// It is derived from STObject so it inherits a lot of behavior from that.
//
// VFALCO TODO Rename the source file to match the class
//
// VFALCO TODO Can we rename this class to something shorter and more concise?
//
// Can we just call this LedgerEntry?
//
class SerializedLedgerEntry
: public STObject
, public CountedObject <SerializedLedgerEntry>
{
public:
static char const* getCountedObjectName () { return "SerializedLedgerEntry"; }
typedef boost::shared_ptr<SerializedLedgerEntry> pointer;
typedef const boost::shared_ptr<SerializedLedgerEntry>& ref;
public:
SerializedLedgerEntry (const Serializer & s, uint256 const & index);
SerializedLedgerEntry (SerializerIterator & sit, uint256 const & index);
SerializedLedgerEntry (LedgerEntryType type, uint256 const & index);
SerializedTypeID getSType () const
{
return STI_LEDGERENTRY;
}
std::string getFullText () const;
std::string getText () const;
Json::Value getJson (int options) const;
uint256 const& getIndex () const
{
return mIndex;
}
void setIndex (uint256 const & i)
{
mIndex = i;
}
void setImmutable ()
{
mMutable = false;
}
bool isMutable ()
{
return mMutable;
}
SerializedLedgerEntry::pointer getMutable () const;
LedgerEntryType getType () const
{
return mType;
}
uint16 getVersion () const
{
return getFieldU16 (sfLedgerEntryType);
}
LedgerFormats::Item const* getFormat ()
{
return mFormat;
}
bool isThreadedType (); // is this a ledger entry that can be threaded
bool isThreaded (); // is this ledger entry actually threaded
bool hasOneOwner (); // This node has one other node that owns it (like nickname)
bool hasTwoOwners (); // This node has two nodes that own it (like ripple balance)
RippleAddress getOwner ();
RippleAddress getFirstOwner ();
RippleAddress getSecondOwner ();
uint256 getThreadedTransaction ();
uint32 getThreadedLedger ();
bool thread (uint256 const & txID, uint32 ledgerSeq, uint256 & prevTxID, uint32 & prevLedgerID);
std::vector<uint256> getOwners (); // nodes notified if this node is deleted
private:
SerializedLedgerEntry* duplicate () const
{
return new SerializedLedgerEntry (*this);
}
private:
uint256 mIndex;
LedgerEntryType mType;
LedgerFormats::Item const* mFormat;
bool mMutable;
};
typedef SerializedLedgerEntry SLE;
#endif
// vim:ts=4

View File

@@ -0,0 +1,289 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (SerializedTransaction)
SerializedTransaction::SerializedTransaction (TxType type)
: STObject (sfTransaction)
, mType (type)
, mSigGood (false)
, mSigBad (false)
{
mFormat = TxFormats::getInstance()->findByType (type);
if (mFormat == nullptr)
{
WriteLog (lsWARNING, SerializedTransaction) << "Transaction type: " << type;
throw std::runtime_error ("invalid transaction type");
}
set (mFormat->elements);
setFieldU16 (sfTransactionType, mFormat->getType ());
}
SerializedTransaction::SerializedTransaction (STObject const& object)
: STObject (object)
, mSigGood (false)
, mSigBad (false)
{
mType = static_cast <TxType> (getFieldU16 (sfTransactionType));
mFormat = TxFormats::getInstance()->findByType (mType);
if (!mFormat)
{
WriteLog (lsWARNING, SerializedTransaction) << "Transaction type: " << mType;
throw std::runtime_error ("invalid transaction type");
}
if (!setType (mFormat->elements))
{
throw std::runtime_error ("transaction not valid");
}
}
SerializedTransaction::SerializedTransaction (SerializerIterator& sit) : STObject (sfTransaction),
mSigGood (false), mSigBad (false)
{
int length = sit.getBytesLeft ();
if ((length < Protocol::txMinSizeBytes) || (length > Protocol::txMaxSizeBytes))
{
Log (lsERROR) << "Transaction has invalid length: " << length;
throw std::runtime_error ("Transaction length invalid");
}
set (sit);
mType = static_cast<TxType> (getFieldU16 (sfTransactionType));
mFormat = TxFormats::getInstance()->findByType (mType);
if (!mFormat)
{
WriteLog (lsWARNING, SerializedTransaction) << "Transaction type: " << mType;
throw std::runtime_error ("invalid transaction type");
}
if (!setType (mFormat->elements))
{
WriteLog (lsWARNING, SerializedTransaction) << "Transaction not legal for format";
throw std::runtime_error ("transaction not valid");
}
}
std::string SerializedTransaction::getFullText () const
{
std::string ret = "\"";
ret += getTransactionID ().GetHex ();
ret += "\" = {";
ret += STObject::getFullText ();
ret += "}";
return ret;
}
std::string SerializedTransaction::getText () const
{
return STObject::getText ();
}
std::vector<RippleAddress> SerializedTransaction::getMentionedAccounts () const
{
std::vector<RippleAddress> accounts;
BOOST_FOREACH (const SerializedType & it, peekData ())
{
const STAccount* sa = dynamic_cast<const STAccount*> (&it);
if (sa != NULL)
{
bool found = false;
RippleAddress na = sa->getValueNCA ();
BOOST_FOREACH (const RippleAddress & it, accounts)
{
if (it == na)
{
found = true;
break;
}
}
if (!found)
accounts.push_back (na);
}
const STAmount* sam = dynamic_cast<const STAmount*> (&it);
if (sam)
{
uint160 issuer = sam->getIssuer ();
if (issuer.isNonZero ())
{
RippleAddress na;
na.setAccountID (issuer);
bool found = false;
BOOST_FOREACH (const RippleAddress & it, accounts)
{
if (it == na)
{
found = true;
break;
}
}
if (!found)
accounts.push_back (na);
}
}
}
return accounts;
}
uint256 SerializedTransaction::getSigningHash () const
{
return STObject::getSigningHash (theConfig.SIGN_TRANSACTION);
}
uint256 SerializedTransaction::getTransactionID () const
{
// perhaps we should cache this
return getHash (HashPrefix::transactionID);
}
Blob SerializedTransaction::getSignature () const
{
try
{
return getFieldVL (sfTxnSignature);
}
catch (...)
{
return Blob ();
}
}
void SerializedTransaction::sign (const RippleAddress& naAccountPrivate)
{
Blob signature;
naAccountPrivate.accountPrivateSign (getSigningHash (), signature);
setFieldVL (sfTxnSignature, signature);
}
bool SerializedTransaction::checkSign () const
{
if (mSigGood)
return true;
if (mSigBad)
return false;
try
{
RippleAddress n;
n.setAccountPublic (getFieldVL (sfSigningPubKey));
if (checkSign (n))
{
mSigGood = true;
return true;
}
}
catch (...)
{
;
}
mSigBad = true;
return false;
}
bool SerializedTransaction::checkSign (const RippleAddress& naAccountPublic) const
{
try
{
return naAccountPublic.accountPublicVerify (getSigningHash (), getFieldVL (sfTxnSignature));
}
catch (...)
{
return false;
}
}
void SerializedTransaction::setSigningPubKey (const RippleAddress& naSignPubKey)
{
setFieldVL (sfSigningPubKey, naSignPubKey.getAccountPublic ());
}
void SerializedTransaction::setSourceAccount (const RippleAddress& naSource)
{
setFieldAccount (sfAccount, naSource);
}
Json::Value SerializedTransaction::getJson (int options, bool binary) const
{
if (binary)
{
Json::Value ret;
Serializer s = STObject::getSerializer ();
ret["tx"] = strHex (s.peekData ());
ret["hash"] = getTransactionID ().GetHex ();
return ret;
}
Json::Value ret = STObject::getJson (0);
ret["hash"] = getTransactionID ().GetHex ();
return ret;
}
std::string SerializedTransaction::getSQLValueHeader ()
{
return "(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn)";
}
std::string SerializedTransaction::getMetaSQLValueHeader ()
{
return "(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn, TxnMeta)";
}
std::string SerializedTransaction::getMetaSQLInsertReplaceHeader ()
{
return "INSERT OR REPLACE INTO Transactions " + getMetaSQLValueHeader () + " VALUES ";
}
std::string SerializedTransaction::getSQL (uint32 inLedger, char status) const
{
Serializer s;
add (s);
return getSQL (s, inLedger, status);
}
std::string SerializedTransaction::getMetaSQL (uint32 inLedger, const std::string& escapedMetaData) const
{
Serializer s;
add (s);
return getMetaSQL (s, inLedger, TXN_SQL_VALIDATED, escapedMetaData);
}
std::string SerializedTransaction::getSQL (Serializer rawTxn, uint32 inLedger, char status) const
{
static boost::format bfTrans ("('%s', '%s', '%s', '%d', '%d', '%c', %s)");
std::string rTxn = sqlEscape (rawTxn.peekData ());
return str (boost::format (bfTrans)
% getTransactionID ().GetHex () % getTransactionType () % getSourceAccount ().humanAccountID ()
% getSequence () % inLedger % status % rTxn);
}
std::string SerializedTransaction::getMetaSQL (Serializer rawTxn, uint32 inLedger, char status,
const std::string& escapedMetaData) const
{
static boost::format bfTrans ("('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)");
std::string rTxn = sqlEscape (rawTxn.peekData ());
return str (boost::format (bfTrans)
% getTransactionID ().GetHex () % getTransactionType () % getSourceAccount ().humanAccountID ()
% getSequence () % inLedger % status % rTxn % escapedMetaData);
}

View File

@@ -0,0 +1,141 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SERIALIZEDTRANSACTION_H
#define RIPPLE_SERIALIZEDTRANSACTION_H
// VFALCO TODO eliminate these macros
#define TXN_SQL_NEW 'N'
#define TXN_SQL_CONFLICT 'C'
#define TXN_SQL_HELD 'H'
#define TXN_SQL_VALIDATED 'V'
#define TXN_SQL_INCLUDED 'I'
#define TXN_SQL_UNKNOWN 'U'
class SerializedTransaction
: public STObject
, public CountedObject <SerializedTransaction>
{
public:
static char const* getCountedObjectName () { return "SerializedTransaction"; }
typedef boost::shared_ptr<SerializedTransaction> pointer;
typedef const boost::shared_ptr<SerializedTransaction>& ref;
public:
SerializedTransaction (SerializerIterator & sit);
SerializedTransaction (TxType type);
SerializedTransaction (const STObject & object);
// STObject functions
SerializedTypeID getSType () const
{
return STI_TRANSACTION;
}
std::string getFullText () const;
std::string getText () const;
// outer transaction functions / signature functions
Blob getSignature () const;
void setSignature (Blob const & s)
{
setFieldVL (sfTxnSignature, s);
}
uint256 getSigningHash () const;
TxType getTxnType () const
{
return mType;
}
STAmount getTransactionFee () const
{
return getFieldAmount (sfFee);
}
void setTransactionFee (const STAmount & fee)
{
setFieldAmount (sfFee, fee);
}
RippleAddress getSourceAccount () const
{
return getFieldAccount (sfAccount);
}
Blob getSigningPubKey () const
{
return getFieldVL (sfSigningPubKey);
}
void setSigningPubKey (const RippleAddress & naSignPubKey);
void setSourceAccount (const RippleAddress & naSource);
std::string getTransactionType () const
{
return mFormat->getName ();
}
uint32 getSequence () const
{
return getFieldU32 (sfSequence);
}
void setSequence (uint32 seq)
{
return setFieldU32 (sfSequence, seq);
}
std::vector<RippleAddress> getMentionedAccounts () const;
uint256 getTransactionID () const;
virtual Json::Value getJson (int options, bool binary = false) const;
void sign (const RippleAddress & naAccountPrivate);
bool checkSign (const RippleAddress & naAccountPublic) const;
bool checkSign () const;
bool isKnownGood () const
{
return mSigGood;
}
bool isKnownBad () const
{
return mSigBad;
}
void setGood () const
{
mSigGood = true;
}
void setBad () const
{
mSigBad = true;
}
// SQL Functions
static std::string getSQLValueHeader ();
static std::string getSQLInsertHeader ();
static std::string getSQLInsertIgnoreHeader ();
std::string getSQL (std::string & sql, uint32 inLedger, char status) const;
std::string getSQL (uint32 inLedger, char status) const;
std::string getSQL (Serializer rawTxn, uint32 inLedger, char status) const;
// SQL Functions with metadata
static std::string getMetaSQLValueHeader ();
static std::string getMetaSQLInsertReplaceHeader ();
std::string getMetaSQL (uint32 inLedger, const std::string & escapedMetaData) const;
std::string getMetaSQL (Serializer rawTxn, uint32 inLedger, char status, const std::string & escapedMetaData) const;
private:
TxType mType;
TxFormats::Item const* mFormat;
SerializedTransaction* duplicate () const
{
return new SerializedTransaction (*this);
}
mutable bool mSigGood;
mutable bool mSigBad;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
BOOST_AUTO_TEST_SUITE (SerializedTransactionTS)
BOOST_AUTO_TEST_CASE ( STrans_test )
{
using namespace ripple;
RippleAddress seed;
seed.setSeedRandom ();
RippleAddress generator = RippleAddress::createGeneratorPublic (seed);
RippleAddress publicAcct = RippleAddress::createAccountPublic (generator, 1);
RippleAddress privateAcct = RippleAddress::createAccountPrivate (generator, seed, 1);
SerializedTransaction j (ttACCOUNT_SET);
j.setSourceAccount (publicAcct);
j.setSigningPubKey (publicAcct);
j.setFieldVL (sfMessageKey, publicAcct.getAccountPublic ());
j.sign (privateAcct);
if (!j.checkSign ()) BOOST_FAIL ("Transaction fails signature test");
Serializer rawTxn;
j.add (rawTxn);
SerializerIterator sit (rawTxn);
SerializedTransaction copy (sit);
if (copy != j)
{
Log (lsFATAL) << j.getJson (0);
Log (lsFATAL) << copy.getJson (0);
BOOST_FAIL ("Transaction fails serialize/deserialize test");
}
UPTR_T<STObject> new_obj = STObject::parseJson (j.getJson (0), sfGeneric);
if (new_obj.get () == NULL) BOOST_FAIL ("Unable to build object from json");
if (STObject (j) != *new_obj)
{
Log (lsINFO) << "ORIG: " << j.getJson (0);
Log (lsINFO) << "BUILT " << new_obj->getJson (0);
BOOST_FAIL ("Built a different transaction");
}
}
BOOST_AUTO_TEST_SUITE_END ();

View File

@@ -0,0 +1,444 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class Validations;
SETUP_LOG (Validations)
typedef std::map<uint160, SerializedValidation::pointer>::value_type u160_val_pair;
typedef boost::shared_ptr<ValidationSet> VSpointer;
class Validations : public IValidations
{
private:
boost::mutex mValidationLock;
TaggedCache<uint256, ValidationSet, UptimeTimerAdapter> mValidations;
boost::unordered_map<uint160, SerializedValidation::pointer> mCurrentValidations;
std::vector<SerializedValidation::pointer> mStaleValidations;
bool mWriting;
private:
boost::shared_ptr<ValidationSet> findCreateSet (uint256 const& ledgerHash)
{
VSpointer j = mValidations.fetch (ledgerHash);
if (!j)
{
j = boost::make_shared<ValidationSet> ();
mValidations.canonicalize (ledgerHash, j);
}
return j;
}
boost::shared_ptr<ValidationSet> findSet (uint256 const& ledgerHash)
{
return mValidations.fetch (ledgerHash);
}
public:
Validations () : mValidations ("Validations", 128, 600), mWriting (false)
{
mStaleValidations.reserve (512);
}
private:
bool addValidation (SerializedValidation::ref val, const std::string& source)
{
RippleAddress signer = val->getSignerPublic ();
bool isCurrent = false;
if (getApp().getUNL ().nodeInUNL (signer) || val->isTrusted ())
{
val->setTrusted ();
uint32 now = getApp().getOPs ().getCloseTimeNC ();
uint32 valClose = val->getSignTime ();
if ((now > (valClose - LEDGER_EARLY_INTERVAL)) && (now < (valClose + LEDGER_VAL_INTERVAL)))
isCurrent = true;
else
{
WriteLog (lsWARNING, Validations) << "Received stale validation now=" << now << ", close=" << valClose;
}
}
else
{
WriteLog (lsDEBUG, Validations) << "Node " << signer.humanNodePublic () << " not in UNL st=" << val->getSignTime () <<
", hash=" << val->getLedgerHash () << ", shash=" << val->getSigningHash () << " src=" << source;
}
uint256 hash = val->getLedgerHash ();
uint160 node = signer.getNodeID ();
{
boost::mutex::scoped_lock sl (mValidationLock);
if (!findCreateSet (hash)->insert (std::make_pair (node, val)).second)
return false;
if (isCurrent)
{
boost::unordered_map<uint160, SerializedValidation::pointer>::iterator it = mCurrentValidations.find (node);
if (it == mCurrentValidations.end ())
mCurrentValidations.emplace (node, val);
else if (!it->second)
it->second = val;
else if (val->getSignTime () > it->second->getSignTime ())
{
val->setPreviousHash (it->second->getLedgerHash ());
mStaleValidations.push_back (it->second);
it->second = val;
condWrite ();
}
else
isCurrent = false;
}
}
WriteLog (lsDEBUG, Validations) << "Val for " << hash << " from " << signer.humanNodePublic ()
<< " added " << (val->isTrusted () ? "trusted/" : "UNtrusted/") << (isCurrent ? "current" : "stale");
if (val->isTrusted ())
getApp().getLedgerMaster ().checkAccept (hash);
// FIXME: This never forwards untrusted validations
return isCurrent;
}
void tune (int size, int age)
{
mValidations.setTargetSize (size);
mValidations.setTargetAge (age);
}
ValidationSet getValidations (uint256 const& ledger)
{
{
boost::mutex::scoped_lock sl (mValidationLock);
VSpointer set = findSet (ledger);
if (set)
return *set;
}
return ValidationSet ();
}
void getValidationCount (uint256 const& ledger, bool currentOnly, int& trusted, int& untrusted)
{
trusted = untrusted = 0;
boost::mutex::scoped_lock sl (mValidationLock);
VSpointer set = findSet (ledger);
if (set)
{
uint32 now = getApp().getOPs ().getNetworkTimeNC ();
BOOST_FOREACH (u160_val_pair & it, *set)
{
bool isTrusted = it.second->isTrusted ();
if (isTrusted && currentOnly)
{
uint32 closeTime = it.second->getSignTime ();
if ((now < (closeTime - LEDGER_EARLY_INTERVAL)) || (now > (closeTime + LEDGER_VAL_INTERVAL)))
isTrusted = false;
else
{
WriteLog (lsTRACE, Validations) << "VC: Untrusted due to time " << ledger;
}
}
if (isTrusted)
++trusted;
else
++untrusted;
}
}
WriteLog (lsTRACE, Validations) << "VC: " << ledger << "t:" << trusted << " u:" << untrusted;
}
void getValidationTypes (uint256 const& ledger, int& full, int& partial)
{
full = partial = 0;
boost::mutex::scoped_lock sl (mValidationLock);
VSpointer set = findSet (ledger);
if (set)
{
BOOST_FOREACH (u160_val_pair & it, *set)
{
if (it.second->isTrusted ())
{
if (it.second->isFull ())
++full;
else
++partial;
}
}
}
WriteLog (lsTRACE, Validations) << "VC: " << ledger << "f:" << full << " p:" << partial;
}
int getTrustedValidationCount (uint256 const& ledger)
{
int trusted = 0;
boost::mutex::scoped_lock sl (mValidationLock);
VSpointer set = findSet (ledger);
if (set)
{
BOOST_FOREACH (u160_val_pair & it, *set)
{
if (it.second->isTrusted ())
++trusted;
}
}
return trusted;
}
int getFeeAverage (uint256 const& ledger, uint64 ref, uint64& fee)
{
int trusted = 0;
fee = 0;
boost::mutex::scoped_lock sl (mValidationLock);
VSpointer set = findSet (ledger);
if (set)
{
BOOST_FOREACH (u160_val_pair & it, *set)
{
if (it.second->isTrusted ())
{
++trusted;
if (it.second->isFieldPresent(sfLoadFee))
fee += it.second->getFieldU32(sfLoadFee);
else
fee += ref;
}
}
}
if (trusted == 0)
fee = ref;
else
fee /= trusted;
return trusted;
}
int getNodesAfter (uint256 const& ledger)
{
// Number of trusted nodes that have moved past this ledger
int count = 0;
boost::mutex::scoped_lock sl (mValidationLock);
BOOST_FOREACH (u160_val_pair & it, mCurrentValidations)
{
if (it.second->isTrusted () && it.second->isPreviousHash (ledger))
++count;
}
return count;
}
int getLoadRatio (bool overLoaded)
{
// how many trusted nodes are able to keep up, higher is better
int goodNodes = overLoaded ? 1 : 0;
int badNodes = overLoaded ? 0 : 1;
{
boost::mutex::scoped_lock sl (mValidationLock);
BOOST_FOREACH (u160_val_pair & it, mCurrentValidations)
{
if (it.second->isTrusted ())
{
if (it.second->isFull ())
++goodNodes;
else
++badNodes;
}
}
}
return (goodNodes * 100) / (goodNodes + badNodes);
}
std::list<SerializedValidation::pointer> getCurrentTrustedValidations ()
{
uint32 cutoff = getApp().getOPs ().getNetworkTimeNC () - LEDGER_VAL_INTERVAL;
std::list<SerializedValidation::pointer> ret;
boost::mutex::scoped_lock sl (mValidationLock);
boost::unordered_map<uint160, SerializedValidation::pointer>::iterator it = mCurrentValidations.begin ();
while (it != mCurrentValidations.end ())
{
if (!it->second) // contains no record
it = mCurrentValidations.erase (it);
else if (it->second->getSignTime () < cutoff)
{
// contains a stale record
mStaleValidations.push_back (it->second);
it->second.reset ();
condWrite ();
it = mCurrentValidations.erase (it);
}
else
{
// contains a live record
if (it->second->isTrusted ())
ret.push_back (it->second);
++it;
}
}
return ret;
}
boost::unordered_map<uint256, currentValidationCount>
getCurrentValidations (uint256 currentLedger, uint256 priorLedger)
{
uint32 cutoff = getApp().getOPs ().getNetworkTimeNC () - LEDGER_VAL_INTERVAL;
bool valCurrentLedger = currentLedger.isNonZero ();
bool valPriorLedger = priorLedger.isNonZero ();
boost::unordered_map<uint256, currentValidationCount> ret;
boost::mutex::scoped_lock sl (mValidationLock);
boost::unordered_map<uint160, SerializedValidation::pointer>::iterator it = mCurrentValidations.begin ();
while (it != mCurrentValidations.end ())
{
if (!it->second) // contains no record
it = mCurrentValidations.erase (it);
else if (it->second->getSignTime () < cutoff)
{
// contains a stale record
mStaleValidations.push_back (it->second);
it->second.reset ();
condWrite ();
it = mCurrentValidations.erase (it);
}
else
{
// contains a live record
bool countPreferred = valCurrentLedger && (it->second->getLedgerHash () == currentLedger);
if (!countPreferred && // allow up to one ledger slip in either direction
((valCurrentLedger && it->second->isPreviousHash (currentLedger)) ||
(valPriorLedger && (it->second->getLedgerHash () == priorLedger))))
{
countPreferred = true;
WriteLog (lsTRACE, Validations) << "Counting for " << currentLedger << " not " << it->second->getLedgerHash ();
}
currentValidationCount& p = countPreferred ? ret[currentLedger] : ret[it->second->getLedgerHash ()];
++ (p.first);
uint160 ni = it->second->getNodeID ();
if (ni > p.second)
p.second = ni;
++it;
}
}
return ret;
}
void flush ()
{
bool anyNew = false;
WriteLog (lsINFO, Validations) << "Flushing validations";
boost::mutex::scoped_lock sl (mValidationLock);
BOOST_FOREACH (u160_val_pair & it, mCurrentValidations)
{
if (it.second)
mStaleValidations.push_back (it.second);
anyNew = true;
}
mCurrentValidations.clear ();
if (anyNew)
condWrite ();
while (mWriting)
{
sl.unlock ();
boost::this_thread::sleep (boost::posix_time::milliseconds (100));
sl.lock ();
}
WriteLog (lsDEBUG, Validations) << "Validations flushed";
}
void condWrite ()
{
if (mWriting)
return;
mWriting = true;
getApp().getJobQueue ().addJob (jtWRITE, "Validations::doWrite",
BIND_TYPE (&Validations::doWrite, this, P_1));
}
void doWrite (Job&)
{
LoadEvent::autoptr event (getApp().getJobQueue ().getLoadEventAP (jtDISK, "ValidationWrite"));
boost::format insVal ("INSERT INTO Validations "
"(LedgerHash,NodePubKey,SignTime,RawData) VALUES ('%s','%s','%u',%s);");
boost::mutex::scoped_lock sl (mValidationLock);
assert (mWriting);
while (!mStaleValidations.empty ())
{
std::vector<SerializedValidation::pointer> vector;
vector.reserve (512);
mStaleValidations.swap (vector);
sl.unlock ();
{
Database* db = getApp().getLedgerDB ()->getDB ();
ScopedLock dbl (getApp().getLedgerDB ()->getDBLock ());
Serializer s (1024);
db->executeSQL ("BEGIN TRANSACTION;");
BOOST_FOREACH (SerializedValidation::ref it, vector)
{
s.erase ();
it->add (s);
db->executeSQL (boost::str (insVal % it->getLedgerHash ().GetHex ()
% it->getSignerPublic ().humanNodePublic () % it->getSignTime ()
% sqlEscape (s.peekData ())));
}
db->executeSQL ("END TRANSACTION;");
}
sl.lock ();
}
mWriting = false;
}
void sweep ()
{
boost::mutex::scoped_lock sl (mValidationLock);
mValidations.sweep ();
}
};
IValidations* IValidations::New ()
{
return new Validations;
}
// vim:ts=4

View File

@@ -0,0 +1,7 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOGN (WSConnectionLog,"WSConnection")

View File

@@ -0,0 +1,284 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_WSCONNECTION_H
#define RIPPLE_WSCONNECTION_H
// This is for logging
struct WSConnectionLog;
template <typename endpoint_type>
class WSServerHandler;
//
// Storage for connection specific info
// - Subscriptions
//
template <typename endpoint_type>
class WSConnection
: public InfoSub
, public boost::enable_shared_from_this< WSConnection<endpoint_type> >
, public CountedObject <WSConnection <endpoint_type> >
{
public:
static char const* getCountedObjectName () { return "WSConnection"; }
typedef typename endpoint_type::connection_type connection;
typedef typename boost::shared_ptr<connection> connection_ptr;
typedef typename boost::weak_ptr<connection> weak_connection_ptr;
typedef typename endpoint_type::handler::message_ptr message_ptr;
public:
// WSConnection()
// : mHandler((WSServerHandler<websocketpp::WSDOOR_SERVER>*)(NULL)),
// mConnection(connection_ptr()) { ; }
WSConnection (WSServerHandler<endpoint_type>* wshpHandler, const connection_ptr& cpConnection)
: mHandler (wshpHandler), mConnection (cpConnection), mNetwork (getApp().getOPs ()),
mRemoteIP (cpConnection->get_socket ().lowest_layer ().remote_endpoint ().address ().to_string ()),
mLoadSource (mRemoteIP), mPingTimer (cpConnection->get_io_service ()), mPinged (false),
mRcvQueueRunning (false), mDead (false)
{
WriteLog (lsDEBUG, WSConnectionLog) << "Websocket connection from " << mRemoteIP;
setPingTimer ();
}
void preDestroy ()
{
// sever connection
mPingTimer.cancel ();
mConnection.reset ();
boost::recursive_mutex::scoped_lock sl (mRcvQueueLock);
mDead = true;
}
virtual ~WSConnection ()
{
;
}
static void destroy (boost::shared_ptr< WSConnection<endpoint_type> >)
{
// Just discards the reference
}
// Implement overridden functions from base class:
void send (const Json::Value& jvObj, bool broadcast)
{
connection_ptr ptr = mConnection.lock ();
if (ptr)
mHandler->send (ptr, jvObj, broadcast);
}
void send (const Json::Value& jvObj, const std::string& sObj, bool broadcast)
{
connection_ptr ptr = mConnection.lock ();
if (ptr)
mHandler->send (ptr, sObj, broadcast);
}
// Utilities
Json::Value invokeCommand (Json::Value& jvRequest)
{
if (getApp().getLoadManager ().shouldCutoff (mLoadSource))
{
// VFALCO TODO This must be implemented before open sourcing
#if SHOULD_DISCONNECT
// FIXME: Must dispatch to strand
connection_ptr ptr = mConnection.lock ();
if (ptr)
ptr->close (websocketpp::close::status::PROTOCOL_ERROR, "overload");
return rpcError (rpcSLOW_DOWN);
#endif
}
// Requests without "command" are invalid.
//
if (!jvRequest.isMember ("command"))
{
Json::Value jvResult (Json::objectValue);
jvResult["type"] = "response";
jvResult["status"] = "error";
jvResult["error"] = "missingCommand";
jvResult["request"] = jvRequest;
if (jvRequest.isMember ("id"))
{
jvResult["id"] = jvRequest["id"];
}
getApp().getLoadManager ().applyLoadCharge (mLoadSource, LT_RPCInvalid);
return jvResult;
}
LoadType loadType = LT_RPCReference;
RPCHandler mRPCHandler (&mNetwork, boost::dynamic_pointer_cast<InfoSub> (this->shared_from_this ()));
Json::Value jvResult (Json::objectValue);
int iRole = mHandler->getPublic ()
? RPCHandler::GUEST // Don't check on the public interface.
: iAdminGet (jvRequest, mRemoteIP);
if (RPCHandler::FORBID == iRole)
{
jvResult["result"] = rpcError (rpcFORBIDDEN);
}
else
{
jvResult["result"] = mRPCHandler.doCommand (jvRequest, iRole, &loadType);
}
// Debit/credit the load and see if we should include a warning.
//
if (getApp().getLoadManager ().applyLoadCharge (mLoadSource, loadType) &&
getApp().getLoadManager ().shouldWarn (mLoadSource))
{
jvResult["warning"] = "load";
}
// Currently we will simply unwrap errors returned by the RPC
// API, in the future maybe we can make the responses
// consistent.
//
// Regularize result. This is duplicate code.
if (jvResult["result"].isMember ("error"))
{
jvResult = jvResult["result"];
jvResult["status"] = "error";
jvResult["request"] = jvRequest;
}
else
{
jvResult["status"] = "success";
}
if (jvRequest.isMember ("id"))
{
jvResult["id"] = jvRequest["id"];
}
jvResult["type"] = "response";
return jvResult;
}
bool onPingTimer (std::string&)
{
#ifdef DISCONNECT_ON_WEBSOCKET_PING_TIMEOUTS
if (mPinged)
return true; // causes connection to close
#endif
mPinged = true;
setPingTimer ();
return false; // causes ping to be sent
}
void onPong (const std::string&)
{
mPinged = false;
}
static void pingTimer (weak_connection_ptr c, WSServerHandler<endpoint_type>* h, const boost::system::error_code& e)
{
if (e)
return;
connection_ptr ptr = c.lock ();
if (ptr)
h->pingTimer (ptr);
}
void setPingTimer ()
{
connection_ptr ptr = mConnection.lock ();
if (ptr)
{
mPingTimer.expires_from_now (boost::posix_time::seconds (theConfig.WEBSOCKET_PING_FREQ));
mPingTimer.async_wait (ptr->get_strand ().wrap (boost::bind (
&WSConnection<endpoint_type>::pingTimer, mConnection, mHandler, boost::asio::placeholders::error)));
}
}
void rcvMessage (message_ptr msg, bool& msgRejected, bool& runQueue)
{
boost::recursive_mutex::scoped_lock sl (mRcvQueueLock);
if (mDead)
{
msgRejected = false;
runQueue = false;
return;
}
if (mDead || (mRcvQueue.size () >= 1000))
{
msgRejected = !mDead;
runQueue = false;
}
else
{
msgRejected = false;
mRcvQueue.push (msg);
if (mRcvQueueRunning)
runQueue = false;
else
{
runQueue = true;
mRcvQueueRunning = true;
}
}
}
message_ptr getMessage ()
{
boost::recursive_mutex::scoped_lock sl (mRcvQueueLock);
if (mDead || mRcvQueue.empty ())
{
mRcvQueueRunning = false;
return message_ptr ();
}
message_ptr m = mRcvQueue.front ();
mRcvQueue.pop ();
return m;
}
private:
typedef void (WSConnection::*doFuncPtr) (Json::Value& jvResult, Json::Value& jvRequest);
WSServerHandler<endpoint_type>* mHandler;
weak_connection_ptr mConnection;
NetworkOPs& mNetwork;
std::string mRemoteIP;
LoadSource mLoadSource;
boost::asio::deadline_timer mPingTimer;
bool mPinged;
boost::recursive_mutex mRcvQueueLock;
std::queue<message_ptr> mRcvQueue;
bool mRcvQueueRunning;
bool mDead;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,101 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (WSDoor)
//
// This is a light weight, untrusted interface for web clients.
// For now we don't provide proof. Later we will.
//
// Might need to support this header for browsers: Access-Control-Allow-Origin: *
// - https://developer.mozilla.org/en-US/docs/HTTP_access_control
//
//
// Strategy:
// - We only talk to NetworkOPs (so we will work even in thin mode)
// - NetworkOPs is smart enough to subscribe and or pass back messages
//
// VFALCO NOTE NetworkOPs isn't used here...
//
void WSDoor::startListening ()
{
setCallingThreadName ("websocket");
// Generate a single SSL context for use by all connections.
boost::shared_ptr<boost::asio::ssl::context> mCtx;
mCtx = boost::make_shared<boost::asio::ssl::context> (boost::asio::ssl::context::sslv23);
mCtx->set_options (
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
SSL_CTX_set_tmp_dh_callback (mCtx->native_handle (), handleTmpDh);
// Construct a single handler for all requests.
websocketpp::server_autotls::handler::ptr handler (new WSServerHandler<websocketpp::server_autotls> (mCtx, mPublic));
// Construct a websocket server.
mSEndpoint = new websocketpp::server_autotls (handler);
// mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL);
// mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL);
// Call the main-event-loop of the websocket server.
try
{
mSEndpoint->listen (
boost::asio::ip::tcp::endpoint (
boost::asio::ip::address ().from_string (mIp), mPort));
}
catch (websocketpp::exception& e)
{
WriteLog (lsWARNING, WSDoor) << "websocketpp exception: " << e.what ();
while (1) // temporary workaround for websocketpp throwing exceptions on access/close races
{
// https://github.com/zaphoyd/websocketpp/issues/98
try
{
mSEndpoint->get_io_service ().run ();
break;
}
catch (websocketpp::exception& e)
{
WriteLog (lsWARNING, WSDoor) << "websocketpp exception: " << e.what ();
}
}
}
delete mSEndpoint;
}
WSDoor* WSDoor::createWSDoor (const std::string& strIp, const int iPort, bool bPublic)
{
WSDoor* wdpResult = new WSDoor (strIp, iPort, bPublic);
WriteLog (lsINFO, WSDoor) <<
boost::str (boost::format ("Websocket: %s: Listening: %s %d ")
% (bPublic ? "Public" : "Private")
% strIp
% iPort);
wdpResult->mThread = new boost::thread (BIND_TYPE (&WSDoor::startListening, wdpResult));
return wdpResult;
}
void WSDoor::stop ()
{
if (mThread)
{
if (mSEndpoint)
mSEndpoint->stop ();
mThread->join ();
}
}

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_WSDOOR_RIPPLEHEADER
#define RIPPLE_WSDOOR_RIPPLEHEADER
class WSDoor : LeakChecked <WSDoor>
{
private:
websocketpp::server_autotls* mSEndpoint;
boost::thread* mThread;
bool mPublic;
std::string mIp;
int mPort;
void startListening ();
public:
WSDoor (const std::string& strIp, int iPort, bool bPublic) : mSEndpoint (0), mThread (0), mPublic (bPublic), mIp (strIp), mPort (iPort)
{
;
}
void stop ();
static WSDoor* createWSDoor (const std::string& strIp, const int iPort, bool bPublic);
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,7 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOGN (WSServerHandlerLog,"WSServerHandler")

View File

@@ -0,0 +1,334 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_WSHANDLER_H_INCLUDED
#define RIPPLE_WSHANDLER_H_INCLUDED
extern bool serverOkay (std::string& reason);
template <typename endpoint_type>
class WSConnection;
// CAUTION: on_* functions are called by the websocket code while holding a lock
struct WSServerHandlerLog;
// A single instance of this object is made.
// This instance dispatches all events. There is no per connection persistence.
template <typename endpoint_type>
class WSServerHandler
: public endpoint_type::handler
, LeakChecked <WSServerHandler <endpoint_type> >
{
public:
typedef typename endpoint_type::handler::connection_ptr connection_ptr;
typedef typename endpoint_type::handler::message_ptr message_ptr;
typedef boost::shared_ptr< WSConnection<endpoint_type> > wsc_ptr;
// Private reasons to close.
enum
{
crTooSlow = 4000, // Client is too slow.
};
private:
boost::shared_ptr<boost::asio::ssl::context> mCtx;
protected:
boost::mutex mMapLock;
// For each connection maintain an associated object to track subscriptions.
boost::unordered_map<connection_ptr, boost::shared_ptr< WSConnection<endpoint_type> > > mMap;
bool mPublic;
public:
WSServerHandler (boost::shared_ptr<boost::asio::ssl::context> spCtx, bool bPublic) : mCtx (spCtx), mPublic (bPublic)
{
if (theConfig.WEBSOCKET_SECURE != 0)
{
basio::SslContext::initializeFromFile (
*mCtx,
theConfig.WEBSOCKET_SSL_KEY,
theConfig.WEBSOCKET_SSL_CERT,
theConfig.WEBSOCKET_SSL_CHAIN);
}
}
bool getPublic ()
{
return mPublic;
};
boost::asio::ssl::context& getASIOContext ()
{
return *mCtx;
}
static void ssend (connection_ptr cpClient, message_ptr mpMessage)
{
try
{
cpClient->send (mpMessage->get_payload (), mpMessage->get_opcode ());
}
catch (...)
{
cpClient->close (websocketpp::close::status::value (crTooSlow), std::string ("Client is too slow."));
}
}
static void ssendb (connection_ptr cpClient, const std::string& strMessage, bool broadcast)
{
try
{
WriteLog (broadcast ? lsTRACE : lsDEBUG, WSServerHandlerLog) << "Ws:: Sending '" << strMessage << "'";
cpClient->send (strMessage);
}
catch (...)
{
cpClient->close (websocketpp::close::status::value (crTooSlow), std::string ("Client is too slow."));
}
}
void send (connection_ptr cpClient, message_ptr mpMessage)
{
cpClient->get_strand ().post (BIND_TYPE (
&WSServerHandler<endpoint_type>::ssend, cpClient, mpMessage));
}
void send (connection_ptr cpClient, const std::string& strMessage, bool broadcast)
{
cpClient->get_strand ().post (BIND_TYPE (
&WSServerHandler<endpoint_type>::ssendb, cpClient, strMessage, broadcast));
}
void send (connection_ptr cpClient, const Json::Value& jvObj, bool broadcast)
{
Json::FastWriter jfwWriter;
// WriteLog (lsDEBUG, WSServerHandlerLog) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'";
send (cpClient, jfwWriter.write (jvObj), broadcast);
}
void pingTimer (connection_ptr cpClient)
{
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second;
}
std::string data ("ping");
if (ptr->onPingTimer (data))
{
WriteLog (lsWARNING, WSServerHandlerLog) << "Connection pings out";
cpClient->close (websocketpp::close::status::PROTOCOL_ERROR, "ping timeout");
}
else
cpClient->ping (data);
}
void on_send_empty (connection_ptr cpClient)
{
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second;
}
ptr->onSendEmpty ();
}
void on_open (connection_ptr cpClient)
{
boost::mutex::scoped_lock sl (mMapLock);
try
{
mMap[cpClient] = boost::make_shared< WSConnection<endpoint_type> > (this, cpClient);
}
catch (...)
{
}
}
void on_pong (connection_ptr cpClient, std::string data)
{
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second;
}
ptr->onPong (data);
}
void on_close (connection_ptr cpClient)
{
// we cannot destroy the connection while holding the map lock or we deadlock with pubLedger
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second; // prevent the WSConnection from being destroyed until we release the lock
mMap.erase (it);
}
ptr->preDestroy (); // Must be done before we return
// Must be done without holding the websocket send lock
getApp().getJobQueue ().addJob (jtCLIENT, "WSClient::destroy",
BIND_TYPE (&WSConnection<endpoint_type>::destroy, ptr));
}
void on_message (connection_ptr cpClient, message_ptr mpMessage)
{
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second;
}
bool bRejected, bRunQ;
ptr->rcvMessage (mpMessage, bRejected, bRunQ);
if (bRejected)
{
try
{
WriteLog (lsDEBUG, WSServerHandlerLog) << "Ws:: Rejected("
<< cpClient->get_socket ().lowest_layer ().remote_endpoint ().address ().to_string ()
<< ") '" << mpMessage->get_payload () << "'";
}
catch (...)
{
}
}
if (bRunQ)
getApp().getJobQueue ().addJob (jtCLIENT, "WSClient::command",
BIND_TYPE (&WSServerHandler<endpoint_type>::do_messages, this, P_1, cpClient));
}
void do_messages (Job& job, connection_ptr cpClient)
{
wsc_ptr ptr;
{
boost::mutex::scoped_lock sl (mMapLock);
typename boost::unordered_map<connection_ptr, wsc_ptr>::iterator it = mMap.find (cpClient);
if (it == mMap.end ())
return;
ptr = it->second;
}
for (int i = 0; i < 10; ++i)
{
message_ptr msg = ptr->getMessage ();
if (!msg)
return;
do_message (job, cpClient, ptr, msg);
}
getApp().getJobQueue ().addJob (jtCLIENT, "WSClient::more",
BIND_TYPE (&WSServerHandler<endpoint_type>::do_messages, this, P_1, cpClient));
}
void do_message (Job& job, const connection_ptr& cpClient, const wsc_ptr& conn, const message_ptr& mpMessage)
{
Json::Value jvRequest;
Json::Reader jrReader;
try
{
WriteLog (lsDEBUG, WSServerHandlerLog) << "Ws:: Receiving("
<< cpClient->get_socket ().lowest_layer ().remote_endpoint ().address ().to_string ()
<< ") '" << mpMessage->get_payload () << "'";
}
catch (...)
{
}
if (mpMessage->get_opcode () != websocketpp::frame::opcode::TEXT)
{
Json::Value jvResult (Json::objectValue);
jvResult["type"] = "error";
jvResult["error"] = "wsTextRequired"; // We only accept text messages.
send (cpClient, jvResult, false);
}
else if (!jrReader.parse (mpMessage->get_payload (), jvRequest) || jvRequest.isNull () || !jvRequest.isObject ())
{
Json::Value jvResult (Json::objectValue);
jvResult["type"] = "error";
jvResult["error"] = "jsonInvalid"; // Received invalid json.
jvResult["value"] = mpMessage->get_payload ();
send (cpClient, jvResult, false);
}
else
{
if (jvRequest.isMember ("command"))
job.rename (std::string ("WSClient::") + jvRequest["command"].asString ());
send (cpClient, conn->invokeCommand (jvRequest), false);
}
}
boost::shared_ptr<boost::asio::ssl::context> on_tls_init ()
{
return mCtx;
}
// Respond to http requests.
bool http (connection_ptr cpClient)
{
std::string reason;
if (!serverOkay (reason))
{
cpClient->set_body (std::string ("<HTML><BODY>Server cannot accept clients: ") + reason + "</BODY></HTML>");
return false;
}
cpClient->set_body (
"<!DOCTYPE html><html><head><title>" SYSTEM_NAME " Test</title></head>"
"<body><h1>" SYSTEM_NAME " Test</h1><p>This page shows http(s) connectivity is working.</p></body></html>");
return true;
}
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,361 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (PathRequest)
// VFALCO TODO Move these globals into a PathRequests collection inteface
boost::recursive_mutex PathRequest::sLock;
std::set <PathRequest::wptr> PathRequest::sRequests;
PathRequest::PathRequest (const boost::shared_ptr<InfoSub>& subscriber)
: wpSubscriber (subscriber)
, jvStatus (Json::objectValue)
, bValid (false)
, bNew (true)
{
}
bool PathRequest::isValid ()
{
boost::recursive_mutex::scoped_lock sl (mLock);
return bValid;
}
bool PathRequest::isNew ()
{
boost::recursive_mutex::scoped_lock sl (mLock);
return bNew;
}
bool PathRequest::isValid (Ledger::ref lrLedger)
{
boost::recursive_mutex::scoped_lock sl (mLock);
bValid = raSrcAccount.isSet () && raDstAccount.isSet () && saDstAmount.isPositive ();
if (bValid)
{
AccountState::pointer asSrc = getApp().getOPs ().getAccountState (lrLedger, raSrcAccount);
if (!asSrc)
{
// no source account
bValid = false;
jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND);
}
else
{
AccountState::pointer asDst = getApp().getOPs ().getAccountState (lrLedger, raDstAccount);
Json::Value jvDestCur;
if (!asDst)
{
// no destination account
jvDestCur.append (Json::Value ("XRP"));
if (!saDstAmount.isNative ())
{
// only XRP can be send to a non-existent account
bValid = false;
jvStatus = rpcError (rpcACT_NOT_FOUND);
}
else if (saDstAmount < STAmount (lrLedger->getReserve (0)))
{
// payment must meet reserve
bValid = false;
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
}
}
else
{
boost::unordered_set<uint160> usDestCurrID = usAccountDestCurrencies (raDstAccount, lrLedger, true);
BOOST_FOREACH (const uint160 & uCurrency, usDestCurrID)
jvDestCur.append (STAmount::createHumanCurrency (uCurrency));
jvStatus["destination_tag"] = (asDst->peekSLE ().getFlags () & lsfRequireDestTag) != 0;
}
jvStatus["destination_currencies"] = jvDestCur;
}
}
jvStatus["ledger_hash"] = lrLedger->getHash ().GetHex ();
jvStatus["ledger_index"] = lrLedger->getLedgerSeq ();
return bValid;
}
Json::Value PathRequest::doCreate (Ledger::ref lrLedger, const Json::Value& value)
{
assert (lrLedger->isClosed ());
Json::Value status;
bool mValid;
{
boost::recursive_mutex::scoped_lock sl (mLock);
if (parseJson (value, true) != PFR_PJ_INVALID)
{
mValid = isValid (lrLedger);
if (mValid)
{
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lrLedger);
doUpdate (cache, true);
}
}
else
mValid = false;
}
if (mValid)
{
WriteLog (lsINFO, PathRequest) << "Request created: " << raSrcAccount.humanAccountID () <<
" -> " << raDstAccount.humanAccountID ();
WriteLog (lsINFO, PathRequest) << "Deliver: " << saDstAmount.getFullText ();
boost::recursive_mutex::scoped_lock sl (sLock);
sRequests.insert (shared_from_this ());
}
return jvStatus;
}
int PathRequest::parseJson (const Json::Value& jvParams, bool complete)
{
int ret = PFR_PJ_NOCHANGE;
if (jvParams.isMember ("source_account"))
{
if (!raSrcAccount.setAccountID (jvParams["source_account"].asString ()))
{
jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
}
else if (complete)
{
jvStatus = rpcError (rpcSRC_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember ("destination_account"))
{
if (!raDstAccount.setAccountID (jvParams["destination_account"].asString ()))
{
jvStatus = rpcError (rpcDST_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
}
else if (complete)
{
jvStatus = rpcError (rpcDST_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember ("destination_amount"))
{
if (!saDstAmount.bSetJson (jvParams["destination_amount"]) ||
(saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) ||
(saDstAmount.getCurrency () == CURRENCY_BAD) ||
!saDstAmount.isPositive ())
{
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
return PFR_PJ_INVALID;
}
}
else if (complete)
{
jvStatus = rpcError (rpcDST_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember ("source_currencies"))
{
const Json::Value& jvSrcCur = jvParams["source_currencies"];
if (!jvSrcCur.isArray ())
{
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
return PFR_PJ_INVALID;
}
sciSourceCurrencies.clear ();
for (unsigned i = 0; i < jvSrcCur.size (); ++i)
{
const Json::Value& jvCur = jvSrcCur[i];
uint160 uCur, uIss;
if (!jvCur.isObject() || !jvCur.isMember ("currency") || !STAmount::currencyFromString (uCur, jvCur["currency"].asString ()))
{
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
return PFR_PJ_INVALID;
}
if (jvCur.isMember ("issuer") && !STAmount::issuerFromString (uIss, jvCur["issuer"].asString ()))
{
jvStatus = rpcError (rpcSRC_ISR_MALFORMED);
}
if (uCur.isZero () && uIss.isNonZero ())
{
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
return PFR_PJ_INVALID;
}
sciSourceCurrencies.insert (currIssuer_t (uCur, uIss));
}
}
if (jvParams.isMember ("id"))
jvId = jvParams["id"];
return ret;
}
Json::Value PathRequest::doClose (const Json::Value&)
{
boost::recursive_mutex::scoped_lock sl (mLock);
return jvStatus;
}
Json::Value PathRequest::doStatus (const Json::Value&)
{
boost::recursive_mutex::scoped_lock sl (mLock);
return jvStatus;
}
bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
{
boost::recursive_mutex::scoped_lock sl (mLock);
jvStatus = Json::objectValue;
if (!isValid (cache->getLedger ()))
return false;
if (!fast)
bNew = false;
std::set<currIssuer_t> sourceCurrencies (sciSourceCurrencies);
if (sourceCurrencies.empty ())
{
boost::unordered_set<uint160> usCurrencies =
usAccountSourceCurrencies (raSrcAccount, cache->getLedger (), true);
bool sameAccount = raSrcAccount == raDstAccount;
BOOST_FOREACH (const uint160 & c, usCurrencies)
{
if (!sameAccount || (c != saDstAmount.getCurrency ()))
{
if (c.isZero ())
sourceCurrencies.insert (std::make_pair (c, ACCOUNT_XRP));
else
sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ()));
}
}
}
jvStatus["source_account"] = raSrcAccount.humanAccountID ();
jvStatus["destination_account"] = raDstAccount.humanAccountID ();
jvStatus["destination_amount"] = saDstAmount.getJson (0);
if (!jvId.isNull ())
jvStatus["id"] = jvId;
Json::Value jvArray = Json::arrayValue;
BOOST_FOREACH (const currIssuer_t & currIssuer, sourceCurrencies)
{
{
STAmount test (currIssuer.first, currIssuer.second, 1);
WriteLog (lsDEBUG, PathRequest) << "Trying to find paths: " << test.getFullText ();
}
bool valid;
STPathSet spsPaths;
Pathfinder pf (cache, raSrcAccount, raDstAccount,
currIssuer.first, currIssuer.second, saDstAmount, valid);
CondLog (!valid, lsINFO, PathRequest) << "PF request not valid";
if (valid && pf.findPaths (theConfig.PATH_SEARCH_SIZE - (fast ? 0 : 1), 3, spsPaths))
{
LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE);
std::vector<PathState::pointer> vpsExpanded;
STAmount saMaxAmountAct;
STAmount saDstAmountAct;
STAmount saMaxAmount (currIssuer.first,
currIssuer.second.isNonZero () ? currIssuer.second :
(currIssuer.first.isZero () ? ACCOUNT_XRP : raSrcAccount.getAccountID ()), 1);
saMaxAmount.negate ();
WriteLog (lsDEBUG, PathRequest) << "Paths found, calling rippleCalc";
TER terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
vpsExpanded, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
spsPaths, false, false, false, true);
if (terResult == tesSUCCESS)
{
Json::Value jvEntry (Json::objectValue);
jvEntry["source_amount"] = saMaxAmountAct.getJson (0);
jvEntry["paths_computed"] = spsPaths.getJson (0);
jvArray.append (jvEntry);
}
else
{
WriteLog (lsINFO, PathRequest) << "rippleCalc returns " << transHuman (terResult);
}
}
else
{
WriteLog (lsINFO, PathRequest) << "No paths found";
}
}
jvStatus["alternatives"] = jvArray;
return true;
}
void PathRequest::updateAll (Ledger::ref ledger, bool newOnly)
{
std::set<wptr> requests;
{
boost::recursive_mutex::scoped_lock sl (sLock);
requests = sRequests;
}
if (requests.empty ())
return;
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (ledger);
BOOST_FOREACH (wref wRequest, requests)
{
bool remove = true;
PathRequest::pointer pRequest = wRequest.lock ();
if (pRequest && (!newOnly || pRequest->isNew ()))
{
InfoSub::pointer ipSub = pRequest->wpSubscriber.lock ();
if (ipSub)
{
Json::Value update;
{
boost::recursive_mutex::scoped_lock sl (pRequest->mLock);
pRequest->doUpdate (cache, false);
update = pRequest->jvStatus;
}
update["type"] = "path_find";
ipSub->send (update, false);
remove = false;
}
}
if (remove)
{
boost::recursive_mutex::scoped_lock sl (sLock);
sRequests.erase (wRequest);
}
}
}
// vim:ts=4

View File

@@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PATHREQUEST_H
#define RIPPLE_PATHREQUEST_H
// A pathfinding request submitted by a client
// The request issuer must maintain a strong pointer
class RippleLineCache;
// Return values from parseJson <0 = invalid, >0 = valid
#define PFR_PJ_INVALID -1
#define PFR_PJ_NOCHANGE 0
#define PFR_PJ_CHANGE 1
class PathRequest : public boost::enable_shared_from_this<PathRequest>
{
public:
typedef boost::weak_ptr<PathRequest> wptr;
typedef boost::shared_ptr<PathRequest> pointer;
typedef const pointer& ref;
typedef const wptr& wref;
typedef std::pair<uint160, uint160> currIssuer_t;
public:
// VFALCO TODO Break the cyclic dependency on InfoSub
explicit PathRequest (boost::shared_ptr <InfoSub> const& subscriber);
bool isValid (const boost::shared_ptr<Ledger>&);
bool isValid ();
bool isNew ();
Json::Value getStatus ();
Json::Value doCreate (const boost::shared_ptr<Ledger>&, const Json::Value&);
Json::Value doClose (const Json::Value&);
Json::Value doStatus (const Json::Value&);
bool doUpdate (const boost::shared_ptr<RippleLineCache>&, bool fast); // update jvStatus
static void updateAll (const boost::shared_ptr<Ledger>& ledger, bool newOnly);
private:
boost::recursive_mutex mLock;
boost::weak_ptr<InfoSub> wpSubscriber; // Who this request came from
Json::Value jvId;
Json::Value jvStatus; // Last result
// Client request parameters
RippleAddress raSrcAccount;
RippleAddress raDstAccount;
STAmount saDstAmount;
std::set<currIssuer_t> sciSourceCurrencies;
std::vector<Json::Value> vjvBridges;
bool bValid;
bool bNew;
// Track all requests
static std::set<wptr> sRequests;
static boost::recursive_mutex sLock;
void setValid ();
int parseJson (const Json::Value&, bool complete);
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,794 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// TODO:
// - Do automatic bridging via XRP.
//
// OPTIMIZE: When calculating path increment, note if increment consumes all liquidity. No need to revisit path in the future if
// all liquidity is used.
//
class RippleCalc; // for logging
std::size_t hash_value (const aciSource& asValue)
{
std::size_t seed = 0;
asValue.get<0> ().hash_combine (seed);
asValue.get<1> ().hash_combine (seed);
asValue.get<2> ().hash_combine (seed);
return seed;
}
// Compare the non-calculated fields.
bool PathState::Node::operator== (const Node& pnOther) const
{
return pnOther.uFlags == uFlags
&& pnOther.uAccountID == uAccountID
&& pnOther.uCurrencyID == uCurrencyID
&& pnOther.uIssuerID == uIssuerID;
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PathState::Node::getJson () const
{
Json::Value jvNode (Json::objectValue);
Json::Value jvFlags (Json::arrayValue);
jvNode["type"] = uFlags;
if (isSetBit (uFlags, STPathElement::typeAccount) || !!uAccountID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account");
if (isSetBit (uFlags, STPathElement::typeCurrency) || !!uCurrencyID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency");
if (isSetBit (uFlags, STPathElement::typeIssuer) || !!uIssuerID)
jvFlags.append (!!isSetBit (uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer");
jvNode["flags"] = jvFlags;
if (!!uAccountID)
jvNode["account"] = RippleAddress::createHumanAccountID (uAccountID);
if (!!uCurrencyID)
jvNode["currency"] = STAmount::createHumanCurrency (uCurrencyID);
if (!!uIssuerID)
jvNode["issuer"] = RippleAddress::createHumanAccountID (uIssuerID);
if (saRevRedeem)
jvNode["rev_redeem"] = saRevRedeem.getFullText ();
if (saRevIssue)
jvNode["rev_issue"] = saRevIssue.getFullText ();
if (saRevDeliver)
jvNode["rev_deliver"] = saRevDeliver.getFullText ();
if (saFwdRedeem)
jvNode["fwd_redeem"] = saFwdRedeem.getFullText ();
if (saFwdIssue)
jvNode["fwd_issue"] = saFwdIssue.getFullText ();
if (saFwdDeliver)
jvNode["fwd_deliver"] = saFwdDeliver.getFullText ();
return jvNode;
}
//
// PathState implementation
//
// Return true, iff lhs has less priority than rhs.
bool PathState::lessPriority (PathState& lhs, PathState& rhs)
{
// First rank is quality.
if (lhs.uQuality != rhs.uQuality)
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
// Second rank is best quantity.
if (lhs.saOutPass != rhs.saOutPass)
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
// Third rank is path index.
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
}
// Make sure last path node delivers to uAccountID: uCurrencyID from uIssuerID.
//
// If the unadded next node as specified by arguments would not work as is, then add the necessary nodes so it would work.
//
// Rules:
// - Currencies must be converted via an offer.
// - A node names it's output.
// - A ripple nodes output issuer must be the node's account or the next node's account.
// - Offers can only go directly to another offer if the currency and issuer are an exact match.
// - Real issuers must be specified for non-XRP.
TER PathState::pushImply (
const uint160& uAccountID, // --> Delivering to this account.
const uint160& uCurrencyID, // --> Delivering this currency.
const uint160& uIssuerID) // --> Delivering this issuer.
{
const Node& pnPrv = vpnNodes.back ();
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushImply> "
<< RippleAddress::createHumanAccountID (uAccountID)
<< " " << STAmount::createHumanCurrency (uCurrencyID)
<< " " << RippleAddress::createHumanAccountID (uIssuerID);
if (pnPrv.uCurrencyID != uCurrencyID)
{
// Currency is different, need to convert via an offer.
terResult = pushNode ( // Offer.
!!uCurrencyID
? STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeCurrency,
ACCOUNT_XRP, // Placeholder for offers.
uCurrencyID, // The offer's output is what is now wanted.
uIssuerID);
}
const Node& pnBck = vpnNodes.back ();
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
if (tesSUCCESS == terResult
&& !!uCurrencyID // Not XRP.
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
{
// Need to ripple through uIssuerID's account.
terResult = pushNode (
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
uIssuerID, // Intermediate account is the needed issuer.
uCurrencyID,
uIssuerID);
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushImply< : %s") % transToken (terResult));
return terResult;
}
// Append a node and insert before it any implied nodes.
// Offers may go back to back.
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
const int iType,
const uint160& uAccountID,
const uint160& uCurrencyID,
const uint160& uIssuerID)
{
Node pnCur;
const bool bFirst = vpnNodes.empty ();
const Node& pnPrv = bFirst ? Node () : vpnNodes.back ();
// true, iff node is a ripple account. false, iff node is an offer node.
const bool bAccount = isSetBit (iType, STPathElement::typeAccount);
// true, iff currency supplied.
// Currency is specified for the output of the current node.
const bool bCurrency = isSetBit (iType, STPathElement::typeCurrency);
// Issuer is specified for the output of the current node.
const bool bIssuer = isSetBit (iType, STPathElement::typeIssuer);
TER terResult = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushNode> "
<< iType
<< ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-")
<< " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-")
<< "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
pnCur.uFlags = iType;
pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;
if (iType & ~STPathElement::typeValidBits)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
terResult = temBAD_PATH;
}
else if (bIssuer && !pnCur.uCurrencyID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
terResult = temBAD_PATH;
}
else if (bIssuer && !uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
terResult = temBAD_PATH;
}
else if (!bAccount && !bCurrency && !bIssuer)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
terResult = temBAD_PATH;
}
else if (bAccount)
{
// Account link
pnCur.uAccountID = uAccountID;
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? uAccountID
: ACCOUNT_XRP;
pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (bFirst)
{
// The first node is always correct as is.
nothing ();
}
else if (!uAccountID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
terResult = temBAD_PATH;
}
else
{
// Add required intermediate nodes to deliver to current account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
terResult = pushImply (
pnCur.uAccountID, // Current account.
pnCur.uCurrencyID, // Wanted currency.
!!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
// Note: pnPrv may no longer be the immediately previous node.
}
if (tesSUCCESS == terResult && !vpnNodes.empty ())
{
const Node& pnBck = vpnNodes.back ();
bool bBckAccount = isSetBit (pnBck.uFlags, STPathElement::typeAccount);
if (bBckAccount)
{
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));
if (!sleRippleState)
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
WriteLog (lsTRACE, RippleCalc) << getJson ();
terResult = terNO_LINE;
}
else
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< "." ;
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
bool bHigh = pnBck.uAccountID > pnCur.uAccountID;
if (!sleBck)
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
terResult = terNO_ACCOUNT;
}
else if ((isSetBit (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
&& !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
&& sleRippleState->getFieldAmount(sfBalance).isZero()) // CHECKME
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
terResult = terNO_AUTH;
}
if (tesSUCCESS == terResult)
{
STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
STAmount saLimit;
if (!saOwed.isPositive ()
&& -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("pushNode: dry: saOwed=%s saLimit=%s")
% saOwed
% saLimit);
terResult = tecPATH_DRY;
}
}
}
}
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
else
{
// Offer link
// Offers bridge a change in currency & issuer or just a change in issuer.
pnCur.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? !!pnPrv.uIssuerID
? pnPrv.uIssuerID // Default to previous issuer
: pnPrv.uAccountID // Or previous account if no previous issuer.
: ACCOUNT_XRP;
pnCur.saRateMax = saZero;
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";
terResult = temBAD_PATH;
}
else if (!!pnPrv.uAccountID)
{
// Previous is an account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
// Insert intermediary issuer account if needed.
terResult = pushImply (
ACCOUNT_XRP, // Rippling, but offers don't have an account.
pnPrv.uCurrencyID,
pnPrv.uIssuerID);
}
if (tesSUCCESS == terResult)
{
vpnNodes.push_back (pnCur);
}
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("pushNode< : %s") % transToken (terResult));
return terResult;
}
// Set to an expanded path.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP
void PathState::setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
)
{
uQuality = 1; // Mark path as active.
const uint160 uMaxCurrencyID = saInReq.getCurrency ();
const uint160 uMaxIssuerID = saInReq.getIssuer ();
const uint160 uOutCurrencyID = saOutReq.getCurrency ();
const uint160 uOutIssuerID = saOutReq.getIssuer ();
const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0));
lesEntries = lesSource.duplicate ();
terStatus = tesSUCCESS;
// XRP with issuer is malformed.
if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID))
terStatus = temBAD_PATH;
// Push sending node.
// For non-XRP, issuer is always sending account.
// - Trying to expand, not-compact.
// - Every issuer will be traversed through.
if (tesSUCCESS == terStatus)
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uSenderID,
uMaxCurrencyID, // Max specifes the currency.
uSenderIssuerID);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: pushed: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uSenderID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uSenderIssuerID));
if (tesSUCCESS == terStatus
&& uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender.
{
// May have an implied account node.
// - If it was XRP, then issuers would have matched.
// Figure out next node properties for implied node.
const uint160 uNxtCurrencyID = spSourcePath.size ()
? spSourcePath.getElement (0).getCurrency () // Use next node.
: uOutCurrencyID; // Use send.
const uint160 uNxtAccountID = spSourcePath.size ()
? spSourcePath.getElement (0).getAccountID ()
: !!uOutCurrencyID
? uOutIssuerID == uReceiverID
? uReceiverID
: uOutIssuerID // Use implied node.
: ACCOUNT_XRP;
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: implied check: uMaxIssuerID=%s uSenderIssuerID=%s uNxtCurrencyID=%s uNxtAccountID=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% RippleAddress::createHumanAccountID (uSenderIssuerID)
% STAmount::createHumanCurrency (uNxtCurrencyID)
% RippleAddress::createHumanAccountID (uNxtAccountID));
// Can't just use push implied, because it can't compensate for next account.
if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer.
|| uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next...
|| uMaxIssuerID != uNxtAccountID) // Next is not implied issuer
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: sender implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID));
// Add account implied by SendMax.
terStatus = pushNode (
!!uMaxCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uMaxIssuerID,
uMaxCurrencyID,
uMaxIssuerID);
}
}
BOOST_FOREACH (const STPathElement & speElement, spSourcePath)
{
if (tesSUCCESS == terStatus)
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:"));
terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ());
}
}
const Node& pnPrv = vpnNodes.back ();
if (tesSUCCESS == terStatus
&& !!uOutCurrencyID // Next is not XRP
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
&& (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|| pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer.
{
// Add implied account.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: receiver implied: account=%s currency=%s issuer=%s")
% RippleAddress::createHumanAccountID (uOutIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID));
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uOutIssuerID,
uOutCurrencyID,
uOutIssuerID);
}
if (tesSUCCESS == terStatus)
{
// Create receiver node.
// Last node is always an account.
terStatus = pushNode (
!!uOutCurrencyID
? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uReceiverID, // Receive to output
uOutCurrencyID, // Desired currency
uReceiverID);
}
if (tesSUCCESS == terStatus)
{
// Look for first mention of source in nodes and detect loops.
// Note: The output is not allowed to be a source.
const unsigned int uNodes = vpnNodes.size ();
for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
{
const Node& pnCur = vpnNodes[uNode];
if (!umForward.insert (std::make_pair (boost::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
{
// Failed to insert. Have a loop.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: loop detected: %s")
% getJson ());
terStatus = temBAD_PATH_LOOP;
}
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// Set to a canonical path.
// - Remove extra elements
// - Assumes path is expanded.
//
// We do canonicalization to:
// - Prevent waste in the ledger.
// - Allow longer paths to be specified than would otherwise be allowed.
//
// Optimization theory:
// - Can omit elements that the expansion routine derives.
// - Can pack some elements into other elements.
//
// Rules:
// - SendMax if not specified, defaults currency to send and if not sending XRP defaults issuer to sender.
// - All paths start with the sender account.
// - Currency and issuer is from SendMax.
// - All paths end with the destination account.
//
// Optimization:
// - An XRP output implies an offer node or destination node is next.
// - A change in currency implies an offer node.
// - A change in issuer...
void PathState::setCanonical (
const PathState& psExpanded
)
{
assert (false);
saInAct = psExpanded.saInAct;
saOutAct = psExpanded.saOutAct;
const uint160 uMaxCurrencyID = saInAct.getCurrency ();
const uint160 uMaxIssuerID = saInAct.getIssuer ();
const uint160 uOutCurrencyID = saOutAct.getCurrency ();
const uint160 uOutIssuerID = saOutAct.getIssuer ();
unsigned int uNode = 0;
unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include.
uint160 uDstAccountID = psExpanded.vpnNodes[uEnd].uAccountID; // FIXME: This can't be right
uint160 uAccountID = psExpanded.vpnNodes[0].uAccountID;
uint160 uCurrencyID = uMaxCurrencyID;
uint160 uIssuerID = uMaxIssuerID;
// Node 0 is a composite of the sending account and saInAct.
++uNode; // skip node 0
// Last node is implied: Always skip last node
--uEnd; // skip last node
// saInAct
// - currency is always the same as vpnNodes[0].
#if 1
if (uNode != uEnd && uMaxIssuerID != uAccountID)
{
// saInAct issuer is not the sender. This forces an implied node.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd);
// skip node 1
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode;
}
#else
if (uNode != uEnd)
{
// Have another node
bool bKeep = false;
if (uMaxIssuerID != uAccountID)
{
}
if (uMaxCurrencyID) // Not sending XRP.
{
// Node 1 must be an account.
if (uMaxIssuerID != uAccountID)
{
// Node 1 is required to specify issuer.
bKeep = true;
}
else
{
// Node 1 must be an account
}
}
else
{
// Node 1 must be an order book.
bKeep = true;
}
if (bKeep)
{
uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID;
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode; // Keep it.
}
}
#endif
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd);
// The next to last node is saOutAct if an issuer different from receiver is supplied.
// The next to last node can be implied.
--uEnd;
}
const Node& pnEnd = psExpanded.vpnNodes[uEnd];
if (uNode != uEnd
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
{
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd);
--uEnd;
}
// Do not include uEnd.
for (; uNode != uEnd; ++uNode)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd);
const Node& pnPrv = psExpanded.vpnNodes[uNode - 1];
const Node& pnCur = psExpanded.vpnNodes[uNode];
const Node& pnNxt = psExpanded.vpnNodes[uNode + 1];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
bool bSkip = false;
if (bCurAccount)
{
// Currently at an account.
// Output is non-XRP and issuer is account.
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
{
// Account issues itself.
// XXX Not good enough. Previous account must mention it.
bSkip = true;
}
}
else
{
// Currently at an offer.
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
{
// Offer can be implied by currency change.
// XXX What about issuer?
bSkip = true;
}
}
if (!bSkip)
{
// Copy node
Node pnNew;
bool bSetAccount = bCurAccount;
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
// XXX What if we need the next account because we want to skip it?
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
| (bSetCurrency ? STPathElement::typeCurrency : 0)
| (bSetIssuer ? STPathElement::typeIssuer : 0);
if (bSetAccount)
pnNew.uAccountID = pnCur.uAccountID;
if (bSetCurrency)
{
pnNew.uCurrencyID = pnCur.uCurrencyID;
uCurrencyID = pnNew.uCurrencyID;
}
if (bSetIssuer)
pnNew.uIssuerID = pnCur.uIssuerID;
// XXX ^^^ What about setting uIssuerID?
if (bSetCurrency && !uCurrencyID)
uIssuerID.zero ();
vpnNodes.push_back (pnNew);
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setCanonical: in=%s/%s out=%s/%s %s")
% STAmount::createHumanCurrency (uMaxCurrencyID)
% RippleAddress::createHumanAccountID (uMaxIssuerID)
% STAmount::createHumanCurrency (uOutCurrencyID)
% RippleAddress::createHumanAccountID (uOutIssuerID)
% getJson ());
}
// This is for debugging not end users. Output names can be changed without warning.
Json::Value PathState::getJson () const
{
Json::Value jvPathState (Json::objectValue);
Json::Value jvNodes (Json::arrayValue);
BOOST_FOREACH (const Node & pnNode, vpnNodes)
{
jvNodes.append (pnNode.getJson ());
}
jvPathState["status"] = terStatus;
jvPathState["index"] = mIndex;
jvPathState["nodes"] = jvNodes;
if (saInReq)
jvPathState["in_req"] = saInReq.getJson (0);
if (saInAct)
jvPathState["in_act"] = saInAct.getJson (0);
if (saInPass)
jvPathState["in_pass"] = saInPass.getJson (0);
if (saOutReq)
jvPathState["out_req"] = saOutReq.getJson (0);
if (saOutAct)
jvPathState["out_act"] = saOutAct.getJson (0);
if (saOutPass)
jvPathState["out_pass"] = saOutPass.getJson (0);
if (uQuality)
jvPathState["uQuality"] = boost::str (boost::format ("%d") % uQuality);
return jvPathState;
}

View File

@@ -0,0 +1,164 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PATHSTATE_H
#define RIPPLE_PATHSTATE_H
// account id, currency id, issuer id :: node
typedef boost::tuple <uint160, uint160, uint160> aciSource;
typedef boost::unordered_map <aciSource, unsigned int> curIssuerNode; // Map of currency, issuer to node index.
typedef boost::unordered_map <aciSource, unsigned int>::const_iterator curIssuerNodeConstIterator;
extern std::size_t hash_value (const aciSource& asValue);
// Holds a path state under incremental application.
class PathState
{
public:
class Node
{
public:
bool operator == (Node const& pnOther) const;
Json::Value getJson () const;
public:
uint16 uFlags; // --> From path.
uint160 uAccountID; // --> Accounts: Recieving/sending account.
uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send.
// --- For offer's next has currency out.
uint160 uIssuerID; // --> Currency's issuer
STAmount saTransferRate; // Transfer rate for uIssuerID.
// Computed by Reverse.
STAmount saRevRedeem; // <-- Amount to redeem to next.
STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs.
// Issue isn't used by offers.
STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee.
// Computed by forward.
STAmount saFwdRedeem; // <-- Amount node will redeem to next.
STAmount saFwdIssue; // <-- Amount node will issue to next.
// Issue isn't used by offers.
STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee.
// For offers:
STAmount saRateMax;
// Directory
uint256 uDirectTip; // Current directory.
uint256 uDirectEnd; // Next order book.
bool bDirectAdvance; // Need to advance directory.
SLE::pointer sleDirectDir;
STAmount saOfrRate; // For correct ratio.
// PaymentNode
bool bEntryAdvance; // Need to advance entry.
unsigned int uEntry;
uint256 uOfferIndex;
SLE::pointer sleOffer;
uint160 uOfrOwnerID;
bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets.
STAmount saOfferFunds;
STAmount saTakerPays;
STAmount saTakerGets;
};
public:
typedef boost::shared_ptr<PathState> pointer;
typedef const boost::shared_ptr<PathState>& ref;
public:
PathState* setIndex (const int iIndex)
{
mIndex = iIndex;
return this;
}
int getIndex ()
{
return mIndex;
};
PathState (
const STAmount& saSend,
const STAmount& saSendMax)
: saInReq (saSendMax)
, saOutReq (saSend)
{
}
PathState (const PathState& psSrc,
bool bUnused)
: saInReq (psSrc.saInReq)
, saOutReq (psSrc.saOutReq)
{
}
void setExpanded (
const LedgerEntrySet& lesSource,
const STPath& spSourcePath,
const uint160& uReceiverID,
const uint160& uSenderID
);
void setCanonical (
const PathState& psExpanded
);
Json::Value getJson () const;
#if 0
static PathState::pointer createCanonical (
PathState& ref pspExpanded
)
{
PathState::pointer pspNew = boost::make_shared<PathState> (pspExpanded->saOutAct, pspExpanded->saInAct);
pspNew->setCanonical (pspExpanded);
return pspNew;
}
#endif
static bool lessPriority (PathState& lhs, PathState& rhs);
public:
TER terStatus;
std::vector<Node> vpnNodes;
// When processing, don't want to complicate directory walking with deletion.
std::vector<uint256> vUnfundedBecame; // Offers that became unfunded or were completely consumed.
// First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be
// used there.
curIssuerNode umForward; // Map of currency, issuer to node index.
// First time working in reverse a funding source was used.
// Source may only be used there if not mentioned by an account.
curIssuerNode umReverse; // Map of currency, issuer to node index.
LedgerEntrySet lesEntries;
int mIndex; // Index/rank amoung siblings.
uint64 uQuality; // 0 = no quality/liquity left.
const STAmount& saInReq; // --> Max amount to spend by sender.
STAmount saInAct; // --> Amount spent by sender so far.
STAmount saInPass; // <-- Amount spent by sender.
const STAmount& saOutReq; // --> Amount to send.
STAmount saOutAct; // --> Amount actually sent so far.
STAmount saOutPass; // <-- Amount actually sent.
bool bConsumed; // If true, use consumes full liquidity. False, may or may not.
private:
TER pushNode (const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
TER pushImply (const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID);
};
#endif

View File

@@ -0,0 +1,845 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (Pathfinder)
/*
we just need to find a succession of the highest quality paths there until we find enough width
Don't do branching within each path
We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency?
Loops
TODO: what is a good way to come up with multiple paths?
Maybe just change the sort criteria?
first a low cost one and then a fat short one?
OrderDB:
getXRPOffers();
// return list of all orderbooks that want XRP
// return list of all orderbooks that want IssuerID
// return list of all orderbooks that want this issuerID and currencyID
*/
/*
Test sending to XRP
Test XRP to XRP
Test offer in middle
Test XRP to USD
Test USD to EUR
*/
// we sort the options by:
// cost of path
// length of path
// width of path
// correct currency at the end
// quality, length, liquidity, index
typedef boost::tuple<uint64, int, STAmount, unsigned int> path_LQ_t;
// Lower numbers have better quality. Sort higher quality first.
static bool bQualityCmp (const path_LQ_t& a, const path_LQ_t& b)
{
// 1) Higher quality (lower cost) is better
if (a.get<0> () != b.get<0> ())
return a.get<0> () < b.get<0> ();
// 2) More liquidity (higher volume) is better
if (a.get<2> () != b.get<2> ())
return a.get<2> () > b.get<2> ();
// 3) Shorter paths are better
if (a.get<1> () != b.get<1> ())
return a.get<1> () < b.get<1> ();
// 4) Tie breaker
return a.get<3> () > b.get<3> ();
}
// Return true, if path is a default path with an element.
// A path is a default path if it is implied via src, dst, send, and sendmax.
bool Pathfinder::bDefaultPath (const STPath& spPath)
{
if (2 >= spPath.mPath.size ())
{
// Empty path is a default. Don't need to add it to return set.
WriteLog (lsTRACE, Pathfinder) << "findPaths: empty path: direct";
return true;
}
if (!mPsDefault)
{
// No default path.
// There might not be a direct credit line or there may be no implied nodes
// in send and sendmax.
return false; // Didn't generate a default path. So can't match.
}
PathState::pointer pspCurrent = boost::make_shared<PathState> (mDstAmount, mSrcAmount);
if (pspCurrent)
{
bool bDefault;
LedgerEntrySet lesActive (mLedger, tapNONE);
WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("bDefaultPath> mSrcAmount=%s mDstAmount=%s")
% mSrcAmount.getFullText ()
% mDstAmount.getFullText ());
// Expand the current path.
pspCurrent->setExpanded (lesActive, spPath, mDstAccountID, mSrcAccountID);
// XXX Need to report or act on errors returned in pspCurrent->terStatus.
// Determine if expanded current path is the default.
// When path is a default (implied). Don't need to add it to return set.
bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes;
WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: expanded path: " << pspCurrent->getJson ();
WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: source path: " << spPath.getJson (0);
WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: default path: " << mPsDefault->getJson ();
return bDefault;
}
return false;
}
typedef std::pair<int, uint160> candidate_t;
static bool candCmp (uint32 seq, const candidate_t& first, const candidate_t& second)
{
if (first.first < second.first)
return false;
if (first.first > second.first)
return true;
return (first.first ^ seq) < (second.first ^ seq);
}
static int getEffectiveLength (const STPath& spPath)
{
// don't count exchanges to non-XRP currencies twice (only count the forced issuer account node)
int length = 0;
for (std::vector<STPathElement>::const_iterator it = spPath.begin (); it != spPath.end (); ++it)
{
if (it->isAccount () || it->getCurrency ().isZero ())
++length;
}
return length;
}
Pathfinder::Pathfinder (RippleLineCache::ref cache,
const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID,
const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount, bool& bValid)
: mSrcAccountID (uSrcAccountID.getAccountID ()),
mDstAccountID (uDstAccountID.getAccountID ()),
mDstAmount (saDstAmount),
mSrcCurrencyID (uSrcCurrencyID),
mSrcIssuerID (uSrcIssuerID),
mSrcAmount (uSrcCurrencyID, uSrcIssuerID, 1u, 0, true),
mLedger (cache->getLedger ()), mRLCache (cache)
{
if (((mSrcAccountID == mDstAccountID) && (mSrcCurrencyID == mDstAmount.getCurrency ())) || mDstAmount.isZero ())
{
// no need to send to same account with same currency, must send non-zero
bValid = false;
mLedger.reset ();
return;
}
bValid = true;
getApp().getOrderBookDB ().setup (mLedger);
m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath");
// Construct the default path for later comparison.
PathState::pointer psDefault = boost::make_shared<PathState> (mDstAmount, mSrcAmount);
if (psDefault)
{
// Build the default path.
// Later, reject anything that expands to the default path as the default is sufficient.
LedgerEntrySet lesActive (mLedger, tapNONE);
WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("Pathfinder> mSrcAmount=%s mDstAmount=%s")
% mSrcAmount.getFullText ()
% mDstAmount.getFullText ());
psDefault->setExpanded (lesActive, STPath (), mDstAccountID, mSrcAccountID);
if (tesSUCCESS == psDefault->terStatus)
{
// The default path works, remember it.
WriteLog (lsTRACE, Pathfinder) << "Pathfinder: default path: " << psDefault->getJson ();
mPsDefault = psDefault;
}
else
{
// The default path doesn't work.
WriteLog (lsTRACE, Pathfinder) << "Pathfinder: default path: NONE: " << transToken (psDefault->terStatus);
}
}
}
// If possible, returns a single path.
// --> iMaxSteps: Maximum nodes in paths to return.
// --> iMaxPaths: Maximum number of paths to return.
// <-- retPathSet: founds paths not including default paths.
// Returns true if found paths.
//
// When generating a path set blindly, don't allow the empty path, it is implied by default.
// When generating a path set for estimates, allow an empty path instead of no paths to indicate a path exists. The caller will
// need to strip the empty path when submitting the transaction.
//
// Assumes rippling (not XRP to XRP)
//
// Leaves to the caller figuring out overall liquidity.
// Optimization opportunity: For some simple cases, this routine has figured out the overall liquidity.
bool Pathfinder::findPaths (const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst)
{
bool bFound = false; // True, iff found a path.
WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s")
% RippleAddress::createHumanAccountID (mSrcAccountID)
% RippleAddress::createHumanAccountID (mDstAccountID)
% mDstAmount.getFullText ()
% STAmount::createHumanCurrency (mSrcCurrencyID)
% RippleAddress::createHumanAccountID (mSrcIssuerID)
);
if (!mLedger)
{
WriteLog (lsDEBUG, Pathfinder) << "findPaths< no ledger";
return false;
}
LedgerEntrySet lesActive (mLedger, tapNONE);
boost::unordered_map<uint160, AccountItems::pointer> aiMap;
SLE::pointer sleSrc = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mSrcAccountID));
if (!sleSrc)
{
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< no source"));
return false;
}
SLE::pointer sleDst = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mDstAccountID));
if (!sleDst)
{
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< no dest"));
return false;
}
std::vector<STPath> vspResults;
std::queue<STPath> qspExplore; // Path stubs to explore.
STPath spSeed;
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer.
// The end is the cursor, start at the source account.
STPathElement speEnd (mSrcAccountID,
mSrcCurrencyID,
!!mSrcCurrencyID
? mSrcAccountID // Non-XRP, start with self as issuer.
: ACCOUNT_XRP);
// Build a path of one element: the source.
spSeed.addElement (speEnd);
if (bForcedIssuer)
{
// Add forced source issuer to seed, via issuer's account.
STPathElement speIssuer (mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
spSeed.addElement (speEnd);
}
// Push the seed path to explore.
qspExplore.push (spSeed);
while (qspExplore.size ()) // Have paths to explore?
{
STPath spPath = qspExplore.front ();
qspExplore.pop (); // Pop the first path from the queue.
speEnd = spPath.mPath.back (); // Get the last node from the path.
if (!speEnd.mCurrencyID // Tail output is XRP.
&& !mDstAmount.getCurrency ()) // Which is dst currency.
{
// Done, cursor produces XRP and dest wants XRP.
// Remove implied source.
spPath.mPath.erase (spPath.mPath.begin ());
if (bForcedIssuer)
{
// Remove implied source issuer.
spPath.mPath.erase (spPath.mPath.begin ());
}
if (spPath.size ())
{
// There is an actual path element.
WriteLog (lsTRACE, Pathfinder) << "findPaths: adding path: " << spPath.getJson (0);
vspResults.push_back (spPath); // Potential result.
}
else
{
WriteLog (lsWARNING, Pathfinder) << "findPaths: empty path: XRP->XRP";
}
continue;
}
if (ShouldLog (lsTRACE, Pathfinder))
{
WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: spe: %s/%s: %s amt: %s")
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% RippleAddress::createHumanAccountID (speEnd.mIssuerID)
% RippleAddress::createHumanAccountID (mDstAccountID)
% RippleAddress::createHumanAccountID (mDstAmount.getIssuer ()));
WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID);
WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency ());
WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? issuer: "
<< RippleAddress::createHumanAccountID (speEnd.mIssuerID)
<< " / "
<< RippleAddress::createHumanAccountID (mDstAmount.getIssuer ())
<< " / "
<< RippleAddress::createHumanAccountID (mDstAccountID);
WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer ());
}
// YYY Allows going through self. Is this wanted?
if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
&& speEnd.mCurrencyID == mDstAmount.getCurrency () // With correct output currency.
&& ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|| mDstAmount.getIssuer () == mDstAccountID // Any issuer is good.
|| mDstAmount.getIssuer () == speEnd.mIssuerID)) // The desired issuer.
{
// Done, found a path to the destination.
// Cursor on the dest account with correct currency and issuer.
if (bDefaultPath (spPath))
{
WriteLog (lsTRACE, Pathfinder) << "findPaths: dropping: default path: " << spPath.getJson (0);
bFound = true;
}
else
{
// Remove implied nodes.
spPath.mPath.erase (spPath.mPath.begin ());
if (bForcedIssuer)
{
// Remove implied source issuer.
spPath.mPath.erase (spPath.mPath.begin ());
}
spPath.mPath.erase (spPath.mPath.begin () + spPath.mPath.size () - 1);
vspResults.push_back (spPath); // Potential result.
WriteLog (lsDEBUG, Pathfinder) << "findPaths: adding path: " << spPath.getJson (0);
}
continue;
}
bool bContinued = false; // True, if wasn't a dead end.
WriteLog (lsTRACE, Pathfinder) <<
boost::str (boost::format ("findPaths: cursor: %s - %s/%s")
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (speEnd.mIssuerID));
int length = getEffectiveLength (spPath.mPath);
if (length >= iMaxSteps)
{
// Path is at maximum size. Don't want to add more.
WriteLog (lsTRACE, Pathfinder)
<< boost::str (boost::format ("findPaths: dropping: path would exceed max steps"));
continue;
}
bool isLast = (length == (iMaxSteps - 1));
if (!speEnd.mCurrencyID)
{
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
std::vector<OrderBook::pointer> xrpBooks;
getApp().getOrderBookDB ().getBooksByTakerPays (ACCOUNT_XRP, CURRENCY_XRP, xrpBooks);
BOOST_FOREACH (OrderBook::ref book, xrpBooks)
{
// New end is an order book with the currency and issuer.
if (!spPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ()) &&
!matchesOrigin (book->getCurrencyOut (), book->getIssuerOut ()) &&
(!isLast ||
(book->getCurrencyOut () == mDstAmount.getCurrency () &&
book->getIssuerOut () == mDstAccountID)))
{
// Not a order book already in path.
STPath spNew (spPath);
STPathElement speBook (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ());
STPathElement speAccount (book->getIssuerOut (), book->getCurrencyOut (), book->getIssuerOut ());
spNew.mPath.push_back (speBook); // Add the order book.
spNew.mPath.push_back (speAccount); // Add the account and currency
WriteLog (lsDEBUG, Pathfinder)
<< boost::str (boost::format ("findPaths: XRP -> %s/%s")
// % STAmount::createHumanCurrency(book->getCurrencyOut())
// % RippleAddress::createHumanAccountID(book->getIssuerOut())
% STAmount::createHumanCurrency (speBook.mCurrencyID)
% RippleAddress::createHumanAccountID (speBook.mIssuerID));
qspExplore.push (spNew);
bContinued = true;
}
}
CondLog (!bContinued, lsDEBUG, Pathfinder)
<< boost::str (boost::format ("findPaths: XRP -> dead end"));
}
else
{
// Last element is for non-XRP, continue by adding ripple lines and order books.
// Create new paths for each outbound account not already in the path.
SLE::pointer sleEnd = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (speEnd.mAccountID));
CondLog (!sleEnd, lsDEBUG, Pathfinder)
<< boost::str (boost::format ("findPaths: tail: %s/%s : ")
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% RippleAddress::createHumanAccountID (speEnd.mIssuerID));
if (sleEnd)
{
// On a non-XRP account:
// True, the cursor requires the next node to be authorized.
bool bRequireAuth = isSetBit (sleEnd->getFieldU32 (sfFlags), lsfRequireAuth);
bool dstCurrency = speEnd.mCurrencyID == mDstAmount.getCurrency ();
AccountItems& rippleLines (mRLCache->getRippleLines (speEnd.mAccountID));
std::vector< std::pair<int, uint160> > candidates;
candidates.reserve (rippleLines.getItems ().size ());
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
const uint160& uPeerID = rspEntry->getAccountIDPeer ();
if (speEnd.mCurrencyID != rspEntry->getLimit ().getCurrency ())
{
// wrong currency
nothing ();
}
else if (spPath.hasSeen (uPeerID, speEnd.mCurrencyID, uPeerID) ||
((uPeerID == mSrcAccountID) && (uPeerID != mDstAccountID)))
{
// Peer is in path already. Ignore it to avoid a loop.
WriteLog (lsTRACE, Pathfinder) <<
boost::str (boost::format ("findPaths: SEEN: %s/%s -> %s/%s")
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (uPeerID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID));
}
else if (isLast && (!dstCurrency || (uPeerID != mDstAccountID)))
{
nothing ();
}
else if (!rspEntry->getBalance ().isPositive () // No IOUs to send.
&& (!rspEntry->getLimitPeer () // Peer does not extend credit.
|| -rspEntry->getBalance () >= rspEntry->getLimitPeer () // No credit left.
|| (bRequireAuth && !rspEntry->getAuth ()))) // Not authorized to hold credit.
{
// Path has no credit left. Ignore it.
WriteLog (lsTRACE, Pathfinder) <<
boost::str (boost::format ("findPaths: No credit: %s/%s -> %s/%s balance=%s limit=%s")
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (uPeerID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% rspEntry->getBalance ().getFullText ()
% rspEntry->getLimitPeer ().getFullText ()
);
}
else if (dstCurrency && (uPeerID == mDstAccountID))
{
// never skip the destination node
candidates.push_back (std::make_pair (1000000, uPeerID));
}
else
{
// save this candidate
int out = getPathsOut (speEnd.mCurrencyID, uPeerID, dstCurrency, mDstAccountID);
if (out != 0)
candidates.push_back (std::make_pair (out, uPeerID));
else
WriteLog(lsTRACE, Pathfinder) << "findPaths: " << RippleAddress::createHumanAccountID(uPeerID) << " has no paths out";
}
}
if (!candidates.empty ())
{
std::sort (candidates.begin (), candidates.end (),
BIND_TYPE (candCmp, mLedger->getLedgerSeq (), P_1, P_2));
int count = candidates.size ();
if ((count > 10) && (speEnd.mAccountID != mSrcAccountID)) // try more paths from source
count = 10;
else if (count > 50)
count = 50;
std::vector< std::pair<int, uint160> >::iterator it = candidates.begin ();
while (count-- != 0)
{
STPath spNew (spPath);
STPathElement speNew (it->second, speEnd.mCurrencyID, it->second);
spNew.mPath.push_back (speNew);
qspExplore.push (spNew);
bContinued = true;
WriteLog (lsTRACE, Pathfinder) <<
boost::str (boost::format ("findPaths: push explore: %s/%s -> %s/%s")
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (speEnd.mAccountID)
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (it->second));
++it;
}
}
}
// XXX Flip argument order to norm. (currency, issuer)
std::vector<OrderBook::pointer> books;
getApp().getOrderBookDB ().getBooksByTakerPays (speEnd.mIssuerID, speEnd.mCurrencyID, books);
BOOST_FOREACH (OrderBook::ref book, books)
{
if (!spPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ()) &&
!matchesOrigin (book->getCurrencyOut (), book->getIssuerOut ()) &&
(!isLast ||
(book->getCurrencyOut () == mDstAmount.getCurrency () &&
book->getIssuerOut () == mDstAccountID)))
{
// A book we haven't seen before. Add it.
STPath spNew (spPath);
STPathElement speBook (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut (),
book->getCurrencyIn () != book->getCurrencyOut ());
spNew.mPath.push_back (speBook); // Add the order book.
if (!!book->getCurrencyOut ())
{
// For non-XRP out, don't end on the book, add the issuing account.
STPathElement speAccount (book->getIssuerOut (), book->getCurrencyOut (), book->getIssuerOut ());
spNew.mPath.push_back (speAccount); // Add the account and currency
}
qspExplore.push (spNew);
bContinued = true;
WriteLog (lsTRACE, Pathfinder) <<
boost::str (boost::format ("findPaths: push book: %s/%s -> %s/%s")
% STAmount::createHumanCurrency (speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID (speEnd.mIssuerID)
% STAmount::createHumanCurrency (book->getCurrencyOut ())
% RippleAddress::createHumanAccountID (book->getIssuerOut ()));
}
}
CondLog (!bContinued, lsTRACE, Pathfinder)
<< boost::str (boost::format ("findPaths: dropping: non-XRP -> dead end"));
}
}
unsigned int iLimit = std::min (iMaxPaths, (unsigned int) vspResults.size ());
// Only filter, sort, and limit if have non-default paths.
if (iLimit)
{
std::vector<path_LQ_t> vMap;
// Build map of quality to entry.
for (int i = vspResults.size (); i--;)
{
STAmount saMaxAmountAct;
STAmount saDstAmountAct;
std::vector<PathState::pointer> vpsExpanded;
STPathSet spsPaths;
STPath& spCurrent = vspResults[i];
spsPaths.addPath (spCurrent); // Just checking the current path.
TER terResult;
try
{
LedgerEntrySet lesSandbox (lesActive.duplicate ());
terResult = RippleCalc::rippleCalc (
lesSandbox,
saMaxAmountAct,
saDstAmountAct,
vpsExpanded,
mSrcAmount, // --> amount to send max.
mDstAmount, // --> amount to deliver.
mDstAccountID,
mSrcAccountID,
spsPaths,
true, // --> bPartialPayment: Allow, it might contribute.
false, // --> bLimitQuality: Assume normal transaction.
true, // --> bNoRippleDirect: Providing the only path.
true); // --> bStandAlone: Don't need to delete unfundeds.
}
catch (const std::exception& e)
{
WriteLog (lsINFO, Pathfinder) << "findPaths: Caught throw: " << e.what ();
terResult = tefEXCEPTION;
}
if (tesSUCCESS == terResult)
{
uint64 uQuality = STAmount::getRate (saDstAmountAct, saMaxAmountAct);
WriteLog (lsDEBUG, Pathfinder)
<< boost::str (boost::format ("findPaths: quality: %d: %s")
% uQuality
% spCurrent.getJson (0));
vMap.push_back (path_LQ_t (uQuality, spCurrent.mPath.size (), saDstAmountAct, i));
}
else
{
WriteLog (lsDEBUG, Pathfinder)
<< boost::str (boost::format ("findPaths: dropping: %s: %s")
% transToken (terResult)
% spCurrent.getJson (0));
}
}
if (vMap.size ())
{
std::sort (vMap.begin (), vMap.end (), bQualityCmp); // Lower is better and should be first.
STAmount remaining = mDstAmount;
if (bFound)
{
// must subtract liquidity in default path from remaining amount
try
{
STAmount saMaxAmountAct, saDstAmountAct;
std::vector<PathState::pointer> vpsExpanded;
LedgerEntrySet lesSandbox (lesActive.duplicate ());
TER result = RippleCalc::rippleCalc (
lesSandbox,
saMaxAmountAct,
saDstAmountAct,
vpsExpanded,
mSrcAmount,
mDstAmount,
mDstAccountID,
mSrcAccountID,
STPathSet (),
true, // allow partial payment
false,
false, // don't suppress default paths, that's the point
true);
if (tesSUCCESS == result)
{
WriteLog (lsDEBUG, Pathfinder) << "Default path contributes: " << saDstAmountAct;
remaining -= saDstAmountAct;
}
else
{
WriteLog (lsDEBUG, Pathfinder) << "Default path fails: " << transToken (result);
}
}
catch (...)
{
WriteLog (lsDEBUG, Pathfinder) << "Default path causes exception";
}
}
for (int i = 0, iPathsLeft = iMaxPaths; (iPathsLeft > 0) && (i < vMap.size ()); ++i)
{
path_LQ_t& lqt = vMap[i];
if ((iPathsLeft != 1) || (lqt.get<2> () >= remaining))
{
// last path must fill
--iPathsLeft;
remaining -= lqt.get<2> ();
spsDst.addPath (vspResults[lqt.get<3> ()]);
}
else
WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << vspResults[lqt.get<3> ()].getJson (0);
}
if (remaining.isPositive ())
{
bFound = false;
WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount;
}
else
bFound = true;
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: %s") % spsDst.getJson (0));
}
else
{
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: non-defaults filtered away"));
}
}
WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< bFound=%d") % bFound);
return bFound;
}
boost::unordered_set<uint160> usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
bool includeXRP)
{
boost::unordered_set<uint160> usCurrencies;
// YYY Only bother if they are above reserve
if (includeXRP)
usCurrencies.insert (uint160 (CURRENCY_XRP));
// List of ripple lines.
AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ()));
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
const STAmount& saBalance = rspEntry->getBalance ();
// Filter out non
if (saBalance.isPositive () // Have IOUs to send.
|| (rspEntry->getLimitPeer () // Peer extends credit.
&& ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left.
{
usCurrencies.insert (saBalance.getCurrency ());
}
}
usCurrencies.erase (CURRENCY_BAD);
return usCurrencies;
}
boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
bool includeXRP)
{
boost::unordered_set<uint160> usCurrencies;
if (includeXRP)
usCurrencies.insert (uint160 (CURRENCY_XRP)); // Even if account doesn't exist
// List of ripple lines.
AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ()));
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
const STAmount& saBalance = rspEntry->getBalance ();
if (saBalance < rspEntry->getLimit ()) // Can take more
usCurrencies.insert (saBalance.getCurrency ());
}
usCurrencies.erase (CURRENCY_BAD);
return usCurrencies;
}
bool Pathfinder::matchesOrigin (const uint160& currency, const uint160& issuer)
{
return (currency == mSrcCurrencyID) && (issuer == mSrcIssuerID);
}
int Pathfinder::getPathsOut (const uint160& currencyID, const uint160& accountID,
bool isDstCurrency, const uint160& dstAccount)
{
#ifdef C11X
std::pair<const uint160&, const uint160&> accountCurrency (currencyID, accountID);
#else
std::pair<uint160, uint160> accountCurrency (currencyID, accountID);
#endif
boost::unordered_map<std::pair<uint160, uint160>, int>::iterator it = mPOMap.find (accountCurrency);
if (it != mPOMap.end ())
return it->second;
int aFlags = mLedger->getSLEi(Ledger::getAccountRootIndex(accountID))->getFieldU32(sfFlags);
bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0;
int count = 0;
AccountItems& rippleLines (mRLCache->getRippleLines (accountID));
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
if (currencyID != rspEntry->getLimit ().getCurrency ())
nothing ();
else if (!rspEntry->getBalance ().isPositive () &&
(!rspEntry->getLimitPeer ()
|| -rspEntry->getBalance () >= rspEntry->getLimitPeer ()
|| (bAuthRequired && !rspEntry->getAuth ())))
nothing ();
else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ()))
count += 10000; // count a path to the destination extra
else
++count;
}
mPOMap[accountCurrency] = count;
return count;
}
// vim:ts=4

View File

@@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PATHFINDER_H
#define RIPPLE_PATHFINDER_H
// VFALCO TODO Remove this unused stuff?
#if 0
//
// This is a very simple implementation. This can be made way better.
// We are simply flooding from the start. And doing an exhaustive search of all paths under maxSearchSteps. An easy improvement would
be to flood from both directions.
//
class PathOption
{
public:
typedef boost::shared_ptr<PathOption> pointer;
typedef const boost::shared_ptr<PathOption>& ref;
STPath mPath;
bool mCorrectCurrency; // for the sorting
uint160 mCurrencyID; // what currency we currently have at the end of the path
uint160 mCurrentAccount; // what account is at the end of the path
int mTotalCost; // in send currency
STAmount mMinWidth; // in dest currency
float mQuality;
PathOption (uint160& srcAccount, uint160& srcCurrencyID, const uint160& dstCurrencyID);
PathOption (PathOption::pointer other);
};
#endif
/** Calculates payment paths.
The @ref RippleCalc determines the quality of the found paths.
@see RippleCalc
*/
class Pathfinder
{
public:
Pathfinder (RippleLineCache::ref cache,
const RippleAddress& srcAccountID, const RippleAddress& dstAccountID,
const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount, bool& bValid);
bool findPaths (const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst);
bool bDefaultPath (const STPath& spPath);
private:
// void addOptions(PathOption::pointer tail);
// returns true if any building paths are now complete?
bool checkComplete (STPathSet& retPathSet);
// void addPathOption(PathOption::pointer pathOption);
bool matchesOrigin (const uint160& currency, const uint160& issuer);
int getPathsOut (const uint160& currency, const uint160& accountID,
bool isDestCurrency, const uint160& dest);
private:
uint160 mSrcAccountID;
uint160 mDstAccountID;
STAmount mDstAmount;
uint160 mSrcCurrencyID;
uint160 mSrcIssuerID;
STAmount mSrcAmount;
Ledger::pointer mLedger;
PathState::pointer mPsDefault;
LoadEvent::pointer m_loadEvent;
RippleLineCache::pointer mRLCache;
boost::unordered_map<uint160, AccountItems::pointer> mRLMap;
boost::unordered_map<std::pair<uint160, uint160>, int> mPOMap;
// std::list<PathOption::pointer> mBuildingPaths;
// std::list<PathOption::pointer> mCompletePaths;
};
boost::unordered_set<uint160> usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
bool includeXRP);
boost::unordered_set<uint160> usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger,
bool includeXRP);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_RIPPLECALC_H
#define RIPPLE_RIPPLECALC_H
/** Calculate the quality of a payment path.
The quality is a synonym for price. Specifically, the amount of
input required to produce a given output along a specified path.
*/
// VFALCO TODO What's the difference between a RippleState versus PathState?
//
class RippleCalc
{
public:
// First time working in reverse a funding source was mentioned. Source may only be used there.
curIssuerNode mumSource; // Map of currency, issuer to node index.
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
void pathNext (PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAdvance (const unsigned int uNode, PathState& psCur, const bool bMultiQuality, const bool bReverse);
TER calcNodeDeliverRev (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
STAmount& saOutAct);
TER calcNodeDeliverFwd (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
STAmount& saInAct,
STAmount& saInFees);
void calcNodeRipple (const uint32 uQualityIn, const uint32 uQualityOut,
const STAmount& saPrvReq, const STAmount& saCurReq,
STAmount& saPrvAct, STAmount& saCurAct,
uint64& uRateMax);
RippleCalc (LedgerEntrySet& lesNodes, const bool bOpenLedger)
: lesActive (lesNodes), mOpenLedger (bOpenLedger)
{
;
}
static TER rippleCalc (
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
const bool bStandAlone, // --> True, not to affect accounts.
const bool bOpenLedger = true // --> What kind of errors to return.
);
static void setCanonical (STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded, bool bKeepDefault);
protected:
LedgerEntrySet& lesActive;
bool mOpenLedger;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,18 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountItems& RippleLineCache::getRippleLines (const uint160& accountID)
{
boost::mutex::scoped_lock sl (mLock);
boost::unordered_map <uint160, AccountItems::pointer>::iterator it = mRLMap.find (accountID);
if (it == mRLMap.end ())
it = mRLMap.insert (std::make_pair (accountID, boost::make_shared<AccountItems>
(boost::cref (accountID), boost::cref (mLedger), AccountItem::pointer (new RippleState ())))).first;
return *it->second;
}

View File

@@ -0,0 +1,37 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_RIPPLELINECACHE_H
#define RIPPLE_RIPPLELINECACHE_H
// Used by Pathfinder
class RippleLineCache
{
public:
typedef boost::shared_ptr <RippleLineCache> pointer;
typedef pointer const& ref;
explicit RippleLineCache (Ledger::ref l)
: mLedger (l)
{
}
Ledger::ref getLedger () // VFALCO TODO const?
{
return mLedger;
}
AccountItems& getRippleLines (const uint160& accountID);
private:
boost::mutex mLock;
Ledger::pointer mLedger;
boost::unordered_map <uint160, AccountItems::pointer> mRLMap;
};
#endif

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
AccountItem::pointer RippleState::makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry)
{
if (!ledgerEntry || ledgerEntry->getType () != ltRIPPLE_STATE)
return AccountItem::pointer ();
RippleState* rs = new RippleState (ledgerEntry);
rs->setViewAccount (accountID);
return AccountItem::pointer (rs);
}
RippleState::RippleState (SerializedLedgerEntry::ref ledgerEntry) : AccountItem (ledgerEntry),
mValid (false),
mViewLowest (true),
mLowLimit (ledgerEntry->getFieldAmount (sfLowLimit)),
mHighLimit (ledgerEntry->getFieldAmount (sfHighLimit)),
mLowID (mLowLimit.getIssuer ()),
mHighID (mHighLimit.getIssuer ()),
mBalance (ledgerEntry->getFieldAmount (sfBalance))
{
mFlags = mLedgerEntry->getFieldU32 (sfFlags);
mLowQualityIn = mLedgerEntry->getFieldU32 (sfLowQualityIn);
mLowQualityOut = mLedgerEntry->getFieldU32 (sfLowQualityOut);
mHighQualityIn = mLedgerEntry->getFieldU32 (sfHighQualityIn);
mHighQualityOut = mLedgerEntry->getFieldU32 (sfHighQualityOut);
mValid = true;
}
void RippleState::setViewAccount (const uint160& accountID)
{
bool bViewLowestNew = mLowID == accountID;
if (bViewLowestNew != mViewLowest)
{
mViewLowest = bViewLowestNew;
mBalance.negate ();
}
}
Json::Value RippleState::getJson (int)
{
Json::Value ret (Json::objectValue);
ret["low_id"] = mLowID.GetHex ();
ret["high_id"] = mHighID.GetHex ();
return ret;
}
// vim:ts=4

View File

@@ -0,0 +1,122 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_RIPPLESTATE_H
#define RIPPLE_RIPPLESTATE_H
//
// A ripple line's state.
// - Isolate ledger entry format.
//
class RippleState : public AccountItem
{
public:
typedef boost::shared_ptr <RippleState> pointer;
public:
RippleState () { }
virtual ~RippleState () { }
AccountItem::pointer makeItem (const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry);
LedgerEntryType getType ()
{
return ltRIPPLE_STATE;
}
void setViewAccount (const uint160& accountID);
const uint160& getAccountID () const
{
return mViewLowest ? mLowID : mHighID;
}
const uint160& getAccountIDPeer () const
{
return !mViewLowest ? mLowID : mHighID;
}
// True, Provided auth to peer.
bool getAuth () const
{
return isSetBit (mFlags, mViewLowest ? lsfLowAuth : lsfHighAuth);
}
bool getAuthPeer () const
{
return isSetBit (mFlags, !mViewLowest ? lsfLowAuth : lsfHighAuth);
}
const STAmount& getBalance () const
{
return mBalance;
}
const STAmount& getLimit () const
{
return mViewLowest ? mLowLimit : mHighLimit;
}
const STAmount& getLimitPeer () const
{
return !mViewLowest ? mLowLimit : mHighLimit;
}
uint32 getQualityIn () const
{
return ((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn));
}
uint32 getQualityOut () const
{
return ((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut));
}
SerializedLedgerEntry::pointer getSLE ()
{
return mLedgerEntry;
}
const SerializedLedgerEntry& peekSLE () const
{
return *mLedgerEntry;
}
SerializedLedgerEntry& peekSLE ()
{
return *mLedgerEntry;
}
Json::Value getJson (int);
Blob getRaw () const;
private:
explicit RippleState (SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger
private:
bool mValid;
bool mViewLowest;
uint32 mFlags;
STAmount mLowLimit;
STAmount mHighLimit;
uint160 mLowID;
uint160 mHighID;
uint64 mLowQualityIn;
uint64 mLowQualityOut;
uint64 mHighQualityIn;
uint64 mHighQualityOut;
STAmount mBalance;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,86 @@
#ifndef RIPPLE_PEER_H
#define RIPPLE_PEER_H
typedef std::pair <std::string, int> ipPort;
class Peer : public boost::enable_shared_from_this <Peer>
{
public:
typedef boost::shared_ptr<Peer> pointer;
typedef const boost::shared_ptr<Peer>& ref;
static int const psbGotHello = 0;
static int const psbSentHello = 1;
static int const psbInMap = 2;
static int const psbTrusted = 3;
static int const psbNoLedgers = 4;
static int const psbNoTransactions = 5;
static int const psbDownLevel = 6;
public:
static pointer New (boost::asio::io_service& io_service,
boost::asio::ssl::context& ctx,
uint64 id,
bool inbound);
// VFALCO: TODO see if this and below can be private
virtual void handleConnect (const boost::system::error_code& error,
boost::asio::ip::tcp::resolver::iterator it) = 0;
virtual std::string& getIP () = 0;
virtual std::string getDisplayName () = 0;
virtual int getPort () = 0;
virtual void setIpPort (const std::string& strIP, int iPort) = 0;
virtual boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::lowest_layer_type& getSocket () = 0;
virtual void connect (const std::string& strIp, int iPort) = 0;
virtual void connected (const boost::system::error_code& error) = 0;
virtual void detach (const char*, bool onIOStrand) = 0;
//virtual bool samePeer (Peer::ref p) = 0;
//virtual bool samePeer (const Peer& p) = 0;
virtual void sendPacket (const PackedMessage::pointer& packet, bool onStrand) = 0;
virtual void sendGetPeers () = 0;
virtual void punishPeer (LoadType) = 0;
// VFALCO: NOTE, what's with this odd parameter passing? Why the static member?
static void punishPeer (const boost::weak_ptr<Peer>&, LoadType);
virtual Json::Value getJson () = 0;
virtual bool isConnected () const = 0;
virtual bool isInCluster () const = 0;
virtual bool isInbound () const = 0;
virtual bool isOutbound () const = 0;
virtual const uint256& getClosedLedgerHash () const = 0;
virtual bool hasLedger (const uint256& hash, uint32 seq) const = 0;
virtual bool hasTxSet (const uint256& hash) const = 0;
virtual uint64 getPeerId () const = 0;
virtual const RippleAddress& getNodePublic () const = 0;
virtual void cycleStatus () = 0;
virtual bool hasProto (int version) = 0;
virtual bool hasRange (uint32 uMin, uint32 uMax) = 0;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (PeerDoor)
class PeerDoorImp : public PeerDoor, LeakChecked <PeerDoorImp>
{
public:
PeerDoorImp (std::string const& ip,
int port,
std::string const& sslCiphers,
boost::asio::io_service& io_service)
: mAcceptor (
io_service,
boost::asio::ip::tcp::endpoint (boost::asio::ip::address ().from_string (ip.empty () ? "0.0.0.0" : ip),
port))
, mCtx (boost::asio::ssl::context::sslv23)
, mDelayTimer (io_service)
{
mCtx.set_options (
boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
SSL_CTX_set_tmp_dh_callback (mCtx.native_handle (), handleTmpDh);
if (SSL_CTX_set_cipher_list (mCtx.native_handle (), sslCiphers.c_str ()) != 1)
std::runtime_error ("Error setting cipher list (no valid ciphers).");
if (! ip.empty () && port != 0)
{
Log (lsINFO) << "Peer port: " << ip << " " << port;
startListening ();
}
}
//--------------------------------------------------------------------------
boost::asio::ssl::context& getSSLContext ()
{
return mCtx;
}
//--------------------------------------------------------------------------
void startListening ()
{
Peer::pointer new_connection = Peer::New (
mAcceptor.get_io_service (),
mCtx,
getApp().getPeers ().assignPeerId (),
true);
mAcceptor.async_accept (new_connection->getSocket (),
boost::bind (&PeerDoorImp::handleConnect, this, new_connection,
boost::asio::placeholders::error));
}
//--------------------------------------------------------------------------
void handleConnect (Peer::pointer new_connection,
const boost::system::error_code& error)
{
bool delay = false;
if (!error)
{
new_connection->connected (error);
}
else
{
if (error == boost::system::errc::too_many_files_open)
delay = true;
WriteLog (lsERROR, PeerDoor) << error;
}
if (delay)
{
mDelayTimer.expires_from_now (boost::posix_time::milliseconds (500));
mDelayTimer.async_wait (boost::bind (&PeerDoorImp::startListening, this));
}
else
{
startListening ();
}
}
private:
boost::asio::ip::tcp::acceptor mAcceptor;
boost::asio::ssl::context mCtx;
boost::asio::deadline_timer mDelayTimer;
};
//------------------------------------------------------------------------------
PeerDoor* PeerDoor::New (
std::string const& ip,
int port,
std::string const& sslCiphers,
boost::asio::io_service& io_service)
{
return new PeerDoorImp (ip, port, sslCiphers, io_service);
}

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PEERDOOR_H_INCLUDED
#define RIPPLE_PEERDOOR_H_INCLUDED
/** Handles incoming connections from peers.
*/
class PeerDoor : LeakChecked <PeerDoor>
{
public:
virtual ~PeerDoor () { }
static PeerDoor* New (
std::string const& ip,
int port,
std::string const& sslCiphers,
boost::asio::io_service& io_service);
virtual boost::asio::ssl::context& getSSLContext () = 0;
};
#endif

View File

@@ -0,0 +1,58 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_CLUSTERNODESTATUS_H_INCLUDED
#define RIPPLE_CLUSTERNODESTATUS_H_INCLUDED
class ClusterNodeStatus
{
public:
ClusterNodeStatus() : mLoadFee(0), mReportTime(0)
{ ; }
explicit ClusterNodeStatus(std::string const& name) : mNodeName(name), mLoadFee(0), mReportTime(0)
{ ; }
ClusterNodeStatus(const std::string& name, uint32 fee, uint32 rtime) :
mNodeName(name),
mLoadFee(fee),
mReportTime(rtime)
{ ; }
std::string const& getName()
{
return mNodeName;
}
uint32 getLoadFee()
{
return mLoadFee;
}
uint32 getReportTime()
{
return mReportTime;
}
bool update(ClusterNodeStatus const& status)
{
if (status.mReportTime <= mReportTime)
return false;
mLoadFee = status.mLoadFee;
mReportTime = status.mReportTime;
if (mNodeName.empty() || !status.mNodeName.empty())
mNodeName = status.mNodeName;
return true;
}
private:
std::string mNodeName;
uint32 mLoadFee;
uint32 mReportTime;
};
#endif

View File

@@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_IPEERS_H_INCLUDED
#define RIPPLE_IPEERS_H_INCLUDED
/** Manages the set of connected peers.
*/
class IPeers
{
public:
static IPeers* New (boost::asio::io_service& io_service);
virtual ~IPeers () { }
// Begin enforcing connection policy.
virtual void start () = 0;
// Send message to network.
virtual int relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg) = 0;
virtual int relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg) = 0;
virtual void relayMessageTo (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg) = 0;
virtual void relayMessageBut (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg) = 0;
// Manual connection request.
// Queue for immediate scanning.
virtual void connectTo (const std::string& strIp, int iPort) = 0;
//
// Peer connectivity notification.
//
virtual bool getTopNAddrs (int n, std::vector<std::string>& addrs) = 0;
virtual bool savePeer (const std::string& strIp, int iPort, char code) = 0;
// We know peers node public key.
// <-- bool: false=reject
virtual bool peerConnected (Peer::ref peer, const RippleAddress& naPeer, const std::string& strIP, int iPort) = 0;
// No longer connected.
virtual void peerDisconnected (Peer::ref peer, const RippleAddress& naPeer) = 0;
// As client accepted.
virtual void peerVerified (Peer::ref peer) = 0;
// As client failed connect and be accepted.
virtual void peerClosed (Peer::ref peer, const std::string& strIp, int iPort) = 0;
virtual int getPeerCount () = 0;
virtual Json::Value getPeersJson () = 0;
virtual std::vector<Peer::pointer> getPeerVector () = 0;
// Peer 64-bit ID function
virtual uint64 assignPeerId () = 0;
virtual Peer::pointer getPeerById (const uint64& id) = 0;
virtual bool hasPeer (const uint64& id) = 0;
//
// Scanning
//
virtual void scanRefresh () = 0;
//
// Connection policy
//
virtual void policyLowWater () = 0;
virtual void policyEnforce () = 0; // VFALCO This and others can be made private
// configured connections
virtual void makeConfigured () = 0;
};
// VFALCO TODO Put this in some group of utilities
extern void splitIpPort (const std::string& strIpPort, std::string& strIp, int& iPort);
#endif
// vim:ts=4

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PEER_H_INCLUDED
#define RIPPLE_PEER_H_INCLUDED
// VFALCO TODO Couldn't this be a struct?
typedef std::pair <std::string, int> IPAndPortNumber;
/** Represents a peer connection in the overlay.
*/
class Peer
: public boost::enable_shared_from_this <Peer>
, LeakChecked <Peer>
{
public:
typedef boost::shared_ptr <Peer> pointer;
typedef pointer const& ref;
public:
static pointer New (boost::asio::io_service& io_service,
boost::asio::ssl::context& ctx,
uint64 id,
bool inbound);
// VFALCO TODO see if this and below can be private
virtual void handleConnect (const boost::system::error_code& error,
boost::asio::ip::tcp::resolver::iterator it) = 0;
virtual std::string const& getIP () = 0;
virtual std::string getDisplayName () = 0;
virtual int getPort () = 0;
virtual void setIpPort (const std::string& strIP, int iPort) = 0;
virtual boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::lowest_layer_type& getSocket () = 0;
virtual void connect (const std::string& strIp, int iPort) = 0;
virtual void connected (const boost::system::error_code& error) = 0;
virtual void detach (const char*, bool onIOStrand) = 0;
virtual void sendPacket (const PackedMessage::pointer& packet, bool onStrand) = 0;
virtual void sendGetPeers () = 0;
virtual void applyLoadCharge (LoadType) = 0;
// VFALCO NOTE what's with this odd parameter passing? Why the static member?
//
/** Adjust this peer's load balance based on the type of load imposed.
@note Formerly named punishPeer
*/
static void applyLoadCharge (boost::weak_ptr <Peer>& peerTOCharge, LoadType loadThatWasImposed);
virtual Json::Value getJson () = 0;
virtual bool isConnected () const = 0;
virtual bool isInCluster () const = 0;
virtual bool isInbound () const = 0;
virtual bool isOutbound () const = 0;
virtual uint256 const& getClosedLedgerHash () const = 0;
virtual bool hasLedger (uint256 const& hash, uint32 seq) const = 0;
virtual bool hasTxSet (uint256 const& hash) const = 0;
virtual uint64 getPeerId () const = 0;
virtual const RippleAddress& getNodePublic () const = 0;
virtual void cycleStatus () = 0;
virtual bool hasProto (int version) = 0;
virtual bool hasRange (uint32 uMin, uint32 uMax) = 0;
};
#endif

View File

@@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class InboundLedger;
PeerSet::PeerSet (uint256 const& hash, int interval, bool txnData)
: mHash (hash)
, mTimerInterval (interval)
, mTimeouts (0)
, mComplete (false)
, mFailed (false)
, mProgress (true)
, mAggressive (false)
, mTxnData (txnData)
, mTimer (getApp().getIOService ())
{
mLastAction = UptimeTimer::getInstance ().getElapsedSeconds ();
assert ((mTimerInterval > 10) && (mTimerInterval < 30000));
}
void PeerSet::peerHas (Peer::ref ptr)
{
boost::recursive_mutex::scoped_lock sl (mLock);
if (!mPeers.insert (std::make_pair (ptr->getPeerId (), 0)).second)
return;
newPeer (ptr);
}
void PeerSet::badPeer (Peer::ref ptr)
{
boost::recursive_mutex::scoped_lock sl (mLock);
mPeers.erase (ptr->getPeerId ());
}
void PeerSet::setTimer ()
{
mTimer.expires_from_now (boost::posix_time::milliseconds (mTimerInterval));
mTimer.async_wait (boost::bind (&PeerSet::TimerEntry, pmDowncast (), boost::asio::placeholders::error));
}
void PeerSet::invokeOnTimer ()
{
boost::recursive_mutex::scoped_lock sl (mLock);
if (isDone ())
return;
if (!mProgress)
{
++mTimeouts;
WriteLog (lsWARNING, InboundLedger) << "Timeout(" << mTimeouts << ") pc=" << mPeers.size () << " acquiring " << mHash;
onTimer (false, sl);
}
else
{
mProgress = false;
onTimer (true, sl);
}
if (!isDone ())
setTimer ();
}
void PeerSet::TimerEntry (boost::weak_ptr<PeerSet> wptr, const boost::system::error_code& result)
{
if (result == boost::asio::error::operation_aborted)
return;
boost::shared_ptr<PeerSet> ptr = wptr.lock ();
if (ptr)
{
if (ptr->mTxnData)
{
getApp().getJobQueue ().addLimitJob (jtTXN_DATA, "timerEntry", 2,
BIND_TYPE (&PeerSet::TimerJobEntry, P_1, ptr));
}
else
{
int jc = getApp().getJobQueue ().getJobCountTotal (jtLEDGER_DATA);
if (jc > 4)
{
WriteLog (lsDEBUG, InboundLedger) << "Deferring PeerSet timer due to load";
ptr->setTimer ();
}
else
getApp().getJobQueue ().addLimitJob (jtLEDGER_DATA, "timerEntry", 2,
BIND_TYPE (&PeerSet::TimerJobEntry, P_1, ptr));
}
}
}
void PeerSet::TimerJobEntry (Job&, boost::shared_ptr<PeerSet> ptr)
{
ptr->invokeOnTimer ();
}
bool PeerSet::isActive ()
{
boost::recursive_mutex::scoped_lock sl (mLock);
return !isDone ();
}

View File

@@ -0,0 +1,112 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PEERSET_H
#define RIPPLE_PEERSET_H
/** A set of peers used to acquire data.
A peer set is used to acquire a ledger or a transaction set.
*/
class PeerSet : LeakChecked <PeerSet>
{
public:
uint256 const& getHash () const
{
return mHash;
}
bool isComplete () const
{
return mComplete;
}
bool isFailed () const
{
return mFailed;
}
int getTimeouts () const
{
return mTimeouts;
}
bool isActive ();
void progress ()
{
mProgress = true;
mAggressive = false;
}
bool isProgress ()
{
return mProgress;
}
void touch ()
{
mLastAction = UptimeTimer::getInstance ().getElapsedSeconds ();
}
int getLastAction ()
{
return mLastAction;
}
void peerHas (Peer::ref);
void badPeer (Peer::ref);
void setTimer ();
int takePeerSetFrom (const PeerSet& s);
int getPeerCount () const;
virtual bool isDone () const
{
return mComplete || mFailed;
}
private:
static void TimerEntry (boost::weak_ptr<PeerSet>, const boost::system::error_code& result);
static void TimerJobEntry (Job&, boost::shared_ptr<PeerSet>);
// VFALCO TODO try to make some of these private
protected:
PeerSet (uint256 const& hash, int interval, bool txnData);
virtual ~PeerSet () { }
virtual void newPeer (Peer::ref) = 0;
virtual void onTimer (bool progress, boost::recursive_mutex::scoped_lock&) = 0;
virtual boost::weak_ptr<PeerSet> pmDowncast () = 0;
void setComplete ()
{
mComplete = true;
}
void setFailed ()
{
mFailed = true;
}
void invokeOnTimer ();
void sendRequest (const protocol::TMGetLedger& message);
void sendRequest (const protocol::TMGetLedger& message, Peer::ref peer);
protected:
uint256 mHash;
int mTimerInterval;
int mTimeouts;
bool mComplete;
bool mFailed;
bool mProgress;
bool mAggressive;
bool mTxnData;
int mLastAction;
boost::recursive_mutex mLock;
// VFALCO TODO move the responsibility for the timer to a higher level
boost::asio::deadline_timer mTimer;
// VFALCO TODO Verify that these are used in the way that the names suggest.
typedef uint64 PeerIdentifier;
typedef int ReceivedChunkCount;
boost::unordered_map <PeerIdentifier, ReceivedChunkCount> mPeers;
};
#endif

View File

@@ -0,0 +1,898 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class Peers
: public IPeers
, LeakChecked <Peers>
{
public:
enum
{
/** Frequency of policy enforcement.
*/
policyIntervalSeconds = 5
};
explicit Peers (boost::asio::io_service& io_service)
: mLastPeer (0)
, mPhase (0)
, mScanTimer (io_service)
, mPolicyTimer (io_service)
{
}
// Begin enforcing connection policy.
void start ();
// Send message to network.
int relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg);
int relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg);
void relayMessageTo (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg);
void relayMessageBut (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg);
// Manual connection request.
// Queue for immediate scanning.
void connectTo (const std::string& strIp, int iPort);
//
// Peer connectivity notification.
//
bool getTopNAddrs (int n, std::vector<std::string>& addrs);
bool savePeer (const std::string& strIp, int iPort, char code);
// We know peers node public key.
// <-- bool: false=reject
bool peerConnected (Peer::ref peer, const RippleAddress& naPeer, const std::string& strIP, int iPort);
// No longer connected.
void peerDisconnected (Peer::ref peer, const RippleAddress& naPeer);
// As client accepted.
void peerVerified (Peer::ref peer);
// As client failed connect and be accepted.
void peerClosed (Peer::ref peer, const std::string& strIp, int iPort);
int getPeerCount ();
Json::Value getPeersJson ();
std::vector<Peer::pointer> getPeerVector ();
// Peer 64-bit ID function
uint64 assignPeerId ();
Peer::pointer getPeerById (const uint64& id);
bool hasPeer (const uint64& id);
//
// Scanning
//
void scanRefresh ();
//
// Connection policy
//
void policyLowWater ();
void policyEnforce ();
// configured connections
void makeConfigured ();
private:
boost::recursive_mutex mPeerLock;
uint64 mLastPeer;
int mPhase;
typedef std::pair<RippleAddress, Peer::pointer> naPeer;
typedef std::pair<IPAndPortNumber, Peer::pointer> pipPeer;
typedef std::map<IPAndPortNumber, Peer::pointer>::value_type vtPeer;
// Peers we are connecting with and non-thin peers we are connected to.
// Only peers we know the connection ip for are listed.
// We know the ip and port for:
// - All outbound connections
// - Some inbound connections (which we figured out).
boost::unordered_map<IPAndPortNumber, Peer::pointer> mIpMap;
// Non-thin peers which we are connected to.
// Peers we have the public key for.
typedef boost::unordered_map<RippleAddress, Peer::pointer>::value_type vtConMap;
boost::unordered_map<RippleAddress, Peer::pointer> mConnectedMap;
// Connections with have a 64-bit identifier
boost::unordered_map<uint64, Peer::pointer> mPeerIdMap;
Peer::pointer mScanning;
boost::asio::deadline_timer mScanTimer;
std::string mScanIp;
int mScanPort;
void scanHandler (const boost::system::error_code& ecResult);
boost::asio::deadline_timer mPolicyTimer;
void policyHandler (const boost::system::error_code& ecResult);
// Peers we are establishing a connection with as a client.
// int miConnectStarting;
bool peerAvailable (std::string& strIp, int& iPort);
bool peerScanSet (const std::string& strIp, int iPort);
Peer::pointer peerConnect (const std::string& strIp, int iPort);
};
void splitIpPort (const std::string& strIpPort, std::string& strIp, int& iPort)
{
std::vector<std::string> vIpPort;
boost::split (vIpPort, strIpPort, boost::is_any_of (" "));
strIp = vIpPort[0];
iPort = boost::lexical_cast<int> (vIpPort[1]);
}
void Peers::start ()
{
if (theConfig.RUN_STANDALONE)
return;
// Start running policy.
policyEnforce ();
// Start scanning.
scanRefresh ();
}
bool Peers::getTopNAddrs (int n, std::vector<std::string>& addrs)
{
// XXX Filter out other local addresses (like ipv6)
Database* db = getApp().getWalletDB ()->getDB ();
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
SQL_FOREACH (db, str (boost::format ("SELECT IpPort FROM PeerIps LIMIT %d") % n) )
{
std::string str;
db->getStr (0, str);
addrs.push_back (str);
}
return true;
}
bool Peers::savePeer (const std::string& strIp, int iPort, char code)
{
bool bNew = false;
Database* db = getApp().getWalletDB ()->getDB ();
std::string ipAndPort = sqlEscape (str (boost::format ("%s %d") % strIp % iPort));
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
std::string sql = str (boost::format ("SELECT COUNT(*) FROM PeerIps WHERE IpPort=%s;") % ipAndPort);
if (db->executeSQL (sql) && db->startIterRows ())
{
if (!db->getInt (0))
{
db->executeSQL (str (boost::format ("INSERT INTO PeerIps (IpPort,Score,Source) values (%s,0,'%c');") % ipAndPort % code));
bNew = true;
}
else
{
// We already had this peer.
// We will eventually verify its address if it is possible.
// YYY If it is vsInbound, then we might make verification immediate so we can connect back sooner if the connection
// is lost.
nothing ();
}
db->endIterRows ();
}
else
{
Log::out() << "Error saving Peer";
}
if (bNew)
scanRefresh ();
return bNew;
}
Peer::pointer Peers::getPeerById (const uint64& id)
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
const boost::unordered_map<uint64, Peer::pointer>::iterator& it = mPeerIdMap.find (id);
if (it == mPeerIdMap.end ())
return Peer::pointer ();
return it->second;
}
bool Peers::hasPeer (const uint64& id)
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
return mPeerIdMap.find (id) != mPeerIdMap.end ();
}
// An available peer is one we had no trouble connect to last time and that we are not currently knowingly connected or connecting
// too.
//
// <-- true, if a peer is available to connect to
bool Peers::peerAvailable (std::string& strIp, int& iPort)
{
Database* db = getApp().getWalletDB ()->getDB ();
std::vector<std::string> vstrIpPort;
// Convert mIpMap (list of open connections) to a vector of "<ip> <port>".
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
vstrIpPort.reserve (mIpMap.size ());
BOOST_FOREACH (const vtPeer & ipPeer, mIpMap)
{
const std::string& strIp = ipPeer.first.first;
int iPort = ipPeer.first.second;
vstrIpPort.push_back (sqlEscape (str (boost::format ("%s %d") % strIp % iPort)));
}
}
// Get the first IpPort entry which is not in vector and which is not scheduled for scanning.
std::string strIpPort;
{
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
if (db->executeSQL (str (boost::format ("SELECT IpPort FROM PeerIps WHERE ScanNext IS NULL AND IpPort NOT IN (%s) LIMIT 1;")
% strJoin (vstrIpPort.begin (), vstrIpPort.end (), ",")))
&& db->startIterRows ())
{
strIpPort = db->getStrBinary ("IpPort");
db->endIterRows ();
}
}
bool bAvailable = !strIpPort.empty ();
if (bAvailable)
splitIpPort (strIpPort, strIp, iPort);
return bAvailable;
}
// Make sure we have at least low water connections.
void Peers::policyLowWater ()
{
std::string strIp;
int iPort;
// Find an entry to connect to.
if (getPeerCount () > theConfig.PEER_CONNECT_LOW_WATER)
{
// Above low water mark, don't need more connections.
WriteLog (lsTRACE, Peers) << "Pool: Low water: sufficient connections: " << mConnectedMap.size () << "/" << theConfig.PEER_CONNECT_LOW_WATER;
nothing ();
}
#if 0
else if (miConnectStarting == theConfig.PEER_START_MAX)
{
// Too many connections starting to start another.
nothing ();
}
#endif
else if (!peerAvailable (strIp, iPort))
{
// No more connections available to start.
WriteLog (lsTRACE, Peers) << "Pool: Low water: no peers available.";
// XXX Might ask peers for more ips.
nothing ();
}
else
{
// Try to start connection.
WriteLog (lsTRACE, Peers) << "Pool: Low water: start connection.";
if (!peerConnect (strIp, iPort))
{
WriteLog (lsINFO, Peers) << "Pool: Low water: already connected.";
}
// Check if we need more.
policyLowWater ();
}
}
void Peers::policyEnforce ()
{
// Cancel any in progress timer.
(void) mPolicyTimer.cancel ();
// Enforce policies.
policyLowWater ();
if (((++mPhase) % 12) == 0)
{
WriteLog (lsTRACE, Peers) << "Making configured connections";
makeConfigured ();
}
// Schedule next enforcement.
mPolicyTimer.expires_at (boost::posix_time::second_clock::universal_time () + boost::posix_time::seconds (policyIntervalSeconds));
mPolicyTimer.async_wait (BIND_TYPE (&Peers::policyHandler, this, P_1));
}
void Peers::policyHandler (const boost::system::error_code& ecResult)
{
if (ecResult == boost::asio::error::operation_aborted)
{
nothing ();
}
else if (!ecResult)
{
policyEnforce ();
}
else
{
throw std::runtime_error ("Internal error: unexpected deadline error.");
}
}
// YYY: Should probably do this in the background.
// YYY: Might end up sending to disconnected peer?
int Peers::relayMessage (Peer* fromPeer, const PackedMessage::pointer& msg)
{
int sentTo = 0;
std::vector<Peer::pointer> peerVector = getPeerVector ();
BOOST_FOREACH (Peer::ref peer, peerVector)
{
if ((!fromPeer || ! (peer.get () == fromPeer)) && peer->isConnected ())
{
++sentTo;
peer->sendPacket (msg, false);
}
}
return sentTo;
}
int Peers::relayMessageCluster (Peer* fromPeer, const PackedMessage::pointer& msg)
{
int sentTo = 0;
std::vector<Peer::pointer> peerVector = getPeerVector ();
BOOST_FOREACH (Peer::ref peer, peerVector)
{
if ((!fromPeer || ! (peer.get () == fromPeer)) && peer->isConnected () && peer->isInCluster ())
{
++sentTo;
peer->sendPacket (msg, false);
}
}
return sentTo;
}
void Peers::relayMessageBut (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg)
{
// Relay message to all but the specified peers
std::vector<Peer::pointer> peerVector = getPeerVector ();
BOOST_FOREACH (Peer::ref peer, peerVector)
{
if (peer->isConnected () && (fromPeers.count (peer->getPeerId ()) == 0))
peer->sendPacket (msg, false);
}
}
void Peers::relayMessageTo (const std::set<uint64>& fromPeers, const PackedMessage::pointer& msg)
{
// Relay message to the specified peers
std::vector<Peer::pointer> peerVector = getPeerVector ();
BOOST_FOREACH (Peer::ref peer, peerVector)
{
if (peer->isConnected () && (fromPeers.count (peer->getPeerId ()) != 0))
peer->sendPacket (msg, false);
}
}
// Schedule a connection via scanning.
//
// Add or modify into PeerIps as a manual entry for immediate scanning.
// Requires sane IP and port.
void Peers::connectTo (const std::string& strIp, int iPort)
{
{
Database* db = getApp().getWalletDB ()->getDB ();
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
db->executeSQL (str (boost::format ("REPLACE INTO PeerIps (IpPort,Score,Source,ScanNext) values (%s,%d,'%c',0);")
% sqlEscape (str (boost::format ("%s %d") % strIp % iPort))
% getApp().getUNL ().iSourceScore (UniqueNodeList::vsManual)
% char (UniqueNodeList::vsManual)));
}
scanRefresh ();
}
// Start a connection, if not already known connected or connecting.
//
// <-- true, if already connected.
Peer::pointer Peers::peerConnect (const std::string& strIp, int iPort)
{
IPAndPortNumber pipPeer = make_pair (strIp, iPort);
Peer::pointer ppResult;
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
if (mIpMap.find (pipPeer) == mIpMap.end ())
{
ppResult = Peer::New (getApp().getIOService (),
getApp().getPeerDoor ().getSSLContext (),
++mLastPeer,
false);
mIpMap[pipPeer] = ppResult;
// ++miConnectStarting;
}
}
if (ppResult)
{
ppResult->connect (strIp, iPort);
WriteLog (lsDEBUG, Peers) << "Pool: Connecting: " << strIp << " " << iPort;
}
else
{
WriteLog (lsTRACE, Peers) << "Pool: Already connected: " << strIp << " " << iPort;
}
return ppResult;
}
// Returns information on verified peers.
Json::Value Peers::getPeersJson ()
{
Json::Value ret (Json::arrayValue);
std::vector<Peer::pointer> vppPeers = getPeerVector ();
BOOST_FOREACH (Peer::ref peer, vppPeers)
{
ret.append (peer->getJson ());
}
return ret;
}
int Peers::getPeerCount ()
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
return mConnectedMap.size ();
}
std::vector<Peer::pointer> Peers::getPeerVector ()
{
std::vector<Peer::pointer> ret;
boost::recursive_mutex::scoped_lock sl (mPeerLock);
ret.reserve (mConnectedMap.size ());
BOOST_FOREACH (const vtConMap & pair, mConnectedMap)
{
assert (!!pair.second);
ret.push_back (pair.second);
}
return ret;
}
uint64 Peers::assignPeerId ()
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
return ++mLastPeer;
}
// Now know peer's node public key. Determine if we want to stay connected.
// <-- bNew: false = redundant
bool Peers::peerConnected (Peer::ref peer, const RippleAddress& naPeer,
const std::string& strIP, int iPort)
{
bool bNew = false;
assert (!!peer);
if (naPeer == getApp().getLocalCredentials ().getNodePublic ())
{
WriteLog (lsINFO, Peers) << "Pool: Connected: self: " << addressToString (peer.get()) << ": " << naPeer.humanNodePublic () << " " << strIP << " " << iPort;
}
else
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
const boost::unordered_map<RippleAddress, Peer::pointer>::iterator& itCm = mConnectedMap.find (naPeer);
if (itCm == mConnectedMap.end ())
{
// New connection.
//WriteLog (lsINFO, Peers) << "Pool: Connected: new: " << addressToString (peer.get()) << ": " << naPeer.humanNodePublic() << " " << strIP << " " << iPort;
mConnectedMap[naPeer] = peer;
bNew = true;
assert (peer->getPeerId () != 0);
mPeerIdMap.insert (std::make_pair (peer->getPeerId (), peer));
}
// Found in map, already connected.
else if (!strIP.empty ())
{
// Was an outbound connection, we know IP and port.
// Note in previous connection how to reconnect.
if (itCm->second->getIP ().empty ())
{
// Old peer did not know it's IP.
//WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: outbound: " << addressToString (peer.get()) << " discovered: " << addressToString(itCm->second) << ": " << strIP << " " << iPort;
itCm->second->setIpPort (strIP, iPort);
// Add old connection to identified connection list.
mIpMap[make_pair (strIP, iPort)] = itCm->second;
}
else
{
// Old peer knew its IP. Do nothing.
//WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: outbound: rediscovered: " << addressToString (peer.get()) << " " << strIP << " " << iPort;
nothing ();
}
}
else
{
//WriteLog (lsINFO, Peers) << "Pool: Connected: redundant: inbound: " << addressToString (peer.get()) << " " << strIP << " " << iPort;
nothing ();
}
}
return bNew;
}
// We maintain a map of public key to peer for connected and verified peers. Maintain it.
void Peers::peerDisconnected (Peer::ref peer, const RippleAddress& naPeer)
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
if (naPeer.isValid ())
{
const boost::unordered_map<RippleAddress, Peer::pointer>::iterator& itCm = mConnectedMap.find (naPeer);
if (itCm == mConnectedMap.end ())
{
// Did not find it. Not already connecting or connected.
WriteLog (lsWARNING, Peers) << "Pool: disconnected: Internal Error: mConnectedMap was inconsistent.";
// XXX Maybe bad error, considering we have racing connections, may not so bad.
}
else if (itCm->second != peer)
{
WriteLog (lsWARNING, Peers) << "Pool: disconected: non canonical entry";
nothing ();
}
else
{
// Found it. Delete it.
mConnectedMap.erase (itCm);
//WriteLog (lsINFO, Peers) << "Pool: disconnected: " << naPeer.humanNodePublic() << " " << peer->getIP() << " " << peer->getPort();
}
}
else
{
//WriteLog (lsINFO, Peers) << "Pool: disconnected: anonymous: " << peer->getIP() << " " << peer->getPort();
}
assert (peer->getPeerId () != 0);
mPeerIdMap.erase (peer->getPeerId ());
}
// Schedule for immediate scanning, if not already scheduled.
//
// <-- true, scanRefresh needed.
bool Peers::peerScanSet (const std::string& strIp, int iPort)
{
std::string strIpPort = str (boost::format ("%s %d") % strIp % iPort);
bool bScanDirty = false;
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
if (db->executeSQL (str (boost::format ("SELECT ScanNext FROM PeerIps WHERE IpPort=%s;")
% sqlEscape (strIpPort)))
&& db->startIterRows ())
{
if (db->getNull ("ScanNext"))
{
// Non-scanning connection terminated. Schedule for scanning.
int iInterval = theConfig.PEER_SCAN_INTERVAL_MIN;
boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time ();
boost::posix_time::ptime tpNext = tpNow + boost::posix_time::seconds (iInterval);
//WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: schedule create: %s %s (next %s, delay=%d)")
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
db->executeSQL (str (boost::format ("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;")
% iToSeconds (tpNext)
% iInterval
% sqlEscape (strIpPort)));
bScanDirty = true;
}
else
{
// Scan connection terminated, already scheduled for retry.
// boost::posix_time::ptime tpNow = boost::posix_time::second_clock::universal_time();
// boost::posix_time::ptime tpNext = ptFromSeconds(db->getInt("ScanNext"));
//WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: schedule exists: %s %s (next %s, delay=%d)")
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
}
db->endIterRows ();
}
else
{
//WriteLog (lsWARNING, Peers) << "Pool: Scan: peer wasn't in PeerIps: " << strIp << " " << iPort;
}
return bScanDirty;
}
// --> strIp: not empty
void Peers::peerClosed (Peer::ref peer, const std::string& strIp, int iPort)
{
IPAndPortNumber ipPeer = make_pair (strIp, iPort);
bool bScanRefresh = false;
// If the connection was our scan, we are no longer scanning.
if (mScanning && mScanning == peer)
{
//WriteLog (lsINFO, Peers) << "Pool: Scan: scan fail: " << strIp << " " << iPort;
mScanning.reset (); // No longer scanning.
bScanRefresh = true; // Look for more to scan.
}
// Determine if closed peer was redundant.
bool bRedundant = true;
{
boost::recursive_mutex::scoped_lock sl (mPeerLock);
const boost::unordered_map<IPAndPortNumber, Peer::pointer>::iterator& itIp = mIpMap.find (ipPeer);
if (itIp == mIpMap.end ())
{
// Did not find it. Not already connecting or connected.
WriteLog (lsWARNING, Peers) << "Pool: Closed: UNEXPECTED: " << addressToString (peer.get()) << ": " << strIp << " " << iPort;
// XXX Internal error.
}
else if (mIpMap[ipPeer] == peer)
{
// We were the identified connection.
//WriteLog (lsINFO, Peers) << "Pool: Closed: identified: " << addressToString (peer.get()) << ": " << strIp << " " << iPort;
// Delete our entry.
mIpMap.erase (itIp);
bRedundant = false;
}
else
{
// Found it. But, we were redundant.
//WriteLog (lsINFO, Peers) << "Pool: Closed: redundant: " << addressToString (peer.get()) << ": " << strIp << " " << iPort;
}
}
if (!bRedundant)
{
// If closed was not redundant schedule if not already scheduled.
bScanRefresh = peerScanSet (ipPeer.first, ipPeer.second) || bScanRefresh;
}
if (bScanRefresh)
scanRefresh ();
}
void Peers::peerVerified (Peer::ref peer)
{
if (mScanning && mScanning == peer)
{
// Scan completed successfully.
std::string strIp = peer->getIP ();
int iPort = peer->getPort ();
std::string strIpPort = str (boost::format ("%s %d") % strIp % iPort);
//WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: connected: %s %s %s (scanned)") % addressToString (peer.get()) % strIp % iPort);
if (peer->getNodePublic () == getApp().getLocalCredentials ().getNodePublic ())
{
// Talking to ourself. We will just back off. This lets us maybe advertise our outside address.
nothing (); // Do nothing, leave scheduled scanning.
}
else
{
// Talking with a different peer.
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
db->executeSQL (boost::str (boost::format ("UPDATE PeerIps SET ScanNext=NULL,ScanInterval=0 WHERE IpPort=%s;")
% sqlEscape (strIpPort)));
// XXX Check error.
}
mScanning.reset ();
scanRefresh (); // Continue scanning.
}
}
void Peers::scanHandler (const boost::system::error_code& ecResult)
{
if (ecResult == boost::asio::error::operation_aborted)
{
nothing ();
}
else if (!ecResult)
{
scanRefresh ();
}
else
{
throw std::runtime_error ("Internal error: unexpected deadline error.");
}
}
void Peers::makeConfigured ()
{
if (theConfig.RUN_STANDALONE)
return;
BOOST_FOREACH (const std::string & strPeer, theConfig.IPS)
{
std::string strIP;
int iPort;
if (parseIpPort (strPeer, strIP, iPort))
peerConnect (strIP, iPort);
}
}
// Scan ips as per db entries.
void Peers::scanRefresh ()
{
if (theConfig.RUN_STANDALONE)
{
nothing ();
}
else if (mScanning)
{
// Currently scanning, will scan again after completion.
WriteLog (lsTRACE, Peers) << "Pool: Scan: already scanning";
nothing ();
}
else
{
// Discover if there are entries that need scanning.
boost::posix_time::ptime tpNext;
boost::posix_time::ptime tpNow;
std::string strIpPort;
int iInterval;
{
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
if (db->executeSQL ("SELECT * FROM PeerIps INDEXED BY PeerScanIndex WHERE ScanNext NOT NULL ORDER BY ScanNext LIMIT 1;")
&& db->startIterRows ())
{
// Have an entry to scan.
int iNext = db->getInt ("ScanNext");
tpNext = ptFromSeconds (iNext);
tpNow = boost::posix_time::second_clock::universal_time ();
db->getStr ("IpPort", strIpPort);
iInterval = db->getInt ("ScanInterval");
db->endIterRows ();
}
else
{
// No entries to scan.
tpNow = boost::posix_time::ptime (boost::posix_time::not_a_date_time);
}
}
if (tpNow.is_not_a_date_time ())
{
//WriteLog (lsINFO, Peers) << "Pool: Scan: stop.";
(void) mScanTimer.cancel ();
}
else if (tpNext <= tpNow)
{
// Scan it.
splitIpPort (strIpPort, mScanIp, mScanPort);
(void) mScanTimer.cancel ();
iInterval = std::max (iInterval, theConfig.PEER_SCAN_INTERVAL_MIN);
tpNext = tpNow + boost::posix_time::seconds (iInterval);
//WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: Now: %s %s (next %s, delay=%d)")
// % mScanIp % mScanPort % tpNext % (tpNext-tpNow).total_seconds());
iInterval *= 2;
{
ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
Database* db = getApp().getWalletDB ()->getDB ();
db->executeSQL (boost::str (boost::format ("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;")
% iToSeconds (tpNext)
% iInterval
% sqlEscape (strIpPort)));
// XXX Check error.
}
mScanning = peerConnect (mScanIp, mScanPort);
if (!mScanning)
{
// Already connected. Try again.
scanRefresh ();
}
}
else
{
//WriteLog (lsINFO, Peers) << str(boost::format("Pool: Scan: Next: %s (next %s, delay=%d)")
// % strIpPort % tpNext % (tpNext-tpNow).total_seconds());
mScanTimer.expires_at (tpNext);
mScanTimer.async_wait (BIND_TYPE (&Peers::scanHandler, this, P_1));
}
}
}
IPeers* IPeers::New (boost::asio::io_service& io_service)
{
return new Peers (io_service);
}
#if 0
bool Peers::isMessageKnown (PackedMessage::pointer msg)
{
for (unsigned int n = 0; n < mBroadcastMessages.size (); n++)
{
if (msg == mBroadcastMessages[n].first) return (false);
}
return (false);
}
#endif
SETUP_LOG (Peers)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_UNIQUENODELIST_H_INCLUDED
#define RIPPLE_UNIQUENODELIST_H_INCLUDED
class UniqueNodeList
{
public:
enum ValidatorSource
{
vsConfig = 'C', // rippled.cfg
vsInbound = 'I',
vsManual = 'M',
vsReferral = 'R',
vsTold = 'T',
vsValidator = 'V', // validators.txt
vsWeb = 'W',
};
// VFALCO TODO rename this to use the right coding style
typedef long score;
public:
// VFALCO TODO make this not use boost::asio...
static UniqueNodeList* New ();
virtual ~UniqueNodeList () { }
// VFALCO TODO Roll this into the constructor so there is one less state.
virtual void start () = 0;
// VFALCO TODO rename all these, the "node" prefix is redundant (lol)
virtual void nodeAddPublic (const RippleAddress& naNodePublic, ValidatorSource vsWhy, const std::string& strComment) = 0;
virtual void nodeAddDomain (std::string strDomain, ValidatorSource vsWhy, const std::string& strComment = "") = 0;
virtual void nodeRemovePublic (const RippleAddress& naNodePublic) = 0;
virtual void nodeRemoveDomain (std::string strDomain) = 0;
virtual void nodeReset () = 0;
virtual void nodeScore () = 0;
virtual bool nodeInUNL (const RippleAddress& naNodePublic) = 0;
virtual bool nodeInCluster (const RippleAddress& naNodePublic) = 0;
virtual bool nodeInCluster (const RippleAddress& naNodePublic, std::string& name) = 0;
virtual bool nodeUpdate (const RippleAddress& naNodePublic, ClusterNodeStatus const& cnsStatus) = 0;
virtual std::map<RippleAddress, ClusterNodeStatus> getClusterStatus () = 0;
virtual uint32 getClusterFee () = 0;
virtual void addClusterStatus (Json::Value&) = 0;
virtual void nodeBootstrap () = 0;
virtual bool nodeLoad (boost::filesystem::path pConfig) = 0;
virtual void nodeNetwork () = 0;
virtual Json::Value getUnlJson () = 0;
virtual int iSourceScore (ValidatorSource vsWhy) = 0;
};
#endif
// vim:ts=4

View File

@@ -112,84 +112,84 @@ namespace ripple
#include "node/ripple_NullBackendFactory.h"
#include "node/ripple_SqliteBackendFactory.h"
#include "src/cpp/ripple/ripple_SHAMapItem.h"
#include "src/cpp/ripple/ripple_SHAMapNode.h"
#include "src/cpp/ripple/ripple_SHAMapTreeNode.h"
#include "src/cpp/ripple/ripple_SHAMapMissingNode.h"
#include "src/cpp/ripple/ripple_SHAMapSyncFilter.h"
#include "src/cpp/ripple/ripple_SHAMapAddNode.h"
#include "src/cpp/ripple/ripple_SHAMap.h"
#include "src/cpp/ripple/ripple_SerializedTransaction.h"
#include "src/cpp/ripple/ripple_SerializedLedger.h"
#include "src/cpp/ripple/TransactionMeta.h"
#include "src/cpp/ripple/Transaction.h"
#include "src/cpp/ripple/ripple_AccountState.h"
#include "src/cpp/ripple/ripple_NicknameState.h"
#include "shamap/ripple_SHAMapItem.h"
#include "shamap/ripple_SHAMapNode.h"
#include "shamap/ripple_SHAMapTreeNode.h"
#include "shamap/ripple_SHAMapMissingNode.h"
#include "shamap/ripple_SHAMapSyncFilter.h"
#include "shamap/ripple_SHAMapAddNode.h"
#include "shamap/ripple_SHAMap.h"
#include "misc/ripple_SerializedTransaction.h"
#include "misc/ripple_SerializedLedger.h"
#include "tx/TransactionMeta.h"
#include "tx/Transaction.h"
#include "misc/ripple_AccountState.h"
#include "misc/ripple_NicknameState.h"
#include "ledger/Ledger.h"
#include "ledger/SerializedValidation.h"
#include "src/cpp/ripple/ripple_ILoadManager.h"
#include "src/cpp/ripple/ripple_ProofOfWork.h"
#include "src/cpp/ripple/ripple_InfoSub.h"
#include "src/cpp/ripple/ripple_OrderBook.h"
#include "src/cpp/ripple/ripple_SHAMapSyncFilters.h"
#include "src/cpp/ripple/ripple_IFeatures.h"
#include "src/cpp/ripple/ripple_IFeeVote.h"
#include "src/cpp/ripple/ripple_IHashRouter.h"
#include "src/cpp/ripple/ripple_Peer.h" // VFALCO TODO Rename to IPeer
#include "src/cpp/ripple/ripple_IPeers.h"
#include "src/cpp/ripple/ripple_IProofOfWorkFactory.h"
#include "src/cpp/ripple/ripple_ClusterNodeStatus.h"
#include "src/cpp/ripple/ripple_UniqueNodeList.h"
#include "src/cpp/ripple/ripple_IValidations.h"
#include "src/cpp/ripple/ripple_PeerSet.h"
#include "main/ripple_ILoadManager.h"
#include "misc/ripple_ProofOfWork.h"
#include "misc/ripple_InfoSub.h"
#include "misc/ripple_OrderBook.h"
#include "shamap/ripple_SHAMapSyncFilters.h"
#include "misc/ripple_IFeatures.h"
#include "misc/ripple_IFeeVote.h"
#include "misc/ripple_IHashRouter.h"
#include "peers/ripple_Peer.h" // VFALCO TODO Rename to IPeer
#include "peers/ripple_IPeers.h"
#include "misc/ripple_IProofOfWorkFactory.h"
#include "peers/ripple_ClusterNodeStatus.h"
#include "peers/ripple_UniqueNodeList.h"
#include "misc/ripple_IValidations.h"
#include "peers/ripple_PeerSet.h"
#include "ledger/ripple_InboundLedger.h"
#include "ledger/ripple_InboundLedgers.h"
#include "src/cpp/ripple/ripple_AccountItem.h"
#include "src/cpp/ripple/ripple_AccountItems.h"
#include "misc/ripple_AccountItem.h"
#include "misc/ripple_AccountItems.h"
#include "ledger/ripple_AcceptedLedgerTx.h"
#include "ledger/ripple_AcceptedLedger.h"
#include "ledger/ripple_LedgerEntrySet.h"
#include "src/cpp/ripple/TransactionEngine.h"
#include "src/cpp/ripple/ripple_CanonicalTXSet.h"
#include "tx/TransactionEngine.h"
#include "misc/ripple_CanonicalTXSet.h"
#include "ledger/ripple_LedgerHistory.h"
#include "ledger/LedgerMaster.h"
#include "ledger/LedgerProposal.h"
#include "src/cpp/ripple/NetworkOPs.h"
#include "src/cpp/ripple/TransactionMaster.h"
#include "src/cpp/ripple/ripple_LocalCredentials.h"
#include "src/cpp/ripple/WSDoor.h"
#include "src/cpp/ripple/ripple_Application.h"
#include "src/cpp/ripple/RPCHandler.h"
#include "src/cpp/ripple/TransactionQueue.h"
#include "misc/NetworkOPs.h"
#include "tx/TransactionMaster.h"
#include "main/ripple_LocalCredentials.h"
#include "network/WSDoor.h"
#include "main/ripple_Application.h"
#include "rpc/RPCHandler.h"
#include "tx/TransactionQueue.h"
#include "ledger/OrderBookDB.h"
#include "src/cpp/ripple/CallRPC.h"
#include "src/cpp/ripple/Transactor.h"
#include "src/cpp/ripple/ChangeTransactor.h"
#include "src/cpp/ripple/ripple_TransactionAcquire.h"
#include "src/cpp/ripple/ripple_DisputedTx.h"
#include "src/cpp/ripple/ripple_LedgerConsensus.h"
#include "rpc/CallRPC.h"
#include "tx/Transactor.h"
#include "tx/ChangeTransactor.h"
#include "tx/ripple_TransactionAcquire.h"
#include "consensus/ripple_DisputedTx.h"
#include "consensus/ripple_LedgerConsensus.h"
#include "ledger/LedgerTiming.h"
#include "src/cpp/ripple/ripple_Offer.h"
#include "src/cpp/ripple/OfferCancelTransactor.h"
#include "src/cpp/ripple/OfferCreateTransactor.h"
#include "src/cpp/ripple/ripple_PathRequest.h"
#include "src/cpp/ripple/ParameterTable.h"
#include "src/cpp/ripple/ripple_RippleLineCache.h"
#include "src/cpp/ripple/ripple_PathState.h"
#include "src/cpp/ripple/ripple_RippleCalc.h"
#include "src/cpp/ripple/ripple_Pathfinder.h"
#include "src/cpp/ripple/PaymentTransactor.h"
#include "src/cpp/ripple/PeerDoor.h"
#include "src/cpp/ripple/RPC.h"
#include "src/cpp/ripple/RPCErr.h"
#include "src/cpp/ripple/RPCSub.h"
#include "src/cpp/ripple/RegularKeySetTransactor.h"
#include "src/cpp/ripple/ripple_RippleState.h"
#include "src/cpp/ripple/AccountSetTransactor.h"
#include "src/cpp/ripple/TrustSetTransactor.h"
#include "src/cpp/ripple/WSConnection.h"
#include "src/cpp/ripple/ripple_WSHandler.h"
#include "src/cpp/ripple/WalletAddTransactor.h"
#include "misc/ripple_Offer.h"
#include "tx/OfferCancelTransactor.h"
#include "tx/OfferCreateTransactor.h"
#include "paths/ripple_PathRequest.h"
#include "main/ParameterTable.h"
#include "paths/ripple_RippleLineCache.h"
#include "paths/ripple_PathState.h"
#include "paths/ripple_RippleCalc.h"
#include "paths/ripple_Pathfinder.h"
#include "tx/PaymentTransactor.h"
#include "peers/PeerDoor.h"
#include "rpc/RPC.h"
#include "rpc/RPCErr.h"
#include "rpc/RPCSub.h"
#include "tx/RegularKeySetTransactor.h"
#include "paths/ripple_RippleState.h"
#include "tx/AccountSetTransactor.h"
#include "tx/TrustSetTransactor.h"
#include "network/WSConnection.h"
#include "network/ripple_WSHandler.h"
#include "tx/WalletAddTransactor.h"
#include "contracts/ripple_ScriptData.h"
#include "contracts/ripple_Contract.h"
@@ -200,7 +200,7 @@ namespace ripple
#include "basics/ripple_BuildVersion.h" // private
#include "basics/ripple_RPCServerHandler.h"
#include "src/cpp/ripple/RPCDoor.h" // needs RPCServer
#include "rpc/RPCDoor.h" // needs RPCServer
}
@@ -257,13 +257,13 @@ static const uint64 tenTo17m1 = tenTo17 - 1;
#include "node/ripple_SqliteBackendFactory.cpp"
#include "ledger/Ledger.cpp"
#include "src/cpp/ripple/ripple_SHAMapDelta.cpp"
#include "src/cpp/ripple/ripple_SHAMapNode.cpp"
#include "src/cpp/ripple/ripple_SHAMapTreeNode.cpp"
#include "shamap/ripple_SHAMapDelta.cpp"
#include "shamap/ripple_SHAMapNode.cpp"
#include "shamap/ripple_SHAMapTreeNode.cpp"
#include "src/cpp/ripple/ripple_AccountItems.cpp"
#include "src/cpp/ripple/ripple_AccountState.cpp"
#include "src/cpp/ripple/ChangeTransactor.cpp"
#include "misc/ripple_AccountItems.cpp"
#include "misc/ripple_AccountState.cpp"
#include "tx/ChangeTransactor.cpp"
#include "contracts/ripple_Contract.cpp"
#include "contracts/ripple_Operation.cpp"
@@ -276,19 +276,19 @@ static const uint64 tenTo17m1 = tenTo17 - 1;
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 2
#include "src/cpp/ripple/RPCHandler.cpp"
#include "src/cpp/ripple/ripple_SHAMap.cpp" // Uses theApp
#include "src/cpp/ripple/ripple_SHAMapItem.cpp"
#include "src/cpp/ripple/ripple_SHAMapSync.cpp"
#include "src/cpp/ripple/ripple_SHAMapMissingNode.cpp"
#include "rpc/RPCHandler.cpp"
#include "shamap/ripple_SHAMap.cpp" // Uses theApp
#include "shamap/ripple_SHAMapItem.cpp"
#include "shamap/ripple_SHAMapSync.cpp"
#include "shamap/ripple_SHAMapMissingNode.cpp"
#include "src/cpp/ripple/ripple_AccountItem.cpp"
#include "src/cpp/ripple/AccountSetTransactor.cpp"
#include "src/cpp/ripple/ripple_CanonicalTXSet.cpp"
#include "misc/ripple_AccountItem.cpp"
#include "tx/AccountSetTransactor.cpp"
#include "misc/ripple_CanonicalTXSet.cpp"
#include "ledger/LedgerProposal.cpp"
#include "src/cpp/ripple/ripple_LoadManager.cpp"
#include "src/cpp/ripple/ripple_NicknameState.cpp"
#include "src/cpp/ripple/OfferCancelTransactor.cpp"
#include "main/ripple_LoadManager.cpp"
#include "misc/ripple_NicknameState.cpp"
#include "tx/OfferCancelTransactor.cpp"
#include "ledger/OrderBookDB.cpp"
#include "data/ripple_Database.cpp"
@@ -310,23 +310,23 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
return 512 == iKeyLength ? getApp().getLocalCredentials ().getDh512 () : getApp().getLocalCredentials ().getDh1024 ();
}
#include "src/cpp/ripple/ripple_RippleCalc.cpp"
#include "src/cpp/ripple/CallRPC.cpp"
#include "src/cpp/ripple/ripple_PathState.cpp"
#include "paths/ripple_RippleCalc.cpp"
#include "paths/ripple_PathState.cpp"
#include "rpc/CallRPC.cpp"
#include "src/cpp/ripple/ParameterTable.cpp"
#include "src/cpp/ripple/PeerDoor.cpp"
#include "src/cpp/ripple/ripple_RippleLineCache.cpp"
#include "src/cpp/ripple/rpc.cpp"
#include "src/cpp/ripple/RPCErr.cpp"
#include "src/cpp/ripple/RPCSub.cpp"
#include "main/ParameterTable.cpp"
#include "peers/PeerDoor.cpp"
#include "paths/ripple_RippleLineCache.cpp"
#include "rpc/rpc.cpp"
#include "rpc/RPCErr.cpp"
#include "rpc/RPCSub.cpp"
#include "ledger/SerializedValidation.cpp"
#include "src/cpp/ripple/Transaction.cpp"
#include "src/cpp/ripple/TransactionEngine.cpp"
#include "src/cpp/ripple/TransactionMeta.cpp"
#include "src/cpp/ripple/Transactor.cpp"
#include "src/cpp/ripple/WSConnection.cpp"
#include "src/cpp/ripple/WSDoor.cpp"
#include "tx/Transaction.cpp"
#include "tx/TransactionEngine.cpp"
#include "tx/TransactionMeta.cpp"
#include "tx/Transactor.cpp"
#include "network/WSConnection.cpp"
#include "network/WSDoor.cpp"
#endif
@@ -334,18 +334,18 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 4
#include "src/cpp/ripple/ripple_UniqueNodeList.cpp"
#include "peers/ripple_UniqueNodeList.cpp"
#include "ledger/ripple_InboundLedger.cpp"
#include "src/cpp/ripple/PaymentTransactor.cpp"
#include "src/cpp/ripple/RegularKeySetTransactor.cpp"
#include "src/cpp/ripple/ripple_RippleState.cpp"
#include "src/cpp/ripple/RPCDoor.cpp"
#include "src/cpp/ripple/TransactionCheck.cpp"
#include "src/cpp/ripple/TransactionMaster.cpp"
#include "src/cpp/ripple/TransactionQueue.cpp"
#include "src/cpp/ripple/TrustSetTransactor.cpp"
#include "src/cpp/ripple/ripple_WSHandler.cpp"
#include "tx/PaymentTransactor.cpp"
#include "tx/RegularKeySetTransactor.cpp"
#include "paths/ripple_RippleState.cpp"
#include "rpc/RPCDoor.cpp"
#include "tx/TransactionCheck.cpp"
#include "tx/TransactionMaster.cpp"
#include "tx/TransactionQueue.cpp"
#include "tx/TrustSetTransactor.cpp"
#include "network/ripple_WSHandler.cpp"
#endif
@@ -353,14 +353,14 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 5
#include "src/cpp/ripple/ripple_Peer.cpp"
#include "src/cpp/ripple/ripple_Application.cpp"
#include "src/cpp/ripple/OfferCreateTransactor.cpp"
#include "src/cpp/ripple/ripple_Validations.cpp"
#include "peers/ripple_Peer.cpp"
#include "main/ripple_Application.cpp"
#include "tx/OfferCreateTransactor.cpp"
#include "misc/ripple_Validations.cpp"
#include "src/cpp/ripple/WalletAddTransactor.cpp"
#include "tx/WalletAddTransactor.cpp"
#include "ledger/ripple_AcceptedLedgerTx.cpp"
#include "src/cpp/ripple/ripple_FeeVote.cpp"
#include "misc/ripple_FeeVote.cpp"
#include "ledger/LedgerTiming.cpp"
#endif
@@ -370,15 +370,15 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 6
#include "ledger/ripple_LedgerEntrySet.cpp"
#include "src/cpp/ripple/ripple_Pathfinder.cpp"
#include "src/cpp/ripple/ripple_Features.cpp"
#include "paths/ripple_Pathfinder.cpp"
#include "misc/ripple_Features.cpp"
#include "src/cpp/ripple/ripple_LocalCredentials.cpp"
#include "main/ripple_LocalCredentials.cpp"
#include "ledger/ripple_AcceptedLedger.cpp"
#include "src/cpp/ripple/ripple_DisputedTx.cpp"
#include "src/cpp/ripple/ripple_HashRouter.cpp"
#include "src/cpp/ripple/ripple_Main.cpp"
#include "src/cpp/ripple/ripple_Offer.cpp"
#include "consensus/ripple_DisputedTx.cpp"
#include "misc/ripple_HashRouter.cpp"
#include "main/ripple_Main.cpp"
#include "misc/ripple_Offer.cpp"
#endif
@@ -386,14 +386,14 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 7
#include "src/cpp/ripple/NetworkOPs.cpp"
#include "src/cpp/ripple/ripple_Peers.cpp"
#include "misc/NetworkOPs.cpp"
#include "peers/ripple_Peers.cpp"
#include "ledger/ripple_InboundLedgers.cpp"
#include "ledger/ripple_LedgerHistory.cpp"
#include "src/cpp/ripple/ripple_PathRequest.cpp"
#include "src/cpp/ripple/ripple_SerializedLedger.cpp"
#include "src/cpp/ripple/ripple_TransactionAcquire.cpp"
#include "paths/ripple_PathRequest.cpp"
#include "misc/ripple_SerializedLedger.cpp"
#include "tx/ripple_TransactionAcquire.cpp"
#endif
@@ -401,18 +401,17 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 8
#include "src/cpp/ripple/ripple_LedgerConsensus.cpp"
#include "consensus/ripple_LedgerConsensus.cpp"
#include "ledger/LedgerMaster.cpp"
#include "peers/ripple_PeerSet.cpp"
#include "misc/ripple_InfoSub.cpp"
#include "misc/ripple_OrderBook.cpp"
#include "misc/ripple_ProofOfWork.cpp"
#include "misc/ripple_ProofOfWorkFactory.h" // private
#include "misc/ripple_ProofOfWorkFactory.cpp" // requires ProofOfWork.cpp for ProofOfWork::sMaxDifficulty
#include "misc/ripple_SerializedTransaction.cpp"
#include "src/cpp/ripple/ripple_InfoSub.cpp"
#include "src/cpp/ripple/ripple_OrderBook.cpp"
#include "src/cpp/ripple/ripple_PeerSet.cpp"
#include "src/cpp/ripple/ripple_ProofOfWork.cpp"
#include "src/cpp/ripple/ripple_ProofOfWorkFactory.h" // private
#include "src/cpp/ripple/ripple_ProofOfWorkFactory.cpp" // requires ProofOfWork.cpp for ProofOfWork::sMaxDifficulty
#include "src/cpp/ripple/ripple_SerializedTransaction.cpp"
#include "src/cpp/ripple/ripple_SHAMapSyncFilters.cpp" // requires Application
#include "shamap/ripple_SHAMapSyncFilters.cpp" // requires Application
#endif
@@ -431,9 +430,9 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
// VFALCO TODO Eliminate the need for boost for unit tests.
//
#include "ledger/LedgerUnitTests.cpp"
#include "src/cpp/ripple/ripple_SHAMapUnitTests.cpp"
#include "src/cpp/ripple/ripple_SHAMapSyncUnitTests.cpp"
#include "src/cpp/ripple/ripple_SerializedTransactionUnitTests.cpp"
#include "shamap/ripple_SHAMapUnitTests.cpp"
#include "shamap/ripple_SHAMapSyncUnitTests.cpp"
#include "misc/ripple_SerializedTransactionUnitTests.cpp"
//------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef __CALLRPC__
#define __CALLRPC__
class RPCParser
{
public:
Json::Value parseCommand (std::string strMethod, Json::Value jvParams);
private:
typedef Json::Value (RPCParser::*parseFuncPtr) (const Json::Value& jvParams);
Json::Value parseAccountRaw (const Json::Value& jvParams, bool bPeer);
Json::Value parseAccountItems (const Json::Value& jvParams);
Json::Value parseAccountLines (const Json::Value& jvParams);
Json::Value parseAccountTransactions (const Json::Value& jvParams);
Json::Value parseAsIs (const Json::Value& jvParams);
Json::Value parseBookOffers (const Json::Value& jvParams);
Json::Value parseConnect (const Json::Value& jvParams);
#if ENABLE_INSECURE
Json::Value parseDataDelete (const Json::Value& jvParams);
Json::Value parseDataFetch (const Json::Value& jvParams);
Json::Value parseDataStore (const Json::Value& jvParams);
#endif
Json::Value parseEvented (const Json::Value& jvParams);
Json::Value parseFeature (const Json::Value& jvParams);
Json::Value parseFetchInfo (const Json::Value& jvParams);
Json::Value parseGetCounts (const Json::Value& jvParams);
Json::Value parseInternal (const Json::Value& jvParams);
Json::Value parseJson (const Json::Value& jvParams);
Json::Value parseLedger (const Json::Value& jvParams);
Json::Value parseLedgerId (const Json::Value& jvParams);
#if ENABLE_INSECURE
Json::Value parseLogin (const Json::Value& jvParams);
#endif
Json::Value parseLogLevel (const Json::Value& jvParams);
Json::Value parseOwnerInfo (const Json::Value& jvParams);
Json::Value parseProofCreate (const Json::Value& jvParams);
Json::Value parseProofSolve (const Json::Value& jvParams);
Json::Value parseProofVerify (const Json::Value& jvParams);
Json::Value parseRandom (const Json::Value& jvParams);
Json::Value parseRipplePathFind (const Json::Value& jvParams);
Json::Value parseSMS (const Json::Value& jvParams);
Json::Value parseSignSubmit (const Json::Value& jvParams);
Json::Value parseTx (const Json::Value& jvParams);
Json::Value parseTxHistory (const Json::Value& jvParams);
Json::Value parseUnlAdd (const Json::Value& jvParams);
Json::Value parseUnlDelete (const Json::Value& jvParams);
Json::Value parseValidationCreate (const Json::Value& jvParams);
Json::Value parseValidationSeed (const Json::Value& jvParams);
Json::Value parseWalletAccounts (const Json::Value& jvParams);
Json::Value parseWalletPropose (const Json::Value& jvParams);
Json::Value parseWalletSeed (const Json::Value& jvParams);
};
extern int commandLineRPC (const std::vector<std::string>& vCmd);
extern void callRPC (
boost::asio::io_service& io_service,
const std::string& strIp, const int iPort,
const std::string& strUsername, const std::string& strPassword,
const std::string& strPath, const std::string& strMethod,
const Json::Value& jvParams, const bool bSSL,
FUNCTION_TYPE<void (const Json::Value& jvInput)> callbackFuncP = FUNCTION_TYPE<void (const Json::Value& jvInput)> ());
#endif
// vim:ts=4

View File

@@ -0,0 +1,36 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_RPC_H_INCLUDED
#define RIPPLE_RPC_H_INCLUDED
// VFALCO TODO Wrap these up into a class. It looks like they just do some
// convenience packaging of JSON data from the pieces. It looks
// Ripple client protocol-specific.
//
extern std::string JSONRPCRequest (const std::string& strMethod, const Json::Value& params,
const Json::Value& id);
extern std::string JSONRPCReply (const Json::Value& result, const Json::Value& error, const Json::Value& id);
extern Json::Value JSONRPCError (int code, const std::string& message);
extern std::string createHTTPPost (const std::string& strHost, const std::string& strPath, const std::string& strMsg,
const std::map<std::string, std::string>& mapRequestHeaders);
extern std::string HTTPReply (int nStatus, const std::string& strMsg);
// VFALCO TODO Create a HTTPHeaders class with a nice interface instead of the std::map
//
extern bool HTTPAuthorized (std::map <std::string, std::string> const& mapHeaders);
// VFALCO NOTE This one looks like it does some sort of stream i/o
//
extern int ReadHTTP (std::basic_istream<char>& stream,
std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet);
#endif

View File

@@ -0,0 +1,101 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (RPCDoor)
RPCDoor::RPCDoor (boost::asio::io_service& io_service, RPCServer::Handler& handler)
: m_rpcServerHandler (handler)
, mAcceptor (io_service,
boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string (theConfig.getRpcIP ()), theConfig.getRpcPort ()))
, mDelayTimer (io_service)
, mSSLContext (boost::asio::ssl::context::sslv23)
{
WriteLog (lsINFO, RPCDoor) << "RPC port: " << theConfig.getRpcAddress().toRawUTF8() << " allow remote: " << theConfig.RPC_ALLOW_REMOTE;
if (theConfig.RPC_SECURE != 0)
{
// VFALCO TODO This could be a method of theConfig
//
basio::SslContext::initializeFromFile (
mSSLContext,
theConfig.RPC_SSL_KEY,
theConfig.RPC_SSL_CERT,
theConfig.RPC_SSL_CHAIN);
}
startListening ();
}
RPCDoor::~RPCDoor ()
{
WriteLog (lsINFO, RPCDoor) << "RPC port: " << theConfig.getRpcAddress().toRawUTF8() << " allow remote: " << theConfig.RPC_ALLOW_REMOTE;
}
void RPCDoor::startListening ()
{
RPCServer::pointer new_connection = RPCServer::New (mAcceptor.get_io_service (), mSSLContext, m_rpcServerHandler);
mAcceptor.set_option (boost::asio::ip::tcp::acceptor::reuse_address (true));
mAcceptor.async_accept (new_connection->getRawSocket (),
boost::bind (&RPCDoor::handleConnect, this, new_connection,
boost::asio::placeholders::error));
}
bool RPCDoor::isClientAllowed (const std::string& ip)
{
if (theConfig.RPC_ALLOW_REMOTE)
return true;
// VFALCO TODO Represent ip addresses as a structure. Use isLoopback() member here
//
if (ip == "127.0.0.1")
return true;
return false;
}
void RPCDoor::handleConnect (RPCServer::pointer new_connection, const boost::system::error_code& error)
{
bool delay = false;
if (!error)
{
// Restrict callers by IP
try
{
if (! isClientAllowed (new_connection->getRemoteAddressText ()))
{
startListening ();
return;
}
}
catch (...)
{
// client may have disconnected
startListening ();
return;
}
new_connection->getSocket ().async_handshake (AutoSocket::ssl_socket::server,
boost::bind (&RPCServer::connected, new_connection));
}
else
{
if (error == boost::system::errc::too_many_files_open)
delay = true;
WriteLog (lsINFO, RPCDoor) << "RPCDoor::handleConnect Error: " << error;
}
if (delay)
{
mDelayTimer.expires_from_now (boost::posix_time::milliseconds (1000));
mDelayTimer.async_wait (boost::bind (&RPCDoor::startListening, this));
}
else
startListening ();
}
// vim:ts=4

View File

@@ -0,0 +1,35 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_RPCDOOR_H
#define RIPPLE_RPCDOOR_H
/*
Handles incoming connections from people making RPC Requests
*/
class RPCDoor : LeakChecked <RPCDoor>
{
public:
explicit RPCDoor (
boost::asio::io_service& io_service,
RPCServer::Handler& handler);
~RPCDoor ();
private:
RPCServer::Handler& m_rpcServerHandler;
boost::asio::ip::tcp::acceptor mAcceptor;
boost::asio::deadline_timer mDelayTimer;
boost::asio::ssl::context mSSLContext;
void startListening ();
void handleConnect (RPCServer::pointer new_connection,
const boost::system::error_code& error);
bool isClientAllowed (const std::string& ip);
};
#endif

View File

@@ -0,0 +1,106 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
struct RPCErr; // for Log
SETUP_LOG (RPCErr)
Json::Value rpcError (int iError, Json::Value jvResult)
{
static struct
{
int iError;
const char* pToken;
const char* pMessage;
} errorInfoA[] =
{
{ rpcACT_BITCOIN, "actBitcoin", "Account is bitcoin address." },
{ rpcACT_EXISTS, "actExists", "Account already exists." },
{ rpcACT_MALFORMED, "actMalformed", "Account malformed." },
{ rpcACT_NOT_FOUND, "actNotFound", "Account not found." },
{ rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string." },
{ rpcBAD_FEATURE, "badFeature", "Feature unknown or invalid." },
{ rpcBAD_ISSUER, "badIssuer", "Issuer account malformed." },
{ rpcBAD_MARKET, "badMarket", "No such market." },
{ rpcBAD_SECRET, "badSecret", "Secret does not match account." },
{ rpcBAD_SEED, "badSeed", "Disallowed seed." },
{ rpcBAD_SYNTAX, "badSyntax", "Syntax error." },
{ rpcCOMMAND_MISSING, "commandMissing", "Missing command entry." },
{ rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." },
{ rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exist." },
{ rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." },
{ rpcDST_ISR_MALFORMED, "dstIsrMalformed", "Destination issuer is malformed." },
{ rpcFORBIDDEN, "forbidden", "Bad credentials." },
{ rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." },
{ rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." },
{ rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." },
{ rpcHOST_IP_MALFORMED, "hostIpMalformed", "Host IP is malformed." },
{ rpcINSUF_FUNDS, "insufFunds", "Insufficient funds." },
{ rpcINTERNAL, "internal", "Internal error." },
{ rpcINVALID_PARAMS, "invalidParams", "Invalid parameters." },
{ rpcJSON_RPC, "json_rpc", "JSON-RPC transport error." },
{ rpcLGR_IDXS_INVALID, "lgrIdxsInvalid", "Ledger indexes invalid." },
{ rpcLGR_IDX_MALFORMED, "lgrIdxMalformed", "Ledger index malformed." },
{ rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found." },
{ rpcNICKNAME_MALFORMED, "nicknameMalformed", "Nickname is malformed." },
{ rpcNICKNAME_MISSING, "nicknameMissing", "Nickname does not exist." },
{ rpcNICKNAME_PERM, "nicknamePerm", "Account does not control nickname." },
{ rpcNOT_IMPL, "notImpl", "Not implemented." },
{ rpcNO_ACCOUNT, "noAccount", "No such account." },
{ rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable." },
{ rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable." },
{ rpcNO_EVENTS, "noEvents", "Current transport does not support events." },
{ rpcNO_GEN_DECRPYT, "noGenDectypt", "Password failed to decrypt master public generator." },
{ rpcNO_NETWORK, "noNetwork", "Network not available." },
{ rpcNO_PATH, "noPath", "Unable to find a ripple path." },
{ rpcNO_PERMISSION, "noPermission", "You don't have permission for this command." },
{ rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress." },
{ rpcNOT_STANDALONE, "notStandAlone", "Operation valid in debug mode only." },
{ rpcNOT_SUPPORTED, "notSupported", "Operation not supported." },
{ rpcPASSWD_CHANGED, "passwdChanged", "Wrong key, password changed." },
{ rpcPAYS_ACT_MALFORMED, "paysActMalformed", "Pays account malformed." },
{ rpcPAYS_AMT_MALFORMED, "paysAmtMalformed", "Pays amount malformed." },
{ rpcPORT_MALFORMED, "portMalformed", "Port is malformed." },
{ rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed." },
{ rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed." },
{ rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed." },
{ rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided." },
{ rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source account not found." },
{ rpcSRC_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency/issuer is malformed." },
{ rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed." },
{ rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed." },
{ rpcSRC_UNCLAIMED, "srcUnclaimed", "Source account is not claimed." },
{ rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found." },
{ rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method." },
{ rpcWRONG_SEED, "wrongSeed", "The regular key does not point as the master key." },
{ rpcTOO_BUSY, "tooBusy", "The server is too busy to help you now." },
{ rpcSLOW_DOWN, "slowDown", "You are placing too much load on the server." },
};
int i;
for (i = NUMBER (errorInfoA); i-- && errorInfoA[i].iError != iError;)
;
jvResult["error"] = i >= 0 ? errorInfoA[i].pToken : lexical_cast_i (iError);
jvResult["error_message"] = i >= 0 ? errorInfoA[i].pMessage : lexical_cast_i (iError);
jvResult["error_code"] = iError;
if (i >= 0)
{
WriteLog (lsDEBUG, RPCErr) << "rpcError: "
<< errorInfoA[i].pToken << ": " << errorInfoA[i].pMessage << std::endl;
}
return jvResult;
}
bool isRpcError (Json::Value jvResult)
{
return jvResult.isObject () && jvResult.isMember ("error");
}
// vim:ts=4

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef __RPCERR__
#define __RPCERR__
enum
{
rpcSUCCESS = 0,
rpcBAD_SYNTAX, // Must be 1 to print usage to command line.
rpcJSON_RPC,
rpcFORBIDDEN,
// Error numbers beyond this line are not stable between versions.
// Programs should use error tokens.
// Misc failure
rpcLOAD_FAILED,
rpcNO_PERMISSION,
rpcNO_EVENTS,
rpcNOT_STANDALONE,
rpcTOO_BUSY,
rpcSLOW_DOWN,
// Networking
rpcNO_CLOSED,
rpcNO_CURRENT,
rpcNO_NETWORK,
// Ledger state
rpcACT_EXISTS,
rpcACT_NOT_FOUND,
rpcINSUF_FUNDS,
rpcLGR_NOT_FOUND,
rpcNICKNAME_MISSING,
rpcNO_ACCOUNT,
rpcNO_PATH,
rpcPASSWD_CHANGED,
rpcSRC_MISSING,
rpcSRC_UNCLAIMED,
rpcTXN_NOT_FOUND,
rpcWRONG_SEED,
// Malformed command
rpcINVALID_PARAMS,
rpcUNKNOWN_COMMAND,
rpcNO_PF_REQUEST,
// Bad parameter
rpcACT_BITCOIN,
rpcACT_MALFORMED,
rpcQUALITY_MALFORMED,
rpcBAD_BLOB,
rpcBAD_FEATURE,
rpcBAD_ISSUER,
rpcBAD_MARKET,
rpcBAD_SECRET,
rpcBAD_SEED,
rpcCOMMAND_MISSING,
rpcDST_ACT_MALFORMED,
rpcDST_ACT_MISSING,
rpcDST_AMT_MALFORMED,
rpcDST_ISR_MALFORMED,
rpcGETS_ACT_MALFORMED,
rpcGETS_AMT_MALFORMED,
rpcHOST_IP_MALFORMED,
rpcLGR_IDXS_INVALID,
rpcLGR_IDX_MALFORMED,
rpcNICKNAME_MALFORMED,
rpcNICKNAME_PERM,
rpcPAYS_ACT_MALFORMED,
rpcPAYS_AMT_MALFORMED,
rpcPORT_MALFORMED,
rpcPUBLIC_MALFORMED,
rpcSRC_ACT_MALFORMED,
rpcSRC_ACT_MISSING,
rpcSRC_ACT_NOT_FOUND,
rpcSRC_AMT_MALFORMED,
rpcSRC_CUR_MALFORMED,
rpcSRC_ISR_MALFORMED,
// Internal error (should never happen)
rpcINTERNAL, // Generic internal error.
rpcFAIL_GEN_DECRPYT,
rpcNOT_IMPL,
rpcNOT_SUPPORTED,
rpcNO_GEN_DECRPYT,
};
bool isRpcError (Json::Value jvResult);
Json::Value rpcError (int iError, Json::Value jvResult = Json::Value (Json::objectValue));
#endif
// vim:ts=4

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef __RPCHANDLER__
#define __RPCHANDLER__
#define LEDGER_CURRENT -1
#define LEDGER_CLOSED -2
#define LEDGER_VALIDATED -3
// used by the RPCServer or WSDoor to carry out these RPC commands
class NetworkOPs;
class InfoSub;
// VFALCO TODO Refactor to abstract interface IRPCHandler
//
class RPCHandler
{
public:
enum
{
GUEST,
USER,
ADMIN,
FORBID
};
explicit RPCHandler (NetworkOPs* netOps);
RPCHandler (NetworkOPs* netOps, InfoSub::pointer infoSub);
Json::Value doCommand (const Json::Value& jvRequest, int role, LoadType* loadType);
Json::Value doRpcCommand (const std::string& strCommand, Json::Value const& jvParams, int iRole, LoadType* loadType);
private:
typedef Json::Value (RPCHandler::*doFuncPtr) (
Json::Value params,
LoadType* loadType,
Application::ScopedLockType& MasterLockHolder);
// VFALCO TODO Document these and give the enumeration a label.
enum
{
optNone = 0,
optNetwork = 1, // Need network
optCurrent = 2 + optNetwork, // Need current ledger
optClosed = 4 + optNetwork, // Need closed ledger
};
// Utilities
void addSubmitPath (Json::Value& txJSON);
boost::unordered_set <RippleAddress> parseAccountIds (const Json::Value& jvArray);
Json::Value transactionSign (Json::Value jvRequest, bool bSubmit, bool bFailHard, Application::ScopedLockType& mlh);
Json::Value lookupLedger (Json::Value jvRequest, Ledger::pointer& lpLedger);
Json::Value getMasterGenerator (
Ledger::ref lrLedger,
const RippleAddress& naRegularSeed,
RippleAddress& naMasterGenerator);
Json::Value authorize (
Ledger::ref lrLedger,
const RippleAddress& naRegularSeed,
const RippleAddress& naSrcAccountID,
RippleAddress& naAccountPublic,
RippleAddress& naAccountPrivate,
STAmount& saSrcBalance,
const STAmount& saFee,
AccountState::pointer& asSrc,
const RippleAddress& naVerifyGenerator);
Json::Value accounts (
Ledger::ref lrLedger,
const RippleAddress& naMasterGenerator);
Json::Value accountFromString (
Ledger::ref lrLedger,
RippleAddress& naAccount,
bool& bIndex,
const std::string& strIdent,
const int iIndex,
const bool bStrict);
Json::Value doAccountInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doAccountLines (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doAccountOffers (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doAccountTransactions (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doBookOffers (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doConnect (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doConsensusInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doFeature (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doFetchInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doGetCounts (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doInternal (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedger (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerAccept (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerClosed (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerCurrent (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerEntry (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLedgerHeader (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLogLevel (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLogRotate (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doNicknameInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doOwnerInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doPathFind (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doPeers (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doPing (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doProfile (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doProofCreate (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doProofSolve (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doProofVerify (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doRandom (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doRipplePathFind (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doSMS (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doServerInfo (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh); // for humans
Json::Value doServerState (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh); // for machines
Json::Value doSessionClose (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doSessionOpen (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doSign (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doStop (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doSubmit (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doSubscribe (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doTransactionEntry (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doTx (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doTxHistory (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlAdd (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlDelete (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlFetch (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlList (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlLoad (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlNetwork (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlReset (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnlScore (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doUnsubscribe (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doValidationCreate (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doValidationSeed (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletAccounts (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletLock (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletPropose (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletSeed (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletUnlock (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doWalletVerify (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
#if ENABLE_INSECURE
Json::Value doDataDelete (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doDataFetch (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doDataStore (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
Json::Value doLogin (Json::Value params, LoadType* loadType, Application::ScopedLockType& mlh);
#endif
private:
NetworkOPs* mNetOps;
InfoSub::pointer mInfoSub;
// VFALCO TODO Create an enumeration for this.
int mRole;
};
class RPCInternalHandler
{
public:
typedef Json::Value (*handler_t) (const Json::Value&);
public:
RPCInternalHandler (const std::string& name, handler_t handler);
static Json::Value runHandler (const std::string& name, const Json::Value& params);
private:
// VFALCO TODO Replace with a singleton with a well defined interface and
// a lock free stack (if necessary).
//
static RPCInternalHandler* sHeadHandler;
RPCInternalHandler* mNextHandler;
std::string mName;
handler_t mHandler;
};
// VFALCO TODO tidy up this loose function
int iAdminGet (const Json::Value& jvRequest, const std::string& strRemoteIp);
#endif
// vim:ts=4

View File

@@ -0,0 +1,119 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
SETUP_LOG (RPCSub)
RPCSub::RPCSub (const std::string& strUrl, const std::string& strUsername, const std::string& strPassword)
: mUrl (strUrl), mSSL (false), mUsername (strUsername), mPassword (strPassword), mSending (false)
{
std::string strScheme;
if (!parseUrl (strUrl, strScheme, mIp, mPort, mPath))
{
throw std::runtime_error ("Failed to parse url.");
}
else if (strScheme == "https")
{
mSSL = true;
}
else if (strScheme != "http")
{
throw std::runtime_error ("Only http and https is supported.");
}
mSeq = 1;
if (mPort < 0)
mPort = mSSL ? 443 : 80;
WriteLog (lsINFO, RPCSub) << boost::str (boost::format ("callRPC sub: ip='%s' port=%d ssl=%d path='%s'")
% mIp
% mPort
% mSSL
% mPath);
}
// XXX Could probably create a bunch of send jobs in a single get of the lock.
void RPCSub::sendThread ()
{
Json::Value jvEvent;
bool bSend;
do
{
{
// Obtain the lock to manipulate the queue and change sending.
boost::mutex::scoped_lock sl (mLockInfo);
if (mDeque.empty ())
{
mSending = false;
bSend = false;
}
else
{
std::pair<int, Json::Value> pEvent = mDeque.front ();
mDeque.pop_front ();
jvEvent = pEvent.second;
jvEvent["seq"] = pEvent.first;
bSend = true;
}
}
// Send outside of the lock.
if (bSend)
{
// XXX Might not need this in a try.
try
{
WriteLog (lsINFO, RPCSub) << boost::str (boost::format ("callRPC calling: %s") % mIp);
callRPC (
getApp().getIOService (),
mIp, mPort,
mUsername, mPassword,
mPath, "event",
jvEvent,
mSSL);
}
catch (const std::exception& e)
{
WriteLog (lsINFO, RPCSub) << boost::str (boost::format ("callRPC exception: %s") % e.what ());
}
}
}
while (bSend);
}
void RPCSub::send (const Json::Value& jvObj, bool broadcast)
{
boost::mutex::scoped_lock sl (mLockInfo);
if (RPC_EVENT_QUEUE_MAX == mDeque.size ())
{
// Drop the previous event.
WriteLog (lsWARNING, RPCSub) << boost::str (boost::format ("callRPC drop"));
mDeque.pop_back ();
}
WriteLog (broadcast ? lsDEBUG : lsINFO, RPCSub) << boost::str (boost::format ("callRPC push: %s") % jvObj);
mDeque.push_back (std::make_pair (mSeq++, jvObj));
if (!mSending)
{
// Start a sending thread.
mSending = true;
WriteLog (lsINFO, RPCSub) << boost::str (boost::format ("callRPC start"));
boost::thread (BIND_TYPE (&RPCSub::sendThread, this)).detach ();
}
}
// vim:ts=4

View File

@@ -0,0 +1,65 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef __RPCSUB__
#define __RPCSUB__
#define RPC_EVENT_QUEUE_MAX 32
// Subscription object for JSON-RPC
class RPCSub
: public InfoSub
, LeakChecked <RPCSub>
{
public:
typedef boost::shared_ptr<RPCSub> pointer;
typedef const pointer& ref;
RPCSub (const std::string& strUrl, const std::string& strUsername, const std::string& strPassword);
virtual ~RPCSub ()
{
;
}
// Implement overridden functions from base class:
void send (const Json::Value& jvObj, bool broadcast);
void setUsername (const std::string& strUsername)
{
boost::mutex::scoped_lock sl (mLockInfo);
mUsername = strUsername;
}
void setPassword (const std::string& strPassword)
{
boost::mutex::scoped_lock sl (mLockInfo);
mPassword = strPassword;
}
protected:
void sendThread ();
private:
std::string mUrl;
std::string mIp;
int mPort;
bool mSSL;
std::string mUsername;
std::string mPassword;
std::string mPath;
int mSeq; // Next id to allocate.
bool mSending; // Sending threead is active.
std::deque<std::pair<int, Json::Value> > mDeque;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,281 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// Used for logging
struct RPC;
SETUP_LOG (RPC)
unsigned int const gMaxHTTPHeaderSize = 0x02000000;
std::string gFormatStr ("v1");
// VFALCO TODO clean up this nonsense
std::string FormatFullVersion ()
{
return (gFormatStr);
}
Json::Value JSONRPCError (int code, const std::string& message)
{
Json::Value error (Json::objectValue);
error["code"] = Json::Value (code);
error["message"] = Json::Value (message);
return error;
}
//
// HTTP protocol
//
// This ain't Apache. We're just using HTTP header for the length field
// and to be compatible with other JSON-RPC implementations.
//
std::string createHTTPPost (const std::string& strHost, const std::string& strPath, const std::string& strMsg, const std::map<std::string, std::string>& mapRequestHeaders)
{
std::ostringstream s;
s << "POST "
<< (strPath.empty () ? "/" : strPath)
<< " HTTP/1.0\r\n"
<< "User-Agent: " SYSTEM_NAME "-json-rpc/" << FormatFullVersion () << "\r\n"
<< "Host: " << strHost << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Content-Length: " << strMsg.size () << "\r\n"
<< "Accept: application/json\r\n";
typedef std::map<std::string, std::string>::value_type HeaderType;
BOOST_FOREACH (const HeaderType & item, mapRequestHeaders)
s << item.first << ": " << item.second << "\r\n";
s << "\r\n" << strMsg;
return s.str ();
}
std::string rfc1123Time ()
{
char buffer[64];
time_t now;
time (&now);
struct tm* now_gmt = gmtime (&now);
std::string locale (setlocale (LC_TIME, NULL));
setlocale (LC_TIME, "C"); // we want posix (aka "C") weekday/month strings
strftime (buffer, sizeof (buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt);
setlocale (LC_TIME, locale.c_str ());
return std::string (buffer);
}
std::string HTTPReply (int nStatus, const std::string& strMsg)
{
WriteLog (lsTRACE, RPC) << "HTTP Reply " << nStatus << " " << strMsg;
if (nStatus == 401)
return strprintf ("HTTP/1.0 401 Authorization Required\r\n"
"Date: %s\r\n"
"Server: " SYSTEM_NAME "-json-rpc/%s\r\n"
"WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 296\r\n"
"\r\n"
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
"\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
"<HTML>\r\n"
"<HEAD>\r\n"
"<TITLE>Error</TITLE>\r\n"
"<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n"
"</HEAD>\r\n"
"<BODY><H1>401 Unauthorized.</H1></BODY>\r\n"
"</HTML>\r\n", rfc1123Time ().c_str (), FormatFullVersion ().c_str ());
std::string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 403) strStatus = "Forbidden";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
std::string access;
if (theConfig.RPC_ALLOW_REMOTE) access = "Access-Control-Allow-Origin: *\r\n";
else access = "";
return strprintf (
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Connection: Keep-Alive\r\n"
"%s"
"Content-Length: %d\r\n"
"Content-Type: application/json; charset=UTF-8\r\n"
"Server: " SYSTEM_NAME "-json-rpc/%s\r\n"
"\r\n"
"%s\r\n",
nStatus,
strStatus.c_str (),
rfc1123Time ().c_str (),
access.c_str (),
strMsg.size () + 2,
SERVER_VERSION,
strMsg.c_str ());
}
int ReadHTTPStatus (std::basic_istream<char>& stream)
{
std::string str;
getline (stream, str);
std::vector<std::string> vWords;
boost::split (vWords, str, boost::is_any_of (" "));
if (vWords.size () < 2)
return 500;
return atoi (vWords[1].c_str ());
}
int ReadHTTPHeader (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet)
{
int nLen = 0;
for (;;)
{
std::string str;
std::getline (stream, str);
if (str.empty () || str == "\r")
break;
std::string::size_type nColon = str.find (":");
if (nColon != std::string::npos)
{
std::string strHeader = str.substr (0, nColon);
boost::trim (strHeader);
boost::to_lower (strHeader);
std::string strValue = str.substr (nColon + 1);
boost::trim (strValue);
mapHeadersRet[strHeader] = strValue;
if (strHeader == "content-length")
nLen = atoi (strValue.c_str ());
}
}
return nLen;
}
int ReadHTTP (std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet,
std::string& strMessageRet)
{
mapHeadersRet.clear ();
strMessageRet = "";
// Read status
int nStatus = ReadHTTPStatus (stream);
// Read header
int nLen = ReadHTTPHeader (stream, mapHeadersRet);
if (nLen < 0 || nLen > gMaxHTTPHeaderSize)
return 500;
// Read message
if (nLen > 0)
{
std::vector<char> vch (nLen);
stream.read (&vch[0], nLen);
strMessageRet = std::string (vch.begin (), vch.end ());
}
return nStatus;
}
std::string DecodeBase64 (std::string s)
{
// FIXME: This performs badly
BIO* b64, *bmem;
char* buffer = static_cast<char*> (calloc (s.size (), sizeof (char)));
b64 = BIO_new (BIO_f_base64 ());
BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new_mem_buf (const_cast<char*> (s.data ()), s.size ());
bmem = BIO_push (b64, bmem);
BIO_read (bmem, buffer, s.size ());
BIO_free_all (bmem);
std::string result (buffer);
free (buffer);
return result;
}
bool HTTPAuthorized (const std::map<std::string, std::string>& mapHeaders)
{
std::map<std::string, std::string>::const_iterator it = mapHeaders.find ("authorization");
if ((it == mapHeaders.end ()) || (it->second.substr (0, 6) != "Basic "))
return theConfig.RPC_USER.empty () && theConfig.RPC_PASSWORD.empty ();
std::string strUserPass64 = it->second.substr (6);
boost::trim (strUserPass64);
std::string strUserPass = DecodeBase64 (strUserPass64);
std::string::size_type nColon = strUserPass.find (":");
if (nColon == std::string::npos)
return false;
std::string strUser = strUserPass.substr (0, nColon);
std::string strPassword = strUserPass.substr (nColon + 1);
return (strUser == theConfig.RPC_USER) && (strPassword == theConfig.RPC_PASSWORD);
}
//
// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
// unspecified (HTTP errors and contents of 'error').
//
// 1.0 spec: http://json-rpc.org/wiki/specification
// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http
//
std::string JSONRPCRequest (const std::string& strMethod, const Json::Value& params, const Json::Value& id)
{
Json::Value request;
request["method"] = strMethod;
request["params"] = params;
request["id"] = id;
Json::FastWriter writer;
return writer.write (request) + "\n";
}
std::string JSONRPCReply (const Json::Value& result, const Json::Value& error, const Json::Value& id)
{
Json::Value reply (Json::objectValue);
reply["result"] = result;
//reply["error"]=error;
//reply["id"]=id;
Json::FastWriter writer;
return writer.write (reply) + "\n";
}
void ErrorReply (std::ostream& stream, const Json::Value& objError, const Json::Value& id)
{
// Send error reply from json-rpc error object
int nStatus = 500;
int code = objError["code"].asInt ();
if (code == -32600) nStatus = 400;
else if (code == -32601) nStatus = 404;
std::string strReply = JSONRPCReply (Json::Value (), objError, id);
stream << HTTPReply (nStatus, strReply) << std::flush;
}
// vim:ts=4

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,237 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SHAMAP_H
#define RIPPLE_SHAMAP_H
enum SHAMapState
{
smsModifying = 0, // Objects can be added and removed (like an open ledger)
smsImmutable = 1, // Map cannot be changed (like a closed ledger)
smsSynching = 2, // Map's hash is locked in, valid nodes can be added (like a peer's closing ledger)
smsFloating = 3, // Map is free to change hash (like a synching open ledger)
smsInvalid = 4, // Map is known not to be valid (usually synching a corrupt ledger)
};
class SHAMap
: public CountedObject <SHAMap>
{
public:
static char const* getCountedObjectName () { return "SHAMap"; }
typedef boost::shared_ptr<SHAMap> pointer;
typedef const boost::shared_ptr<SHAMap>& ref;
typedef std::pair<SHAMapItem::pointer, SHAMapItem::pointer> DeltaItem;
typedef std::map<uint256, DeltaItem> Delta;
typedef boost::unordered_map<SHAMapNode, SHAMapTreeNode::pointer> DirtyMap;
public:
// build new map
explicit SHAMap (SHAMapType t, uint32 seq = 1);
SHAMap (SHAMapType t, uint256 const & hash);
~SHAMap ()
{
mState = smsInvalid;
}
// Returns a new map that's a snapshot of this one. Force CoW
SHAMap::pointer snapShot (bool isMutable);
// Remove nodes from memory
void dropCache ();
void setLedgerSeq (uint32 lseq)
{
mLedgerSeq = lseq;
}
// hold the map stable across operations
ScopedLock Lock () const
{
return ScopedLock (mLock);
}
bool hasNode (const SHAMapNode & id);
bool fetchRoot (uint256 const & hash, SHAMapSyncFilter * filter);
// normal hash access functions
bool hasItem (uint256 const & id);
bool delItem (uint256 const & id);
bool addItem (const SHAMapItem & i, bool isTransaction, bool hasMeta);
bool updateItem (const SHAMapItem & i, bool isTransaction, bool hasMeta);
SHAMapItem getItem (uint256 const & id);
uint256 getHash () const
{
return root->getNodeHash ();
}
uint256 getHash ()
{
return root->getNodeHash ();
}
// save a copy if you have a temporary anyway
bool updateGiveItem (SHAMapItem::ref, bool isTransaction, bool hasMeta);
bool addGiveItem (SHAMapItem::ref, bool isTransaction, bool hasMeta);
// save a copy if you only need a temporary
SHAMapItem::pointer peekItem (uint256 const & id);
SHAMapItem::pointer peekItem (uint256 const & id, uint256 & hash);
SHAMapItem::pointer peekItem (uint256 const & id, SHAMapTreeNode::TNType & type);
// traverse functions
SHAMapItem::pointer peekFirstItem ();
SHAMapItem::pointer peekFirstItem (SHAMapTreeNode::TNType & type);
SHAMapItem::pointer peekLastItem ();
SHAMapItem::pointer peekNextItem (uint256 const& );
SHAMapItem::pointer peekNextItem (uint256 const& , SHAMapTreeNode::TNType & type);
SHAMapItem::pointer peekPrevItem (uint256 const& );
// comparison/sync functions
void getMissingNodes (std::vector<SHAMapNode>& nodeIDs, std::vector<uint256>& hashes, int max,
SHAMapSyncFilter * filter);
bool getNodeFat (const SHAMapNode & node, std::vector<SHAMapNode>& nodeIDs,
std::list<Blob >& rawNode, bool fatRoot, bool fatLeaves);
bool getRootNode (Serializer & s, SHANodeFormat format);
std::vector<uint256> getNeededHashes (int max, SHAMapSyncFilter * filter);
SHAMapAddNode addRootNode (uint256 const & hash, Blob const & rootNode, SHANodeFormat format,
SHAMapSyncFilter * filter);
SHAMapAddNode addRootNode (Blob const & rootNode, SHANodeFormat format,
SHAMapSyncFilter * filter);
SHAMapAddNode addKnownNode (const SHAMapNode & nodeID, Blob const & rawNode,
SHAMapSyncFilter * filter);
// status functions
void setImmutable ()
{
assert (mState != smsInvalid);
mState = smsImmutable;
}
void clearImmutable ()
{
mState = smsModifying;
}
bool isSynching () const
{
return (mState == smsFloating) || (mState == smsSynching);
}
void setSynching ()
{
mState = smsSynching;
}
void setFloating ()
{
mState = smsFloating;
}
void clearSynching ()
{
mState = smsModifying;
}
bool isValid ()
{
return mState != smsInvalid;
}
// caution: otherMap must be accessed only by this function
// return value: true=successfully completed, false=too different
bool compare (SHAMap::ref otherMap, Delta & differences, int maxCount);
int armDirty ();
static int flushDirty (DirtyMap & dirtyMap, int maxNodes, NodeObjectType t, uint32 seq);
boost::shared_ptr<DirtyMap> disarmDirty ();
void setSeq (uint32 seq)
{
mSeq = seq;
}
uint32 getSeq ()
{
return mSeq;
}
// overloads for backed maps
boost::shared_ptr<SHAMapTreeNode> fetchNodeExternal (const SHAMapNode & id, uint256 const & hash); // throws
boost::shared_ptr<SHAMapTreeNode> fetchNodeExternalNT (const SHAMapNode & id, uint256 const & hash); // no throw
bool operator== (const SHAMap & s)
{
return getHash () == s.getHash ();
}
// trusted path operations - prove a particular node is in a particular ledger
std::list<Blob > getTrustedPath (uint256 const & index);
static Blob checkTrustedPath (uint256 const & ledgerHash, uint256 const & leafIndex,
const std::list<Blob >& path);
void walkMap (std::vector<SHAMapMissingNode>& missingNodes, int maxMissing);
bool getPath (uint256 const & index, std::vector< Blob >& nodes, SHANodeFormat format);
bool deepCompare (SHAMap & other);
virtual void dump (bool withHashes = false);
typedef std::pair <uint256, Blob> fetchPackEntry_t;
std::list<fetchPackEntry_t> getFetchPack (SHAMap * have, bool includeLeaves, int max);
void getFetchPack (SHAMap * have, bool includeLeaves, int max, FUNCTION_TYPE<void (const uint256&, const Blob&)>);
static int getFullBelowSize ()
{
return fullBelowCache.getSize ();
}
static void sweep ()
{
fullBelowCache.sweep ();
}
private:
static KeyCache <uint256, UptimeTimerAdapter> fullBelowCache;
void dirtyUp (std::stack<SHAMapTreeNode::pointer>& stack, uint256 const & target, uint256 prevHash);
std::stack<SHAMapTreeNode::pointer> getStack (uint256 const & id, bool include_nonmatching_leaf);
SHAMapTreeNode::pointer walkTo (uint256 const & id, bool modify);
SHAMapTreeNode* walkToPointer (uint256 const & id);
SHAMapTreeNode::pointer checkCacheNode (const SHAMapNode&);
void returnNode (SHAMapTreeNode::pointer&, bool modify);
void trackNewNode (SHAMapTreeNode::pointer&);
SHAMapTreeNode::pointer getNode (const SHAMapNode & id);
SHAMapTreeNode::pointer getNode (const SHAMapNode & id, uint256 const & hash, bool modify);
SHAMapTreeNode* getNodePointer (const SHAMapNode & id, uint256 const & hash);
SHAMapTreeNode* getNodePointerNT (const SHAMapNode & id, uint256 const & hash);
SHAMapTreeNode* getNodePointer (const SHAMapNode & id, uint256 const & hash, SHAMapSyncFilter * filter);
SHAMapTreeNode* getNodePointerNT (const SHAMapNode & id, uint256 const & hash, SHAMapSyncFilter * filter);
SHAMapTreeNode* firstBelow (SHAMapTreeNode*);
SHAMapTreeNode* lastBelow (SHAMapTreeNode*);
SHAMapItem::pointer onlyBelow (SHAMapTreeNode*);
void eraseChildren (SHAMapTreeNode::pointer);
void dropBelow (SHAMapTreeNode*);
bool hasInnerNode (const SHAMapNode & nodeID, uint256 const & hash);
bool hasLeafNode (uint256 const & tag, uint256 const & hash);
bool walkBranch (SHAMapTreeNode * node, SHAMapItem::ref otherMapItem, bool isFirstMap,
Delta & differences, int & maxCount);
private:
uint32 mSeq;
uint32 mLedgerSeq; // sequence number of ledger this is part of
mutable boost::recursive_mutex mLock;
boost::unordered_map<SHAMapNode, SHAMapTreeNode::pointer> mTNByID;
boost::shared_ptr<DirtyMap> mDirtyNodes;
SHAMapTreeNode::pointer root;
SHAMapState mState;
SHAMapType mType;
};
#endif
// vim:ts=4

View File

@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SHAMAPADDNODE_H
#define RIPPLE_SHAMAPADDNODE_H
// results of adding nodes
class SHAMapAddNode
{
public:
SHAMapAddNode ()
: mInvalid (false)
, mUseful (false)
{
}
void setInvalid ()
{
mInvalid = true;
}
void setUseful ()
{
mUseful = true;
}
void reset ()
{
mInvalid = false;
mUseful = false;
}
bool isInvalid () const
{
return mInvalid;
}
bool isUseful () const
{
return mUseful;
}
bool combine (SHAMapAddNode const& n)
{
// VFALCO NOTE What is the meaning of these lines?
if (n.mInvalid)
{
mInvalid = true;
return false;
}
if (n.mUseful)
mUseful = true;
return true;
}
operator bool () const
{
return !mInvalid;
}
static SHAMapAddNode okay ()
{
return SHAMapAddNode (false, false);
}
static SHAMapAddNode useful ()
{
return SHAMapAddNode (false, true);
}
static SHAMapAddNode invalid ()
{
return SHAMapAddNode (true, false);
}
private:
SHAMapAddNode (bool i, bool u)
: mInvalid (i)
, mUseful (u)
{
}
bool mInvalid;
bool mUseful;
};
#endif

View File

@@ -0,0 +1,257 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// This code is used to compare another node's transaction tree
// to our own. It returns a map containing all items that are different
// between two SHA maps. It is optimized not to descend down tree
// branches with the same branch hash. A limit can be passed so
// that we will abort early if a node sends a map to us that
// makes no sense at all. (And our sync algorithm will avoid
// synchronizing matching brances too.)
class SHAMapDeltaNode
{
public:
SHAMapNode mNodeID;
uint256 mOurHash, mOtherHash;
SHAMapDeltaNode (const SHAMapNode& id, uint256 const& ourHash, uint256 const& otherHash) :
mNodeID (id), mOurHash (ourHash), mOtherHash (otherHash)
{
;
}
};
bool SHAMap::walkBranch (SHAMapTreeNode* node, SHAMapItem::ref otherMapItem, bool isFirstMap,
Delta& differences, int& maxCount)
{
// Walk a branch of a SHAMap that's matched by an empty branch or single item in the other map
std::stack<SHAMapTreeNode*> nodeStack;
nodeStack.push (node);
bool emptyBranch = !otherMapItem;
while (!nodeStack.empty ())
{
SHAMapTreeNode* node = nodeStack.top ();
nodeStack.pop ();
if (node->isInner ())
{
// This is an inner node, add all non-empty branches
for (int i = 0; i < 16; ++i)
if (!node->isEmptyBranch (i))
nodeStack.push (getNodePointer (node->getChildNodeID (i), node->getChildHash (i)));
}
else
{
// This is a leaf node, process its item
SHAMapItem::pointer item = node->getItem ();
if (!emptyBranch && (otherMapItem->getTag () < item->getTag ()))
{
// this item comes after the item from the other map, so add the other item
if (isFirstMap) // this is first map, so other item is from second
differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (SHAMapItem::pointer (), otherMapItem)));
else
differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (otherMapItem, SHAMapItem::pointer ())));
if (--maxCount <= 0)
return false;
emptyBranch = true;
}
if (emptyBranch || (item->getTag () != otherMapItem->getTag ()))
{
// unmatched
if (isFirstMap)
differences.insert (std::make_pair (item->getTag (), std::make_pair (item, SHAMapItem::pointer ())));
else
differences.insert (std::make_pair (item->getTag (), std::make_pair (SHAMapItem::pointer (), item)));
if (--maxCount <= 0)
return false;
}
else
{
if (item->peekData () != otherMapItem->peekData ())
{
// non-matching items
if (isFirstMap) differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (item, otherMapItem)));
else differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (otherMapItem, item)));
if (--maxCount <= 0)
return false;
}
emptyBranch = true;
}
}
}
if (!emptyBranch)
{
// otherMapItem was unmatched, must add
if (isFirstMap) // this is first map, so other item is from second
differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (SHAMapItem::pointer (), otherMapItem)));
else
differences.insert (std::make_pair (otherMapItem->getTag (),
std::make_pair (otherMapItem, SHAMapItem::pointer ())));
if (--maxCount <= 0)
return false;
}
return true;
}
bool SHAMap::compare (SHAMap::ref otherMap, Delta& differences, int maxCount)
{
// compare two hash trees, add up to maxCount differences to the difference table
// return value: true=complete table of differences given, false=too many differences
// throws on corrupt tables or missing nodes
// CAUTION: otherMap is not locked and must be immutable
assert (isValid () && otherMap && otherMap->isValid ());
std::stack<SHAMapDeltaNode> nodeStack; // track nodes we've pushed
boost::recursive_mutex::scoped_lock sl (mLock);
if (getHash () == otherMap->getHash ())
return true;
nodeStack.push (SHAMapDeltaNode (SHAMapNode (), getHash (), otherMap->getHash ()));
while (!nodeStack.empty ())
{
SHAMapDeltaNode dNode (nodeStack.top ());
nodeStack.pop ();
SHAMapTreeNode* ourNode = getNodePointer (dNode.mNodeID, dNode.mOurHash);
SHAMapTreeNode* otherNode = otherMap->getNodePointer (dNode.mNodeID, dNode.mOtherHash);
if (!ourNode || !otherNode)
{
assert (false);
throw SHAMapMissingNode (mType, dNode.mNodeID, uint256 ());
}
if (ourNode->isLeaf () && otherNode->isLeaf ())
{
// two leaves
if (ourNode->getTag () == otherNode->getTag ())
{
if (ourNode->peekData () != otherNode->peekData ())
{
differences.insert (std::make_pair (ourNode->getTag (),
std::make_pair (ourNode->getItem (), otherNode->getItem ())));
if (--maxCount <= 0)
return false;
}
}
else
{
differences.insert (std::make_pair (ourNode->getTag (),
std::make_pair (ourNode->getItem (), SHAMapItem::pointer ())));
if (--maxCount <= 0)
return false;
differences.insert (std::make_pair (otherNode->getTag (),
std::make_pair (SHAMapItem::pointer (), otherNode->getItem ())));
if (--maxCount <= 0)
return false;
}
}
else if (ourNode->isInner () && otherNode->isLeaf ())
{
if (!walkBranch (ourNode, otherNode->getItem (), true, differences, maxCount))
return false;
}
else if (ourNode->isLeaf () && otherNode->isInner ())
{
if (!otherMap->walkBranch (otherNode, ourNode->getItem (), false, differences, maxCount))
return false;
}
else if (ourNode->isInner () && otherNode->isInner ())
{
for (int i = 0; i < 16; ++i)
if (ourNode->getChildHash (i) != otherNode->getChildHash (i))
{
if (otherNode->isEmptyBranch (i))
{
// We have a branch, the other tree does not
SHAMapTreeNode* iNode = getNodePointer (ourNode->getChildNodeID (i), ourNode->getChildHash (i));
if (!walkBranch (iNode, SHAMapItem::pointer (), true, differences, maxCount))
return false;
}
else if (ourNode->isEmptyBranch (i))
{
// The other tree has a branch, we do not
SHAMapTreeNode* iNode =
otherMap->getNodePointer (otherNode->getChildNodeID (i), otherNode->getChildHash (i));
if (!otherMap->walkBranch (iNode, SHAMapItem::pointer (), false, differences, maxCount))
return false;
}
else // The two trees have different non-empty branches
nodeStack.push (SHAMapDeltaNode (ourNode->getChildNodeID (i),
ourNode->getChildHash (i), otherNode->getChildHash (i)));
}
}
else
assert (false);
}
return true;
}
void SHAMap::walkMap (std::vector<SHAMapMissingNode>& missingNodes, int maxMissing)
{
std::stack<SHAMapTreeNode::pointer> nodeStack;
boost::recursive_mutex::scoped_lock sl (mLock);
if (!root->isInner ()) // root is only node, and we have it
return;
nodeStack.push (root);
while (!nodeStack.empty ())
{
SHAMapTreeNode::pointer node = nodeStack.top ();
nodeStack.pop ();
for (int i = 0; i < 16; ++i)
if (!node->isEmptyBranch (i))
{
try
{
SHAMapTreeNode::pointer d = getNode (node->getChildNodeID (i), node->getChildHash (i), false);
if (d->isInner ())
nodeStack.push (d);
}
catch (SHAMapMissingNode& n)
{
missingNodes.push_back (n);
if (--maxMissing <= 0)
return;
}
}
}
}

View File

@@ -0,0 +1,19 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
class SHAMap;
SHAMapItem::SHAMapItem (uint256 const& tag, Blob const& data)
: mTag (tag)
, mData (data)
{
}
SHAMapItem::SHAMapItem (uint256 const& tag, const Serializer& data)
: mTag (tag)
, mData (data.peekData ())
{
}

View File

@@ -0,0 +1,116 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SHAMAPITEM_H
#define RIPPLE_SHAMAPITEM_H
// an item stored in a SHAMap
class SHAMapItem
: public CountedObject <SHAMapItem>
{
public:
static char const* getCountedObjectName () { return "SHAMapItem"; }
typedef boost::shared_ptr<SHAMapItem> pointer;
typedef const boost::shared_ptr<SHAMapItem>& ref;
public:
explicit SHAMapItem (uint256 const & tag) : mTag (tag)
{
;
}
explicit SHAMapItem (Blob const & data); // tag by hash
SHAMapItem (uint256 const & tag, Blob const & data);
SHAMapItem (uint256 const & tag, const Serializer & s);
uint256 const& getTag () const
{
return mTag;
}
Blob getData () const
{
return mData.getData ();
}
Blob const& peekData () const
{
return mData.peekData ();
}
Serializer& peekSerializer ()
{
return mData;
}
void addRaw (Blob & s) const
{
s.insert (s.end (), mData.begin (), mData.end ());
}
void updateData (Blob const & data)
{
mData = data;
}
bool operator== (const SHAMapItem & i) const
{
return mTag == i.mTag;
}
bool operator!= (const SHAMapItem & i) const
{
return mTag != i.mTag;
}
bool operator== (uint256 const & i) const
{
return mTag == i;
}
bool operator!= (uint256 const & i) const
{
return mTag != i;
}
#if 0
// This code is comment out because it is unused. It could work.
bool operator< (const SHAMapItem & i) const
{
return mTag < i.mTag;
}
bool operator> (const SHAMapItem & i) const
{
return mTag > i.mTag;
}
bool operator<= (const SHAMapItem & i) const
{
return mTag <= i.mTag;
}
bool operator>= (const SHAMapItem & i) const
{
return mTag >= i.mTag;
}
bool operator< (uint256 const & i) const
{
return mTag < i;
}
bool operator> (uint256 const & i) const
{
return mTag > i;
}
bool operator<= (uint256 const & i) const
{
return mTag <= i;
}
bool operator>= (uint256 const & i) const
{
return mTag >= i;
}
#endif
virtual void dump ();
private:
uint256 mTag;
Serializer mData;
};
#endif

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
std::ostream& operator<< (std::ostream& out, const SHAMapMissingNode& mn)
{
switch (mn.getMapType ())
{
case smtTRANSACTION:
out << "Missing/TXN(" << mn.getNodeID () << "/" << mn.getNodeHash () << ")";
break;
case smtSTATE:
out << "Missing/STA(" << mn.getNodeID () << "/" << mn.getNodeHash () << ")";
break;
case smtFREE:
default:
out << "Missing/" << mn.getNodeID ();
break;
};
return out;
}

View File

@@ -0,0 +1,85 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_SHAMAPMISSINGNODE_H
#define RIPPLE_SHAMAPMISSINGNODE_H
enum SHAMapType
{
smtTRANSACTION = 1, // A tree of transactions
smtSTATE = 2, // A tree of state nodes
smtFREE = 3, // A tree not part of a ledger
};
class SHAMapMissingNode : public std::runtime_error
{
public:
SHAMapMissingNode (SHAMapType t,
SHAMapNode const& nodeID,
uint256 const& nodeHash)
: std::runtime_error ("SHAMapMissingNode")
, mType (t)
, mNodeID (nodeID)
, mNodeHash (nodeHash)
{
}
SHAMapMissingNode (SHAMapType t,
SHAMapNode const& nodeID,
uint256 const& nodeHash,
uint256 const& targetIndex)
: std::runtime_error (nodeID.getString ())
, mType (t)
, mNodeID (nodeID)
, mNodeHash (nodeHash)
, mTargetIndex (targetIndex)
{
}
virtual ~SHAMapMissingNode () throw ()
{
}
void setTargetNode (uint256 const& tn)
{
mTargetIndex = tn;
}
SHAMapType getMapType () const
{
return mType;
}
SHAMapNode const& getNodeID () const
{
return mNodeID;
}
uint256 const& getNodeHash () const
{
return mNodeHash;
}
uint256 const& getTargetIndex () const
{
return mTargetIndex;
}
bool hasTargetIndex () const
{
return !mTargetIndex.isZero ();
}
private:
SHAMapType mType;
SHAMapNode mNodeID;
uint256 mNodeHash;
uint256 mTargetIndex;
};
extern std::ostream& operator<< (std::ostream&, SHAMapMissingNode const&);
#endif

Some files were not shown because too many files have changed in this diff Show More