mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Move ./modules to ./src
This commit is contained in:
@@ -1030,9 +1030,6 @@
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\modules\beast_core\beast_core.cpp">
|
||||
<Filter>beast_core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\containers\beast_AbstractFifo.cpp">
|
||||
<Filter>beast_core\containers</Filter>
|
||||
</ClCompile>
|
||||
@@ -1312,9 +1309,6 @@
|
||||
<ClCompile Include="..\..\modules\beast_core\diagnostic\beast_UnitTest.cpp">
|
||||
<Filter>beast_core\diagnostic</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_crypto\beast_crypto.cpp">
|
||||
<Filter>beast_crypto</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_crypto\math\beast_UnsignedInteger.cpp">
|
||||
<Filter>beast_crypto\math</Filter>
|
||||
</ClCompile>
|
||||
@@ -1393,9 +1387,6 @@
|
||||
<ClCompile Include="..\..\modules\beast_core\diagnostic\beast_SemanticVersion.cpp">
|
||||
<Filter>beast_core\diagnostic</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_asio\beast_asio.cpp">
|
||||
<Filter>beast_asio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\threads\beast_ReadWriteMutex.cpp">
|
||||
<Filter>beast_core\threads</Filter>
|
||||
</ClCompile>
|
||||
@@ -1543,6 +1534,15 @@
|
||||
<ClCompile Include="..\..\modules\beast_asio\http\UniformResourceLocator.cpp">
|
||||
<Filter>beast_asio\http</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_crypto\beast_crypto.cpp">
|
||||
<Filter>beast_crypto</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\beast_core.cpp">
|
||||
<Filter>beast_core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_asio\beast_asio.cpp">
|
||||
<Filter>beast_asio</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="..\..\TODO.txt" />
|
||||
|
||||
137
src/ripple_app/consensus/ripple_DisputedTx.cpp
Normal file
137
src/ripple_app/consensus/ripple_DisputedTx.cpp
Normal 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;
|
||||
}
|
||||
|
||||
75
src/ripple_app/consensus/ripple_DisputedTx.h
Normal file
75
src/ripple_app/consensus/ripple_DisputedTx.h
Normal 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
|
||||
1519
src/ripple_app/consensus/ripple_LedgerConsensus.cpp
Normal file
1519
src/ripple_app/consensus/ripple_LedgerConsensus.cpp
Normal file
File diff suppressed because it is too large
Load Diff
161
src/ripple_app/consensus/ripple_LedgerConsensus.h
Normal file
161
src/ripple_app/consensus/ripple_LedgerConsensus.h
Normal file
@@ -0,0 +1,161 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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;
|
||||
boost::unordered_set<uint256> mCompares;
|
||||
|
||||
// Close time estimates
|
||||
std::map<uint32, int> mCloseTimes;
|
||||
|
||||
// nodes that have bowed out of this consensus process
|
||||
boost::unordered_set<uint160> mDeadNodes;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
33
src/ripple_app/contracts/ripple_Contract.cpp
Normal file
33
src/ripple_app/contracts/ripple_Contract.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
Contract::Contract ()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Contract::executeCreate ()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeRemove ()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeFund ()
|
||||
{
|
||||
|
||||
}
|
||||
void Contract::executeAccept ()
|
||||
{
|
||||
//std::vector<char> code;
|
||||
|
||||
//Interpreter interpreter;
|
||||
//interpreter.interpret(this,code);
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
35
src/ripple_app/contracts/ripple_Contract.h
Normal file
35
src/ripple_app/contracts/ripple_Contract.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef CONTRACT_H
|
||||
#define CONTRACT_H
|
||||
|
||||
/*
|
||||
Encapsulates the SLE for a Contract
|
||||
*/
|
||||
|
||||
class Contract
|
||||
{
|
||||
public:
|
||||
Contract ();
|
||||
|
||||
uint160& getIssuer ();
|
||||
uint160& getOwner ();
|
||||
STAmount& getRippleEscrow ();
|
||||
uint32 getEscrow ();
|
||||
uint32 getBond ();
|
||||
|
||||
Script::Data getData (int index);
|
||||
|
||||
void executeCreate ();
|
||||
void executeRemove ();
|
||||
void executeFund ();
|
||||
void executeAccept ();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
214
src/ripple_app/contracts/ripple_Interpreter.cpp
Normal file
214
src/ripple_app/contracts/ripple_Interpreter.cpp
Normal file
@@ -0,0 +1,214 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
/*
|
||||
We also need to charge for each op
|
||||
|
||||
*/
|
||||
|
||||
namespace Script
|
||||
{
|
||||
|
||||
Interpreter::Interpreter ()
|
||||
{
|
||||
mContract = NULL;
|
||||
mCode = NULL;
|
||||
mInstructionPointer = 0;
|
||||
mTotalFee = 0;
|
||||
|
||||
mInBlock = false;
|
||||
mBlockSuccess = true;
|
||||
mBlockJump = 0;
|
||||
|
||||
mFunctionTable.resize (NUM_OF_OPS);
|
||||
|
||||
mFunctionTable[INT_OP] = new IntOp ();
|
||||
mFunctionTable[FLOAT_OP] = new FloatOp ();
|
||||
mFunctionTable[UINT160_OP] = new Uint160Op ();
|
||||
mFunctionTable[BOOL_OP] = new Uint160Op ();
|
||||
mFunctionTable[PATH_OP] = new Uint160Op ();
|
||||
|
||||
mFunctionTable[ADD_OP] = new AddOp ();
|
||||
mFunctionTable[SUB_OP] = new SubOp ();
|
||||
mFunctionTable[MUL_OP] = new MulOp ();
|
||||
mFunctionTable[DIV_OP] = new DivOp ();
|
||||
mFunctionTable[MOD_OP] = new ModOp ();
|
||||
mFunctionTable[GTR_OP] = new GtrOp ();
|
||||
mFunctionTable[LESS_OP] = new LessOp ();
|
||||
mFunctionTable[EQUAL_OP] = new SubOp ();
|
||||
mFunctionTable[NOT_EQUAL_OP] = new SubOp ();
|
||||
mFunctionTable[AND_OP] = new SubOp ();
|
||||
mFunctionTable[OR_OP] = new SubOp ();
|
||||
mFunctionTable[NOT_OP] = new SubOp ();
|
||||
mFunctionTable[JUMP_OP] = new SubOp ();
|
||||
mFunctionTable[JUMPIF_OP] = new SubOp ();
|
||||
mFunctionTable[STOP_OP] = new SubOp ();
|
||||
mFunctionTable[CANCEL_OP] = new SubOp ();
|
||||
mFunctionTable[BLOCK_OP] = new SubOp ();
|
||||
mFunctionTable[BLOCK_END_OP] = new SubOp ();
|
||||
mFunctionTable[SEND_XRP_OP] = new SendXRPOp ();
|
||||
/*
|
||||
mFunctionTable[SEND_OP]=new SendOp();
|
||||
mFunctionTable[REMOVE_CONTRACT_OP]=new SubOp();
|
||||
mFunctionTable[FEE_OP]=new SubOp();
|
||||
mFunctionTable[CHANGE_CONTRACT_OWNER_OP]=new SubOp();
|
||||
mFunctionTable[STOP_REMOVE_OP]=new SubOp();
|
||||
mFunctionTable[SET_DATA_OP]=new SubOp();
|
||||
mFunctionTable[GET_DATA_OP]=new SubOp();
|
||||
mFunctionTable[GET_NUM_DATA_OP]=new SubOp();
|
||||
mFunctionTable[SET_REGISTER_OP]=new SubOp();
|
||||
mFunctionTable[GET_REGISTER_OP]=new SubOp();
|
||||
mFunctionTable[GET_ISSUER_ID_OP]=new SubOp();
|
||||
mFunctionTable[GET_OWNER_ID_OP]=new SubOp();
|
||||
mFunctionTable[GET_LEDGER_TIME_OP]=new SubOp();
|
||||
mFunctionTable[GET_LEDGER_NUM_OP]=new SubOp();
|
||||
mFunctionTable[GET_RAND_FLOAT_OP]=new SubOp();
|
||||
mFunctionTable[GET_XRP_ESCROWED_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_CURRENCY_OP]=new SubOp();
|
||||
mFunctionTable[GET_RIPPLE_ESCROWED_ISSUER]=new GetRippleEscrowedIssuerOp();
|
||||
mFunctionTable[GET_ACCEPT_DATA_OP]=new AcceptDataOp();
|
||||
mFunctionTable[GET_ACCEPTOR_ID_OP]=new GetAcceptorIDOp();
|
||||
mFunctionTable[GET_CONTRACT_ID_OP]=new GetContractIDOp();
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::popStack ()
|
||||
{
|
||||
if (mStack.size ())
|
||||
{
|
||||
Data::pointer item = mStack[mStack.size () - 1];
|
||||
mStack.pop_back ();
|
||||
return (item);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (Data::pointer (new ErrorData ()));
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::pushStack (Data::pointer data)
|
||||
{
|
||||
mStack.push_back (data);
|
||||
}
|
||||
|
||||
|
||||
// offset is where to jump to if the block fails
|
||||
bool Interpreter::startBlock (int offset)
|
||||
{
|
||||
if (mInBlock) return (false); // can't nest blocks
|
||||
|
||||
mBlockSuccess = true;
|
||||
mInBlock = true;
|
||||
mBlockJump = offset + mInstructionPointer;
|
||||
return (true);
|
||||
}
|
||||
|
||||
bool Interpreter::endBlock ()
|
||||
{
|
||||
if (!mInBlock) return (false);
|
||||
|
||||
mInBlock = false;
|
||||
mBlockJump = 0;
|
||||
pushStack (Data::pointer (new BoolData (mBlockSuccess)));
|
||||
return (true);
|
||||
}
|
||||
|
||||
TER Interpreter::interpret (Contract* contract, const SerializedTransaction& txn, Blob& code)
|
||||
{
|
||||
mContract = contract;
|
||||
mCode = &code;
|
||||
mTotalFee = 0;
|
||||
mInstructionPointer = 0;
|
||||
|
||||
while (mInstructionPointer < code.size ())
|
||||
{
|
||||
unsigned int fun = (*mCode)[mInstructionPointer];
|
||||
mInstructionPointer++;
|
||||
|
||||
if (fun >= mFunctionTable.size ())
|
||||
{
|
||||
// TODO: log
|
||||
return (temMALFORMED); // TODO: is this actually what we want to do?
|
||||
}
|
||||
|
||||
mTotalFee += mFunctionTable[ fun ]->getFee (); // FIXME: You can't use fees this way, there's no consensus
|
||||
|
||||
if (mTotalFee > txn.getTransactionFee ().getNValue ())
|
||||
{
|
||||
// TODO: log
|
||||
return (telINSUF_FEE_P);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mFunctionTable[ fun ]->work (this))
|
||||
{
|
||||
// TODO: log
|
||||
return (temMALFORMED); // TODO: is this actually what we want to do?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (tesSUCCESS);
|
||||
}
|
||||
|
||||
|
||||
Data::pointer Interpreter::getIntData ()
|
||||
{
|
||||
int value = 0; // TODO
|
||||
mInstructionPointer += 4;
|
||||
return (Data::pointer (new IntData (value)));
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getFloatData ()
|
||||
{
|
||||
float value = 0; // TODO
|
||||
mInstructionPointer += 4;
|
||||
return (Data::pointer (new FloatData (value)));
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getUint160Data ()
|
||||
{
|
||||
uint160 value; // TODO
|
||||
mInstructionPointer += 20;
|
||||
return (Data::pointer (new Uint160Data (value)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Interpreter::jumpTo (int offset)
|
||||
{
|
||||
mInstructionPointer += offset;
|
||||
|
||||
if ( (mInstructionPointer < 0) || (mInstructionPointer > mCode->size ()) )
|
||||
{
|
||||
mInstructionPointer -= offset;
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
void Interpreter::stop ()
|
||||
{
|
||||
mInstructionPointer = mCode->size ();
|
||||
}
|
||||
|
||||
Data::pointer Interpreter::getContractData (int index)
|
||||
{
|
||||
return (Data::pointer (new ErrorData ()));
|
||||
}
|
||||
|
||||
bool Interpreter::canSign (const uint160& signer)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // end namespace
|
||||
86
src/ripple_app/contracts/ripple_Interpreter.h
Normal file
86
src/ripple_app/contracts/ripple_Interpreter.h
Normal file
@@ -0,0 +1,86 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef INTERPRETER_H
|
||||
#define INTERPRETER_H
|
||||
|
||||
namespace Script
|
||||
{
|
||||
|
||||
class Operation;
|
||||
// Contracts are non typed have variable data types
|
||||
|
||||
class Interpreter
|
||||
{
|
||||
std::vector<Operation*> mFunctionTable;
|
||||
|
||||
std::vector<Data::pointer> mStack;
|
||||
|
||||
Contract* mContract;
|
||||
Blob* mCode;
|
||||
unsigned int mInstructionPointer;
|
||||
int mTotalFee;
|
||||
|
||||
bool mInBlock;
|
||||
int mBlockJump;
|
||||
bool mBlockSuccess;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
enum { INT_OP = 1, FLOAT_OP, UINT160_OP, BOOL_OP, PATH_OP,
|
||||
ADD_OP, SUB_OP, MUL_OP, DIV_OP, MOD_OP,
|
||||
GTR_OP, LESS_OP, EQUAL_OP, NOT_EQUAL_OP,
|
||||
AND_OP, OR_OP, NOT_OP,
|
||||
JUMP_OP, JUMPIF_OP,
|
||||
STOP_OP, CANCEL_OP,
|
||||
|
||||
BLOCK_OP, BLOCK_END_OP,
|
||||
SEND_XRP_OP, SEND_OP, REMOVE_CONTRACT_OP, FEE_OP, CHANGE_CONTRACT_OWNER_OP,
|
||||
STOP_REMOVE_OP,
|
||||
SET_DATA_OP, GET_DATA_OP, GET_NUM_DATA_OP,
|
||||
SET_REGISTER_OP, GET_REGISTER_OP,
|
||||
GET_ISSUER_ID_OP, GET_OWNER_ID_OP, GET_LEDGER_TIME_OP, GET_LEDGER_NUM_OP, GET_RAND_FLOAT_OP,
|
||||
GET_XRP_ESCROWED_OP, GET_RIPPLE_ESCROWED_OP, GET_RIPPLE_ESCROWED_CURRENCY_OP, GET_RIPPLE_ESCROWED_ISSUER,
|
||||
GET_ACCEPT_DATA_OP, GET_ACCEPTOR_ID_OP, GET_CONTRACT_ID_OP,
|
||||
NUM_OF_OPS
|
||||
};
|
||||
|
||||
Interpreter ();
|
||||
|
||||
// returns a TransactionEngineResult
|
||||
TER interpret (Contract* contract, const SerializedTransaction& txn, Blob& code);
|
||||
|
||||
void stop ();
|
||||
|
||||
bool canSign (const uint160& signer);
|
||||
|
||||
int getInstructionPointer ()
|
||||
{
|
||||
return (mInstructionPointer);
|
||||
}
|
||||
void setInstructionPointer (int n)
|
||||
{
|
||||
mInstructionPointer = n;
|
||||
}
|
||||
|
||||
Data::pointer popStack ();
|
||||
void pushStack (Data::pointer data);
|
||||
bool jumpTo (int offset);
|
||||
|
||||
bool startBlock (int offset);
|
||||
bool endBlock ();
|
||||
|
||||
Data::pointer getIntData ();
|
||||
Data::pointer getFloatData ();
|
||||
Data::pointer getUint160Data ();
|
||||
Data::pointer getAcceptData (int index);
|
||||
Data::pointer getContractData (int index);
|
||||
};
|
||||
|
||||
} // end namespace
|
||||
|
||||
#endif
|
||||
21
src/ripple_app/contracts/ripple_Operation.cpp
Normal file
21
src/ripple_app/contracts/ripple_Operation.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
/*
|
||||
We also need to charge for each op
|
||||
|
||||
*/
|
||||
|
||||
namespace Script
|
||||
{
|
||||
|
||||
|
||||
int Operation::getFee ()
|
||||
{
|
||||
return (getConfig ().FEE_CONTRACT_OPERATION);
|
||||
}
|
||||
|
||||
}
|
||||
367
src/ripple_app/contracts/ripple_Operation.h
Normal file
367
src/ripple_app/contracts/ripple_Operation.h
Normal file
@@ -0,0 +1,367 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef OPERATION_H
|
||||
#define OPERATION_H
|
||||
|
||||
namespace Script
|
||||
{
|
||||
|
||||
// Contracts are non typed have variable data types
|
||||
|
||||
|
||||
class Operation
|
||||
{
|
||||
public:
|
||||
// returns false if there was an error
|
||||
virtual bool work (Interpreter* interpreter) = 0;
|
||||
|
||||
virtual int getFee ();
|
||||
|
||||
virtual ~Operation ()
|
||||
{
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
// this is just an Int in the code
|
||||
class IntOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data = interpreter->getIntData ();
|
||||
|
||||
if (data->isInt32 ())
|
||||
{
|
||||
interpreter->pushStack ( data );
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class FloatOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data = interpreter->getFloatData ();
|
||||
|
||||
if (data->isFloat ())
|
||||
{
|
||||
interpreter->pushStack ( data );
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class Uint160Op : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data = interpreter->getUint160Data ();
|
||||
|
||||
if (data->isUint160 ())
|
||||
{
|
||||
interpreter->pushStack ( data );
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class AddOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
if (data1->isFloat () || data2->isFloat ()) interpreter->pushStack (Data::pointer (new FloatData (data1->getFloat () + data2->getFloat ())));
|
||||
else interpreter->pushStack (Data::pointer (new IntData (data1->getInt () + data2->getInt ())));
|
||||
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SubOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
if (data1->isFloat () || data2->isFloat ()) interpreter->pushStack (Data::pointer (new FloatData (data1->getFloat () - data2->getFloat ())));
|
||||
else interpreter->pushStack (Data::pointer (new IntData (data1->getInt () - data2->getInt ())));
|
||||
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MulOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
if (data1->isFloat () || data2->isFloat ()) interpreter->pushStack (Data::pointer (new FloatData (data1->getFloat ()*data2->getFloat ())));
|
||||
else interpreter->pushStack (Data::pointer (new IntData (data1->getInt ()*data2->getInt ())));
|
||||
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DivOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
if (data1->isFloat () || data2->isFloat ()) interpreter->pushStack (Data::pointer (new FloatData (data1->getFloat () / data2->getFloat ())));
|
||||
else interpreter->pushStack (Data::pointer (new IntData (data1->getInt () / data2->getInt ())));
|
||||
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GtrOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
interpreter->pushStack (Data::pointer (new BoolData (data1->getFloat () > data2->getFloat ())));
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class LessOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( (data1->isInt32 () || data1->isFloat ()) &&
|
||||
(data2->isInt32 () || data2->isFloat ()) )
|
||||
{
|
||||
interpreter->pushStack (Data::pointer (new FloatData (data1->getFloat () < data2->getFloat ())));
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ModOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data1 = interpreter->popStack ();
|
||||
Data::pointer data2 = interpreter->popStack ();
|
||||
|
||||
if ( data1->isInt32 () && data2->isInt32 () )
|
||||
{
|
||||
interpreter->pushStack (Data::pointer (new IntData (data1->getInt () % data2->getInt ())));
|
||||
return (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class StartBlockOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset = interpreter->getIntData ();
|
||||
return (interpreter->startBlock (offset->getInt ()));
|
||||
}
|
||||
};
|
||||
|
||||
class EndBlockOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
return (interpreter->endBlock ());
|
||||
}
|
||||
};
|
||||
|
||||
class StopOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
interpreter->stop ();
|
||||
return (true);
|
||||
}
|
||||
};
|
||||
|
||||
class AcceptDataOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer data = interpreter->popStack ();
|
||||
|
||||
if (data->isInt32 ())
|
||||
{
|
||||
interpreter->pushStack ( interpreter->getAcceptData (data->getInt ()) );
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class JumpIfOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset = interpreter->getIntData ();
|
||||
Data::pointer cond = interpreter->popStack ();
|
||||
|
||||
if (cond->isBool () && offset->isInt32 ())
|
||||
{
|
||||
if (cond->isTrue ())
|
||||
{
|
||||
return (interpreter->jumpTo (offset->getInt ()));
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class JumpOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer offset = interpreter->getIntData ();
|
||||
|
||||
if (offset->isInt32 ())
|
||||
{
|
||||
return (interpreter->jumpTo (offset->getInt ()));
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class SendXRPOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer sourceID = interpreter->popStack ();
|
||||
Data::pointer destID = interpreter->popStack ();
|
||||
Data::pointer amount = interpreter->popStack ();
|
||||
|
||||
if (sourceID->isUint160 () && destID->isUint160 () && amount->isInt32 () && interpreter->canSign (sourceID->getUint160 ()))
|
||||
{
|
||||
// make sure:
|
||||
// source is either, this contract, issuer, or acceptor
|
||||
|
||||
// TODO do the send
|
||||
//interpreter->pushStack( send result);
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
class GetDataOp : public Operation
|
||||
{
|
||||
public:
|
||||
bool work (Interpreter* interpreter)
|
||||
{
|
||||
Data::pointer index = interpreter->popStack ();
|
||||
|
||||
if (index->isInt32 ())
|
||||
{
|
||||
interpreter->pushStack ( interpreter->getContractData (index->getInt ()));
|
||||
return (true);
|
||||
}
|
||||
|
||||
return (false);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
5
src/ripple_app/contracts/ripple_ScriptData.cpp
Normal file
5
src/ripple_app/contracts/ripple_ScriptData.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
171
src/ripple_app/contracts/ripple_ScriptData.h
Normal file
171
src/ripple_app/contracts/ripple_ScriptData.h
Normal file
@@ -0,0 +1,171 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef SCRIPT_DATA_H
|
||||
#define SCRIPT_DATA_H
|
||||
|
||||
namespace Script
|
||||
{
|
||||
class Data
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<Data> pointer;
|
||||
|
||||
virtual ~Data ()
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
virtual bool isInt32 ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
virtual bool isFloat ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
virtual bool isUint160 ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
virtual bool isError ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
virtual bool isTrue ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
virtual bool isBool ()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
//virtual bool isBlockEnd(){ return(false); }
|
||||
|
||||
virtual int getInt ()
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
virtual float getFloat ()
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
virtual uint160 getUint160 ()
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
//virtual bool isCurrency(){ return(false); }
|
||||
};
|
||||
|
||||
class IntData : public Data
|
||||
{
|
||||
int mValue;
|
||||
public:
|
||||
IntData (int value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
bool isInt32 ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
int getInt ()
|
||||
{
|
||||
return (mValue);
|
||||
}
|
||||
float getFloat ()
|
||||
{
|
||||
return ((float)mValue);
|
||||
}
|
||||
bool isTrue ()
|
||||
{
|
||||
return (mValue != 0);
|
||||
}
|
||||
};
|
||||
|
||||
class FloatData : public Data
|
||||
{
|
||||
float mValue;
|
||||
public:
|
||||
FloatData (float value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
bool isFloat ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
float getFloat ()
|
||||
{
|
||||
return (mValue);
|
||||
}
|
||||
bool isTrue ()
|
||||
{
|
||||
return (mValue != 0);
|
||||
}
|
||||
};
|
||||
|
||||
class Uint160Data : public Data
|
||||
{
|
||||
uint160 mValue;
|
||||
public:
|
||||
Uint160Data (uint160 value) : mValue (value)
|
||||
{
|
||||
;
|
||||
}
|
||||
bool isUint160 ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
uint160 getUint160 ()
|
||||
{
|
||||
return (mValue);
|
||||
}
|
||||
};
|
||||
|
||||
class BoolData : public Data
|
||||
{
|
||||
bool mValue;
|
||||
public:
|
||||
BoolData (bool value)
|
||||
{
|
||||
mValue = value;
|
||||
}
|
||||
bool isBool ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
bool isTrue ()
|
||||
{
|
||||
return (mValue);
|
||||
}
|
||||
};
|
||||
|
||||
class ErrorData : public Data
|
||||
{
|
||||
public:
|
||||
bool isError ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
};
|
||||
|
||||
class BlockEndData : public Data
|
||||
{
|
||||
public:
|
||||
bool isBlockEnd ()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
343
src/ripple_app/data/ripple_DBInit.cpp
Normal file
343
src/ripple_app/data/ripple_DBInit.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// Transaction database holds transactions and public keys
|
||||
const char* TxnDBInit[] =
|
||||
{
|
||||
"PRAGMA synchronous=NORMAL;",
|
||||
"PRAGMA journal_mode=WAL;",
|
||||
"PRAGMA journal_size_limit=1582080;",
|
||||
|
||||
#if (ULONG_MAX > UINT_MAX) && !defined (NO_SQLITE_MMAP)
|
||||
"PRAGMA mmap_size=17179869184;",
|
||||
#endif
|
||||
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE Transactions ( \
|
||||
TransID CHARACTER(64) PRIMARY KEY, \
|
||||
TransType CHARACTER(24), \
|
||||
FromAcct CHARACTER(35), \
|
||||
FromSeq BIGINT UNSIGNED, \
|
||||
LedgerSeq BIGINT UNSIGNED, \
|
||||
Status CHARACTER(1), \
|
||||
RawTxn BLOB, \
|
||||
TxnMeta BLOB \
|
||||
);",
|
||||
"CREATE INDEX TxLgrIndex ON \
|
||||
Transactions(LedgerSeq);",
|
||||
|
||||
"CREATE TABLE AccountTransactions ( \
|
||||
TransID CHARACTER(64), \
|
||||
Account CHARACTER(64), \
|
||||
LedgerSeq BIGINT UNSIGNED, \
|
||||
TxnSeq INTEGER \
|
||||
);",
|
||||
"CREATE INDEX AcctTxIDIndex ON \
|
||||
AccountTransactions(TransID);",
|
||||
"CREATE INDEX AcctTxIndex ON \
|
||||
AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);",
|
||||
"CREATE INDEX AcctLgrIndex ON \
|
||||
AccountTransactions(LedgerSeq, Account, TransID);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int TxnDBCount = NUMBER (TxnDBInit);
|
||||
|
||||
// Ledger database holds ledgers and ledger confirmations
|
||||
const char* LedgerDBInit[] =
|
||||
{
|
||||
"PRAGMA synchronous=NORMAL;",
|
||||
"PRAGMA journal_mode=WAL;",
|
||||
"PRAGMA journal_size_limit=1582080;",
|
||||
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE Ledgers ( \
|
||||
LedgerHash CHARACTER(64) PRIMARY KEY, \
|
||||
LedgerSeq BIGINT UNSIGNED, \
|
||||
PrevHash CHARACTER(64), \
|
||||
TotalCoins BIGINT UNSIGNED, \
|
||||
ClosingTime BIGINT UNSIGNED, \
|
||||
PrevClosingTime BIGINT UNSIGNED, \
|
||||
CloseTimeRes BIGINT UNSIGNED, \
|
||||
CloseFlags BIGINT UNSIGNED, \
|
||||
AccountSetHash CHARACTER(64), \
|
||||
TransSetHash CHARACTER(64) \
|
||||
);",
|
||||
"CREATE INDEX SeqLedger ON Ledgers(LedgerSeq);",
|
||||
|
||||
"CREATE TABLE Validations ( \
|
||||
LedgerHash CHARACTER(64), \
|
||||
NodePubKey CHARACTER(56), \
|
||||
SignTime BIGINT UNSIGNED, \
|
||||
RawData BLOB \
|
||||
);",
|
||||
"CREATE INDEX ValidationsByHash ON \
|
||||
Validations(LedgerHash);",
|
||||
"CREATE INDEX ValidationsByTime ON \
|
||||
Validations(SignTime);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int LedgerDBCount = NUMBER (LedgerDBInit);
|
||||
|
||||
// RPC database holds persistent data for RPC clients.
|
||||
const char* RpcDBInit[] =
|
||||
{
|
||||
|
||||
// Local persistence of the RPC client
|
||||
"CREATE TABLE RPCData ( \
|
||||
Key TEXT PRIMARY Key, \
|
||||
Value TEXT \
|
||||
);",
|
||||
};
|
||||
|
||||
int RpcDBCount = NUMBER (RpcDBInit);
|
||||
|
||||
// NodeIdentity database holds local accounts and trusted nodes
|
||||
// VFALCO NOTE but its a table not a database, so...?
|
||||
//
|
||||
const char* WalletDBInit[] =
|
||||
{
|
||||
// Node identity must be persisted for CAS routing and responsibilities.
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE NodeIdentity ( \
|
||||
PublicKey CHARACTER(53), \
|
||||
PrivateKey CHARACTER(52), \
|
||||
Dh512 TEXT, \
|
||||
Dh1024 TEXT \
|
||||
);",
|
||||
|
||||
// Miscellaneous persistent information
|
||||
// Integer: 1 : Used to simplify SQL.
|
||||
// ScoreUpdated: when scores was last updated.
|
||||
// FetchUpdated: when last fetch succeeded.
|
||||
"CREATE TABLE Misc ( \
|
||||
Magic INTEGER UNIQUE NOT NULL, \
|
||||
ScoreUpdated DATETIME, \
|
||||
FetchUpdated DATETIME \
|
||||
);",
|
||||
|
||||
// Scoring and other information for domains.
|
||||
//
|
||||
// Domain:
|
||||
// Domain source for https.
|
||||
// PublicKey:
|
||||
// Set if ever succeeded.
|
||||
// XXX Use NULL in place of ""
|
||||
// Source:
|
||||
// 'M' = Manually added. : 1500
|
||||
// 'V' = validators.txt : 1000
|
||||
// 'W' = Web browsing. : 200
|
||||
// 'R' = Referral : 0
|
||||
// Next:
|
||||
// Time of next fetch attempt.
|
||||
// Scan:
|
||||
// Time of last fetch attempt.
|
||||
// Fetch:
|
||||
// Time of last successful fetch.
|
||||
// Sha256:
|
||||
// Checksum of last fetch.
|
||||
// Comment:
|
||||
// User supplied comment.
|
||||
// Table of Domains user has asked to trust.
|
||||
"CREATE TABLE SeedDomains ( \
|
||||
Domain TEXT PRIMARY KEY NOT NULL, \
|
||||
PublicKey CHARACTER(53), \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
Next DATETIME, \
|
||||
Scan DATETIME, \
|
||||
Fetch DATETIME, \
|
||||
Sha256 CHARACTER[64], \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// Allow us to easily find the next SeedDomain to fetch.
|
||||
"CREATE INDEX SeedDomainNext ON SeedDomains (Next);",
|
||||
|
||||
// Table of PublicKeys user has asked to trust.
|
||||
// Fetches are made to the CAS. This gets the ripple.txt so even validators without a web server can publish a ripple.txt.
|
||||
// Source:
|
||||
// 'M' = Manually added. : 1500
|
||||
// 'V' = validators.txt : 1000
|
||||
// 'W' = Web browsing. : 200
|
||||
// 'R' = Referral : 0
|
||||
// Next:
|
||||
// Time of next fetch attempt.
|
||||
// Scan:
|
||||
// Time of last fetch attempt.
|
||||
// Fetch:
|
||||
// Time of last successful fetch.
|
||||
// Sha256:
|
||||
// Checksum of last fetch.
|
||||
// Comment:
|
||||
// User supplied comment.
|
||||
"CREATE TABLE SeedNodes ( \
|
||||
PublicKey CHARACTER(53) PRIMARY KEY NOT NULL, \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
Next DATETIME, \
|
||||
Scan DATETIME, \
|
||||
Fetch DATETIME, \
|
||||
Sha256 CHARACTER[64], \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// Allow us to easily find the next SeedNode to fetch.
|
||||
"CREATE INDEX SeedNodeNext ON SeedNodes (Next);",
|
||||
|
||||
// Nodes we trust to not grossly collude against us. Derived from SeedDomains, SeedNodes, and ValidatorReferrals.
|
||||
//
|
||||
// Score:
|
||||
// Computed trust score. Higher is better.
|
||||
// Seen:
|
||||
// Last validation received.
|
||||
"CREATE TABLE TrustedNodes ( \
|
||||
PublicKey CHARACTER(53) PRIMARY KEY NOT NULL, \
|
||||
Score INTEGER DEFAULT 0 NOT NULL, \
|
||||
Seen DATETIME, \
|
||||
Comment TEXT \
|
||||
);",
|
||||
|
||||
// List of referrals.
|
||||
// - There may be multiple sources for a Validator. The last source is used.
|
||||
// Validator:
|
||||
// Public key of referrer.
|
||||
// Entry:
|
||||
// Entry index in [validators] table.
|
||||
// Referral:
|
||||
// This is the form provided by the ripple.txt:
|
||||
// - Public key for CAS based referral.
|
||||
// - Domain for domain based referral.
|
||||
// XXX Do garbage collection when validators have no references.
|
||||
"CREATE TABLE ValidatorReferrals ( \
|
||||
Validator CHARACTER(53) NOT NULL, \
|
||||
Entry INTEGER NOT NULL, \
|
||||
Referral TEXT NOT NULL, \
|
||||
PRIMARY KEY (Validator,Entry) \
|
||||
);",
|
||||
|
||||
// List of referrals from ripple.txt files.
|
||||
// Validator:
|
||||
// Public key of referree.
|
||||
// Entry:
|
||||
// Entry index in [validators] table.
|
||||
// IP:
|
||||
// IP of referred.
|
||||
// Port:
|
||||
// -1 = Default
|
||||
// XXX Do garbage collection when ips have no references.
|
||||
"CREATE TABLE IpReferrals ( \
|
||||
Validator CHARACTER(53) NOT NULL, \
|
||||
Entry INTEGER NOT NULL, \
|
||||
IP TEXT NOT NULL, \
|
||||
Port INTEGER NOT NULL DEFAULT -1, \
|
||||
PRIMARY KEY (Validator,Entry) \
|
||||
);",
|
||||
|
||||
// Table of IPs to contact the network.
|
||||
// IP:
|
||||
// IP address to contact.
|
||||
// Port:
|
||||
// Port to contact.
|
||||
// -1 = Default
|
||||
// Score:
|
||||
// Computed trust score. Higher is better.
|
||||
// Source:
|
||||
// 'C' = Configuration file
|
||||
// 'V' = Validation file
|
||||
// 'M' = Manually added.
|
||||
// 'I' = Inbound connection.
|
||||
// 'T' = Told by other peer
|
||||
// 'O' = Other.
|
||||
// ScanNext:
|
||||
// When to next scan. Null=not scanning.
|
||||
// ScanInterval:
|
||||
// Delay between scans.
|
||||
"CREATE TABLE PeerIps ( \
|
||||
IpPort TEXT NOT NULL PRIMARY KEY, \
|
||||
Score INTEGER NOT NULL DEFAULT 0, \
|
||||
Source CHARACTER(1) NOT NULL, \
|
||||
ScanNext DATETIME DEFAULT 0, \
|
||||
ScanInterval INTEGER NOT NULL DEFAULT 0 \
|
||||
);",
|
||||
|
||||
"CREATE INDEX PeerScanIndex ON \
|
||||
PeerIps(ScanNext);",
|
||||
|
||||
"CREATE TABLE Features ( \
|
||||
Hash CHARACTER(64) PRIMARY KEY, \
|
||||
FirstMajority BIGINT UNSIGNED, \
|
||||
LastMajority BIGINT UNSIGNED \
|
||||
);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
int WalletDBCount = NUMBER (WalletDBInit);
|
||||
|
||||
// Hash node database holds nodes indexed by hash
|
||||
// VFALCO TODO Remove this since it looks unused
|
||||
/*
|
||||
|
||||
int HashNodeDBCount = NUMBER (HashNodeDBInit);
|
||||
*/
|
||||
|
||||
// Net node database holds nodes seen on the network
|
||||
// XXX Not really used needs replacement.
|
||||
/*
|
||||
const char* NetNodeDBInit[] =
|
||||
{
|
||||
"CREATE TABLE KnownNodes ( \
|
||||
Hanko CHARACTER(35) PRIMARY KEY, \
|
||||
LastSeen TEXT, \
|
||||
HaveContactInfo CHARACTER(1), \
|
||||
ContactObject BLOB \
|
||||
);"
|
||||
};
|
||||
|
||||
int NetNodeDBCount = NUMBER (NetNodeDBInit);
|
||||
*/
|
||||
|
||||
// This appears to be unused
|
||||
/*
|
||||
const char* PathFindDBInit[] =
|
||||
{
|
||||
"PRAGMA synchronous = OFF; ",
|
||||
|
||||
"DROP TABLE TrustLines; ",
|
||||
|
||||
"CREATE TABLE TrustLines { "
|
||||
"To CHARACTER(40), " // Hex of account trusted
|
||||
"By CHARACTER(40), " // Hex of account trusting
|
||||
"Currency CHARACTER(80), " // Hex currency, hex issuer
|
||||
"Use INTEGER, " // Use count
|
||||
"Seq BIGINT UNSIGNED " // Sequence when use count was updated
|
||||
"}; ",
|
||||
|
||||
"CREATE INDEX TLBy ON TrustLines(By, Currency, Use);",
|
||||
"CREATE INDEX TLTo ON TrustLines(To, Currency, Use);",
|
||||
|
||||
"DROP TABLE Exchanges;",
|
||||
|
||||
"CREATE TABLE Exchanges { "
|
||||
"From CHARACTER(80), "
|
||||
"To CHARACTER(80), "
|
||||
"Currency CHARACTER(80), "
|
||||
"Use INTEGER, "
|
||||
"Seq BIGINT UNSIGNED "
|
||||
"}; ",
|
||||
|
||||
"CREATE INDEX ExBy ON Exchanges(By, Currency, Use);",
|
||||
"CREATE INDEX ExTo ON Exchanges(To, Currency, Use);",
|
||||
};
|
||||
|
||||
int PathFindDBCount = NUMBER (PathFindDBInit);
|
||||
*/
|
||||
|
||||
22
src/ripple_app/data/ripple_DBInit.h
Normal file
22
src/ripple_app/data/ripple_DBInit.h
Normal file
@@ -0,0 +1,22 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_DBINIT_H_INCLUDED
|
||||
#define RIPPLE_DBINIT_H_INCLUDED
|
||||
|
||||
// VFALCO TODO Tidy these up into a class with functions and return types.
|
||||
extern const char* RpcDBInit[];
|
||||
extern const char* TxnDBInit[];
|
||||
extern const char* LedgerDBInit[];
|
||||
extern const char* WalletDBInit[];
|
||||
|
||||
// VFALCO TODO Figure out what these counts are for
|
||||
extern int RpcDBCount;
|
||||
extern int TxnDBCount;
|
||||
extern int LedgerDBCount;
|
||||
extern int WalletDBCount;
|
||||
|
||||
#endif
|
||||
193
src/ripple_app/data/ripple_Database.cpp
Normal file
193
src/ripple_app/data/ripple_Database.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
Database::Database (const char* host)
|
||||
: mNumCol (0)
|
||||
{
|
||||
mHost = host;
|
||||
}
|
||||
|
||||
Database::~Database ()
|
||||
{
|
||||
}
|
||||
|
||||
bool Database::getNull (const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getNull (index);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* Database::getStr (const char* colName, std::string& retStr)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getStr (index, retStr);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32 Database::getInt (const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getInt (index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
float Database::getFloat (const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getFloat (index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Database::getBool (const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getBool (index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Database::getBinary (const char* colName, unsigned char* buf, int maxSize)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return (getBinary (index, buf, maxSize));
|
||||
}
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
Blob Database::getBinary (const std::string& strColName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (strColName.c_str (), &index))
|
||||
{
|
||||
return getBinary (index);
|
||||
}
|
||||
|
||||
return Blob ();
|
||||
}
|
||||
|
||||
std::string Database::getStrBinary (const std::string& strColName)
|
||||
{
|
||||
// YYY Could eliminate a copy if getStrBinary was a template.
|
||||
return strCopy (getBinary (strColName.c_str ()));
|
||||
}
|
||||
|
||||
uint64 Database::getBigInt (const char* colName)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (getColNumber (colName, &index))
|
||||
{
|
||||
return getBigInt (index);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// returns false if can't find col
|
||||
bool Database::getColNumber (const char* colName, int* retIndex)
|
||||
{
|
||||
for (unsigned int n = 0; n < mColNameTable.size (); n++)
|
||||
{
|
||||
if (strcmp (colName, mColNameTable[n].c_str ()) == 0)
|
||||
{
|
||||
*retIndex = n;
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int Database::getSingleDBValueInt (const char* sql)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ( executeSQL (sql) && startIterRows ()
|
||||
{
|
||||
ret = getInt (0);
|
||||
endIterRows ();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret = 0;
|
||||
}
|
||||
return (ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
float Database::getSingleDBValueFloat (const char* sql)
|
||||
{
|
||||
float ret;
|
||||
|
||||
if (executeSQL (sql) && startIterRows () && getNextRow ())
|
||||
{
|
||||
ret = getFloat (0);
|
||||
endIterRows ();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
char* Database::getSingleDBValueStr (const char* sql, std::string& retStr)
|
||||
{
|
||||
char* ret;
|
||||
|
||||
if (executeSQL (sql) && startIterRows ())
|
||||
{
|
||||
ret = getStr (0, retStr);
|
||||
endIterRows ();
|
||||
}
|
||||
else
|
||||
{
|
||||
//theUI->statusMsg("ERROR with database: %s",sql);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return (ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
107
src/ripple_app/data/ripple_Database.h
Normal file
107
src/ripple_app/data/ripple_Database.h
Normal file
@@ -0,0 +1,107 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_DATABASE_RIPPLEHEADER
|
||||
#define RIPPLE_DATABASE_RIPPLEHEADER
|
||||
|
||||
// VFALCO Get rid of these macros
|
||||
//
|
||||
#define SQL_FOREACH(_db, _strQuery) \
|
||||
if ((_db)->executeSQL(_strQuery)) \
|
||||
for (bool _bMore = (_db)->startIterRows(); _bMore; _bMore = (_db)->getNextRow())
|
||||
|
||||
#define SQL_EXISTS(_db, _strQuery) \
|
||||
((_db)->executeSQL(_strQuery) && (_db)->startIterRows())
|
||||
|
||||
/*
|
||||
this maintains the connection to the database
|
||||
*/
|
||||
|
||||
class SqliteDatabase;
|
||||
class JobQueue;
|
||||
|
||||
class Database
|
||||
{
|
||||
public:
|
||||
explicit Database (const char* host);
|
||||
|
||||
virtual ~Database ();
|
||||
|
||||
virtual void connect () = 0;
|
||||
|
||||
virtual void disconnect () = 0;
|
||||
|
||||
// returns true if the query went ok
|
||||
virtual bool executeSQL (const char* sql, bool fail_okay = false) = 0;
|
||||
|
||||
bool executeSQL (std::string strSql, bool fail_okay = false)
|
||||
{
|
||||
return executeSQL (strSql.c_str (), fail_okay);
|
||||
}
|
||||
|
||||
// returns false if there are no results
|
||||
virtual bool startIterRows (bool finalize = true) = 0;
|
||||
virtual void endIterRows () = 0;
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
virtual bool getNextRow (bool finalize = true) = 0;
|
||||
|
||||
// get Data from the current row
|
||||
bool getNull (const char* colName);
|
||||
char* getStr (const char* colName, std::string& retStr);
|
||||
std::string getStrBinary (const std::string& strColName);
|
||||
int32 getInt (const char* colName);
|
||||
float getFloat (const char* colName);
|
||||
bool getBool (const char* colName);
|
||||
|
||||
// returns amount stored in buf
|
||||
int getBinary (const char* colName, unsigned char* buf, int maxSize);
|
||||
Blob getBinary (const std::string& strColName);
|
||||
|
||||
uint64 getBigInt (const char* colName);
|
||||
|
||||
virtual bool getNull (int colIndex) = 0;
|
||||
virtual char* getStr (int colIndex, std::string& retStr) = 0;
|
||||
virtual int32 getInt (int colIndex) = 0;
|
||||
virtual float getFloat (int colIndex) = 0;
|
||||
virtual bool getBool (int colIndex) = 0;
|
||||
virtual int getBinary (int colIndex, unsigned char* buf, int maxSize) = 0;
|
||||
virtual uint64 getBigInt (int colIndex) = 0;
|
||||
virtual Blob getBinary (int colIndex) = 0;
|
||||
|
||||
// int getSingleDBValueInt(const char* sql);
|
||||
// float getSingleDBValueFloat(const char* sql);
|
||||
// char* getSingleDBValueStr(const char* sql, std::string& retStr);
|
||||
|
||||
virtual bool setupCheckpointing (JobQueue*)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual SqliteDatabase* getSqliteDB ()
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
virtual int getKBUsedAll ()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
virtual int getKBUsedDB ()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool getColNumber (const char* colName, int* retIndex);
|
||||
|
||||
int mNumCol;
|
||||
std::string mHost;
|
||||
std::vector <std::string> mColNameTable;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
33
src/ripple_app/data/ripple_DatabaseCon.cpp
Normal file
33
src/ripple_app/data/ripple_DatabaseCon.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
int DatabaseCon::sCount = 0;
|
||||
|
||||
DatabaseCon::DatabaseCon (const std::string& strName, const char* initStrings[], int initCount)
|
||||
{
|
||||
++sCount;
|
||||
|
||||
// VFALCO TODO remove this dependency on the config by making it the caller's
|
||||
// responsibility to pass in the path. Add a member function to Application
|
||||
// or Config to compute this path.
|
||||
//
|
||||
boost::filesystem::path pPath = (getConfig ().RUN_STANDALONE &&
|
||||
((getConfig ().START_UP != Config::LOAD) && (getConfig ().START_UP != Config::REPLAY)))
|
||||
? "" // Use temporary files.
|
||||
: (getConfig ().DATA_DIR / strName); // Use regular db files.
|
||||
|
||||
mDatabase = new SqliteDatabase (pPath.string ().c_str ());
|
||||
mDatabase->connect ();
|
||||
|
||||
for (int i = 0; i < initCount; ++i)
|
||||
mDatabase->executeSQL (initStrings[i], true);
|
||||
}
|
||||
|
||||
DatabaseCon::~DatabaseCon ()
|
||||
{
|
||||
mDatabase->disconnect ();
|
||||
delete mDatabase;
|
||||
}
|
||||
38
src/ripple_app/data/ripple_DatabaseCon.h
Normal file
38
src/ripple_app/data/ripple_DatabaseCon.h
Normal file
@@ -0,0 +1,38 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_DATABASECON_H
|
||||
#define RIPPLE_DATABASECON_H
|
||||
|
||||
// VFALCO NOTE This looks like a pointless class. Figure out
|
||||
// what purpose it is really trying to serve and do it better.
|
||||
class DatabaseCon : LeakChecked <DatabaseCon>
|
||||
{
|
||||
public:
|
||||
DatabaseCon (const std::string& name, const char* initString[], int countInit);
|
||||
~DatabaseCon ();
|
||||
Database* getDB ()
|
||||
{
|
||||
return mDatabase;
|
||||
}
|
||||
DeprecatedRecursiveMutex& getDBLock ()
|
||||
{
|
||||
return mLock;
|
||||
}
|
||||
static int getCount ()
|
||||
{
|
||||
return sCount;
|
||||
}
|
||||
|
||||
// VFALCO TODO change "protected" to "private" throughout the code
|
||||
private:
|
||||
Database* mDatabase;
|
||||
DeprecatedRecursiveMutex mLock;
|
||||
static int sCount;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
450
src/ripple_app/data/ripple_SqliteDatabase.cpp
Normal file
450
src/ripple_app/data/ripple_SqliteDatabase.cpp
Normal file
@@ -0,0 +1,450 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (SqliteDatabase)
|
||||
|
||||
SqliteStatement::SqliteStatement (SqliteDatabase* db, const char* sql, bool aux)
|
||||
{
|
||||
assert (db);
|
||||
|
||||
sqlite3* conn = aux ? db->getAuxConnection () : db->peekConnection ();
|
||||
int j = sqlite3_prepare_v2 (conn, sql, strlen (sql) + 1, &statement, NULL);
|
||||
|
||||
if (j != SQLITE_OK)
|
||||
throw j;
|
||||
}
|
||||
|
||||
SqliteStatement::SqliteStatement (SqliteDatabase* db, const std::string& sql, bool aux)
|
||||
{
|
||||
assert (db);
|
||||
|
||||
sqlite3* conn = aux ? db->getAuxConnection () : db->peekConnection ();
|
||||
int j = sqlite3_prepare_v2 (conn, sql.c_str (), sql.size () + 1, &statement, NULL);
|
||||
|
||||
if (j != SQLITE_OK)
|
||||
throw j;
|
||||
}
|
||||
|
||||
SqliteStatement::~SqliteStatement ()
|
||||
{
|
||||
sqlite3_finalize (statement);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
SqliteDatabase::SqliteDatabase (const char* host)
|
||||
: Database (host)
|
||||
, m_walMutex (this, "SqliteDB", __FILE__, __LINE__)
|
||||
, m_thread ("sqlitedb")
|
||||
, mWalQ (NULL)
|
||||
, walRunning (false)
|
||||
{
|
||||
m_thread.start ();
|
||||
|
||||
mConnection = NULL;
|
||||
mAuxConnection = NULL;
|
||||
mCurrentStmt = NULL;
|
||||
}
|
||||
|
||||
void SqliteDatabase::connect ()
|
||||
{
|
||||
int rc = sqlite3_open (mHost.c_str (), &mConnection);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
WriteLog (lsFATAL, SqliteDatabase) << "Can't open " << mHost << " " << rc;
|
||||
sqlite3_close (mConnection);
|
||||
assert ((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED));
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3* SqliteDatabase::getAuxConnection ()
|
||||
{
|
||||
ScopedLockType sl (m_walMutex, __FILE__, __LINE__);
|
||||
|
||||
if (mAuxConnection == NULL)
|
||||
{
|
||||
int rc = sqlite3_open (mHost.c_str (), &mAuxConnection);
|
||||
|
||||
if (rc)
|
||||
{
|
||||
WriteLog (lsFATAL, SqliteDatabase) << "Can't aux open " << mHost << " " << rc;
|
||||
assert ((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED));
|
||||
|
||||
if (mAuxConnection != NULL)
|
||||
{
|
||||
sqlite3_close (mConnection);
|
||||
mAuxConnection = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mAuxConnection;
|
||||
}
|
||||
|
||||
void SqliteDatabase::disconnect ()
|
||||
{
|
||||
sqlite3_finalize (mCurrentStmt);
|
||||
sqlite3_close (mConnection);
|
||||
|
||||
if (mAuxConnection != NULL)
|
||||
sqlite3_close (mAuxConnection);
|
||||
}
|
||||
|
||||
// returns true if the query went ok
|
||||
bool SqliteDatabase::executeSQL (const char* sql, bool fail_ok)
|
||||
{
|
||||
#ifdef DEBUG_HANGING_LOCKS
|
||||
assert (fail_ok || (mCurrentStmt == NULL));
|
||||
#endif
|
||||
|
||||
sqlite3_finalize (mCurrentStmt);
|
||||
|
||||
int rc = sqlite3_prepare_v2 (mConnection, sql, -1, &mCurrentStmt, NULL);
|
||||
|
||||
if (SQLITE_OK != rc)
|
||||
{
|
||||
if (!fail_ok)
|
||||
{
|
||||
#ifdef BEAST_DEBUG
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "Perror:" << mHost << ": " << rc;
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "Statement: " << sql;
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "Error: " << sqlite3_errmsg (mConnection);
|
||||
#endif
|
||||
}
|
||||
|
||||
endIterRows ();
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_step (mCurrentStmt);
|
||||
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
mMoreRows = true;
|
||||
}
|
||||
else if (rc == SQLITE_DONE)
|
||||
{
|
||||
endIterRows ();
|
||||
mMoreRows = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED))
|
||||
{
|
||||
WriteLog (lsFATAL, SqliteDatabase) << mHost << " returns error " << rc << ": " << sqlite3_errmsg (mConnection);
|
||||
assert (false);
|
||||
}
|
||||
|
||||
mMoreRows = false;
|
||||
|
||||
if (!fail_ok)
|
||||
{
|
||||
#ifdef BEAST_DEBUG
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "SQL Serror:" << mHost << ": " << rc;
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "Statement: " << sql;
|
||||
WriteLog (lsWARNING, SqliteDatabase) << "Error: " << sqlite3_errmsg (mConnection);
|
||||
#endif
|
||||
}
|
||||
|
||||
endIterRows ();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns false if there are no results
|
||||
bool SqliteDatabase::startIterRows (bool finalize)
|
||||
{
|
||||
mColNameTable.clear ();
|
||||
mColNameTable.resize (sqlite3_column_count (mCurrentStmt));
|
||||
|
||||
for (unsigned n = 0; n < mColNameTable.size (); n++)
|
||||
{
|
||||
mColNameTable[n] = sqlite3_column_name (mCurrentStmt, n);
|
||||
}
|
||||
|
||||
if (!mMoreRows && finalize)
|
||||
endIterRows ();
|
||||
|
||||
return (mMoreRows);
|
||||
}
|
||||
|
||||
void SqliteDatabase::endIterRows ()
|
||||
{
|
||||
sqlite3_finalize (mCurrentStmt);
|
||||
mCurrentStmt = NULL;
|
||||
}
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool SqliteDatabase::getNextRow (bool finalize)
|
||||
{
|
||||
if (mMoreRows)
|
||||
{
|
||||
int rc = sqlite3_step (mCurrentStmt);
|
||||
|
||||
if (rc == SQLITE_ROW)
|
||||
return (true);
|
||||
|
||||
assert ((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED));
|
||||
CondLog ((rc != SQLITE_DONE), lsWARNING, SqliteDatabase) << "Rerror: " << mHost << ": " << rc;
|
||||
}
|
||||
|
||||
if (finalize)
|
||||
endIterRows ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SqliteDatabase::getNull (int colIndex)
|
||||
{
|
||||
return (SQLITE_NULL == sqlite3_column_type (mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
char* SqliteDatabase::getStr (int colIndex, std::string& retStr)
|
||||
{
|
||||
const char* text = reinterpret_cast<const char*> (sqlite3_column_text (mCurrentStmt, colIndex));
|
||||
retStr = (text == NULL) ? "" : text;
|
||||
return const_cast<char*> (retStr.c_str ());
|
||||
}
|
||||
|
||||
int32 SqliteDatabase::getInt (int colIndex)
|
||||
{
|
||||
return (sqlite3_column_int (mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
float SqliteDatabase::getFloat (int colIndex)
|
||||
{
|
||||
return (static_cast <float> (sqlite3_column_double (mCurrentStmt, colIndex)));
|
||||
}
|
||||
|
||||
bool SqliteDatabase::getBool (int colIndex)
|
||||
{
|
||||
return (sqlite3_column_int (mCurrentStmt, colIndex) ? true : false);
|
||||
}
|
||||
|
||||
int SqliteDatabase::getBinary (int colIndex, unsigned char* buf, int maxSize)
|
||||
{
|
||||
const void* blob = sqlite3_column_blob (mCurrentStmt, colIndex);
|
||||
int size = sqlite3_column_bytes (mCurrentStmt, colIndex);
|
||||
|
||||
if (size < maxSize) maxSize = size;
|
||||
|
||||
memcpy (buf, blob, maxSize);
|
||||
return (size);
|
||||
}
|
||||
|
||||
Blob SqliteDatabase::getBinary (int colIndex)
|
||||
{
|
||||
const unsigned char* blob = reinterpret_cast<const unsigned char*> (sqlite3_column_blob (mCurrentStmt, colIndex));
|
||||
size_t iSize = sqlite3_column_bytes (mCurrentStmt, colIndex);
|
||||
Blob vucResult;
|
||||
|
||||
vucResult.resize (iSize);
|
||||
std::copy (blob, blob + iSize, vucResult.begin ());
|
||||
|
||||
return vucResult;
|
||||
}
|
||||
|
||||
uint64 SqliteDatabase::getBigInt (int colIndex)
|
||||
{
|
||||
return (sqlite3_column_int64 (mCurrentStmt, colIndex));
|
||||
}
|
||||
|
||||
int SqliteDatabase::getKBUsedAll ()
|
||||
{
|
||||
return static_cast<int> (sqlite3_memory_used () / 1024);
|
||||
}
|
||||
|
||||
int SqliteDatabase::getKBUsedDB ()
|
||||
{
|
||||
int cur = 0, hiw = 0;
|
||||
sqlite3_db_status (mConnection, SQLITE_DBSTATUS_CACHE_USED, &cur, &hiw, 0);
|
||||
return cur / 1024;
|
||||
}
|
||||
|
||||
static int SqliteWALHook (void* s, sqlite3* dbCon, const char* dbName, int walSize)
|
||||
{
|
||||
(reinterpret_cast<SqliteDatabase*> (s))->doHook (dbName, walSize);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
bool SqliteDatabase::setupCheckpointing (JobQueue* q)
|
||||
{
|
||||
mWalQ = q;
|
||||
sqlite3_wal_hook (mConnection, SqliteWALHook, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SqliteDatabase::doHook (const char* db, int pages)
|
||||
{
|
||||
if (pages < 1000)
|
||||
return;
|
||||
|
||||
{
|
||||
ScopedLockType sl (m_walMutex, __FILE__, __LINE__);
|
||||
|
||||
if (walRunning)
|
||||
return;
|
||||
|
||||
walRunning = true;
|
||||
}
|
||||
|
||||
if (mWalQ)
|
||||
{
|
||||
mWalQ->addJob (jtWAL, std::string ("WAL:") + mHost, BIND_TYPE (&SqliteDatabase::runWal, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_thread.call (&SqliteDatabase::runWal, this);
|
||||
}
|
||||
}
|
||||
|
||||
void SqliteDatabase::runWal ()
|
||||
{
|
||||
int log = 0, ckpt = 0;
|
||||
int ret = sqlite3_wal_checkpoint_v2 (mConnection, NULL, SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt);
|
||||
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
WriteLog ((ret == SQLITE_LOCKED) ? lsTRACE : lsWARNING, SqliteDatabase) << "WAL("
|
||||
<< sqlite3_db_filename (mConnection, "main") << "): error " << ret;
|
||||
}
|
||||
else
|
||||
WriteLog (lsTRACE, SqliteDatabase) << "WAL(" << sqlite3_db_filename (mConnection, "main") <<
|
||||
"): frames=" << log << ", written=" << ckpt;
|
||||
|
||||
{
|
||||
ScopedLockType sl (m_walMutex, __FILE__, __LINE__);
|
||||
walRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_stmt* SqliteStatement::peekStatement ()
|
||||
{
|
||||
return statement;
|
||||
}
|
||||
|
||||
int SqliteStatement::bind (int position, const void* data, int length)
|
||||
{
|
||||
return sqlite3_bind_blob (statement, position, data, length, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
int SqliteStatement::bindStatic (int position, const void* data, int length)
|
||||
{
|
||||
return sqlite3_bind_blob (statement, position, data, length, SQLITE_STATIC);
|
||||
}
|
||||
|
||||
int SqliteStatement::bindStatic (int position, Blob const& value)
|
||||
{
|
||||
return sqlite3_bind_blob (statement, position, &value.front (), value.size (), SQLITE_STATIC);
|
||||
}
|
||||
|
||||
int SqliteStatement::bind (int position, uint32 value)
|
||||
{
|
||||
return sqlite3_bind_int64 (statement, position, static_cast<sqlite3_int64> (value));
|
||||
}
|
||||
|
||||
int SqliteStatement::bind (int position, const std::string& value)
|
||||
{
|
||||
return sqlite3_bind_text (statement, position, value.data (), value.size (), SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
int SqliteStatement::bindStatic (int position, const std::string& value)
|
||||
{
|
||||
return sqlite3_bind_text (statement, position, value.data (), value.size (), SQLITE_STATIC);
|
||||
}
|
||||
|
||||
int SqliteStatement::bind (int position)
|
||||
{
|
||||
return sqlite3_bind_null (statement, position);
|
||||
}
|
||||
|
||||
int SqliteStatement::size (int column)
|
||||
{
|
||||
return sqlite3_column_bytes (statement, column);
|
||||
}
|
||||
|
||||
const void* SqliteStatement::peekBlob (int column)
|
||||
{
|
||||
return sqlite3_column_blob (statement, column);
|
||||
}
|
||||
|
||||
Blob SqliteStatement::getBlob (int column)
|
||||
{
|
||||
int size = sqlite3_column_bytes (statement, column);
|
||||
Blob ret (size);
|
||||
memcpy (& (ret.front ()), sqlite3_column_blob (statement, column), size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string SqliteStatement::getString (int column)
|
||||
{
|
||||
return reinterpret_cast<const char*> (sqlite3_column_text (statement, column));
|
||||
}
|
||||
|
||||
const char* SqliteStatement::peekString (int column)
|
||||
{
|
||||
return reinterpret_cast<const char*> (sqlite3_column_text (statement, column));
|
||||
}
|
||||
|
||||
uint32 SqliteStatement::getUInt32 (int column)
|
||||
{
|
||||
return static_cast<uint32> (sqlite3_column_int64 (statement, column));
|
||||
}
|
||||
|
||||
int64 SqliteStatement::getInt64 (int column)
|
||||
{
|
||||
return sqlite3_column_int64 (statement, column);
|
||||
}
|
||||
|
||||
int SqliteStatement::step ()
|
||||
{
|
||||
return sqlite3_step (statement);
|
||||
}
|
||||
|
||||
int SqliteStatement::reset ()
|
||||
{
|
||||
return sqlite3_reset (statement);
|
||||
}
|
||||
|
||||
bool SqliteStatement::isOk (int j)
|
||||
{
|
||||
return j == SQLITE_OK;
|
||||
}
|
||||
|
||||
bool SqliteStatement::isDone (int j)
|
||||
{
|
||||
return j == SQLITE_DONE;
|
||||
}
|
||||
|
||||
bool SqliteStatement::isRow (int j)
|
||||
{
|
||||
return j == SQLITE_ROW;
|
||||
}
|
||||
|
||||
bool SqliteStatement::isError (int j)
|
||||
{
|
||||
switch (j)
|
||||
{
|
||||
case SQLITE_OK:
|
||||
case SQLITE_ROW:
|
||||
case SQLITE_DONE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string SqliteStatement::getError (int j)
|
||||
{
|
||||
return sqlite3_errstr (j);
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
132
src/ripple_app/data/ripple_SqliteDatabase.h
Normal file
132
src/ripple_app/data/ripple_SqliteDatabase.h
Normal file
@@ -0,0 +1,132 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SQLITEDATABASE_RIPPLEHEADER
|
||||
#define RIPPLE_SQLITEDATABASE_RIPPLEHEADER
|
||||
|
||||
class SqliteDatabase
|
||||
: public Database
|
||||
, LeakChecked <SqliteDatabase>
|
||||
{
|
||||
public:
|
||||
explicit SqliteDatabase (char const* host);
|
||||
|
||||
void connect ();
|
||||
void disconnect ();
|
||||
|
||||
// returns true if the query went ok
|
||||
bool executeSQL (const char* sql, bool fail_okay);
|
||||
|
||||
// tells you how many rows were changed by an update or insert
|
||||
int getNumRowsAffected ();
|
||||
|
||||
// returns false if there are no results
|
||||
bool startIterRows (bool finalize);
|
||||
void endIterRows ();
|
||||
|
||||
// call this after you executeSQL
|
||||
// will return false if there are no more rows
|
||||
bool getNextRow (bool finalize);
|
||||
|
||||
bool getNull (int colIndex);
|
||||
char* getStr (int colIndex, std::string& retStr);
|
||||
int32 getInt (int colIndex);
|
||||
float getFloat (int colIndex);
|
||||
bool getBool (int colIndex);
|
||||
// returns amount stored in buf
|
||||
int getBinary (int colIndex, unsigned char* buf, int maxSize);
|
||||
Blob getBinary (int colIndex);
|
||||
uint64 getBigInt (int colIndex);
|
||||
|
||||
sqlite3* peekConnection ()
|
||||
{
|
||||
return mConnection;
|
||||
}
|
||||
sqlite3* getAuxConnection ();
|
||||
virtual bool setupCheckpointing (JobQueue*);
|
||||
virtual SqliteDatabase* getSqliteDB ()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
void runWal ();
|
||||
void doHook (const char* db, int walSize);
|
||||
|
||||
int getKBUsedDB ();
|
||||
int getKBUsedAll ();
|
||||
|
||||
private:
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType m_walMutex;
|
||||
|
||||
ThreadWithCallQueue m_thread;
|
||||
|
||||
sqlite3* mConnection;
|
||||
// VFALCO TODO Why do we need an "aux" connection? Should just use a second SqliteDatabase object.
|
||||
sqlite3* mAuxConnection;
|
||||
sqlite3_stmt* mCurrentStmt;
|
||||
bool mMoreRows;
|
||||
|
||||
JobQueue* mWalQ;
|
||||
bool walRunning;
|
||||
};
|
||||
|
||||
class SqliteStatement
|
||||
{
|
||||
private:
|
||||
SqliteStatement (const SqliteStatement&); // no implementation
|
||||
SqliteStatement& operator= (const SqliteStatement&); // no implementation
|
||||
|
||||
protected:
|
||||
sqlite3_stmt* statement;
|
||||
|
||||
public:
|
||||
// VFALCO TODO This is quite a convoluted interface. A mysterious "aux" connection?
|
||||
// Why not just have two SqliteDatabase objects?
|
||||
//
|
||||
SqliteStatement (SqliteDatabase* db, const char* statement, bool aux = false);
|
||||
SqliteStatement (SqliteDatabase* db, const std::string& statement, bool aux = false);
|
||||
~SqliteStatement ();
|
||||
|
||||
sqlite3_stmt* peekStatement ();
|
||||
|
||||
// positions start at 1
|
||||
int bind (int position, const void* data, int length);
|
||||
int bindStatic (int position, const void* data, int length);
|
||||
int bindStatic (int position, Blob const& value);
|
||||
|
||||
int bind (int position, const std::string& value);
|
||||
int bindStatic (int position, const std::string& value);
|
||||
|
||||
int bind (int position, uint32 value);
|
||||
int bind (int position);
|
||||
|
||||
// columns start at 0
|
||||
int size (int column);
|
||||
|
||||
const void* peekBlob (int column);
|
||||
Blob getBlob (int column);
|
||||
|
||||
std::string getString (int column);
|
||||
const char* peekString (int column);
|
||||
uint32 getUInt32 (int column);
|
||||
int64 getInt64 (int column);
|
||||
|
||||
int step ();
|
||||
int reset ();
|
||||
|
||||
// translate return values of step and reset
|
||||
bool isOk (int);
|
||||
bool isDone (int);
|
||||
bool isRow (int);
|
||||
bool isError (int);
|
||||
std::string getError (int);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
1996
src/ripple_app/ledger/Ledger.cpp
Normal file
1996
src/ripple_app/ledger/Ledger.cpp
Normal file
File diff suppressed because it is too large
Load Diff
505
src/ripple_app/ledger/Ledger.h
Normal file
505
src/ripple_app/ledger/Ledger.h
Normal file
@@ -0,0 +1,505 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGER_H
|
||||
#define RIPPLE_LEDGER_H
|
||||
|
||||
class Job;
|
||||
|
||||
enum LedgerStateParms
|
||||
{
|
||||
lepNONE = 0, // no special flags
|
||||
|
||||
// input flags
|
||||
lepCREATE = 1, // Create if not present
|
||||
|
||||
// output flags
|
||||
lepOKAY = 2, // success
|
||||
lepMISSING = 4, // No node in that slot
|
||||
lepWRONGTYPE = 8, // Node of different type there
|
||||
lepCREATED = 16, // Node was created
|
||||
lepERROR = 32, // error
|
||||
};
|
||||
|
||||
#define LEDGER_JSON_DUMP_TXRP 0x10000000
|
||||
#define LEDGER_JSON_DUMP_STATE 0x20000000
|
||||
#define LEDGER_JSON_EXPAND 0x40000000
|
||||
#define LEDGER_JSON_FULL 0x80000000
|
||||
|
||||
class SqliteStatement;
|
||||
|
||||
class LedgerBase
|
||||
{
|
||||
protected:
|
||||
LedgerBase ();
|
||||
|
||||
// VFALCO TODO eliminate the need for friends
|
||||
friend class TransactionEngine;
|
||||
friend class Transactor;
|
||||
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
};
|
||||
|
||||
// VFALCO TODO figure out exactly how this thing works.
|
||||
// It seems like some ledger database is stored as a global, static in the
|
||||
// class. But then what is the meaning of a Ledger object? Is this
|
||||
// really two classes in one? StoreOfAllLedgers + SingleLedgerObject?
|
||||
//
|
||||
class Ledger
|
||||
: public boost::enable_shared_from_this <Ledger>
|
||||
, public LedgerBase
|
||||
, public CountedObject <Ledger>
|
||||
, public Uncopyable
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "Ledger"; }
|
||||
|
||||
typedef boost::shared_ptr<Ledger> pointer;
|
||||
typedef const boost::shared_ptr<Ledger>& ref;
|
||||
|
||||
enum TransResult
|
||||
{
|
||||
TR_ERROR = -1,
|
||||
TR_SUCCESS = 0,
|
||||
TR_NOTFOUND = 1,
|
||||
TR_ALREADY = 2,
|
||||
TR_BADTRANS = 3, // the transaction itself is corrupt
|
||||
TR_BADACCT = 4, // one of the accounts is invalid
|
||||
TR_INSUFF = 5, // the sending(apply)/receiving(remove) account is broke
|
||||
TR_PASTASEQ = 6, // account is past this transaction
|
||||
TR_PREASEQ = 7, // account is missing transactions before this
|
||||
TR_BADLSEQ = 8, // ledger too early
|
||||
TR_TOOSMALL = 9, // amount is less than Tx fee
|
||||
};
|
||||
|
||||
// ledger close flags
|
||||
static const uint32 sLCF_NoConsensusTime = 1;
|
||||
|
||||
public:
|
||||
Ledger (const RippleAddress & masterID, uint64 startAmount); // used for the starting bootstrap ledger
|
||||
|
||||
Ledger (uint256 const & parentHash, uint256 const & transHash, uint256 const & accountHash,
|
||||
uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, int closeFlags, int closeResolution,
|
||||
uint32 ledgerSeq, bool & loaded); // used for database ledgers
|
||||
|
||||
Ledger (Blob const & rawLedger, bool hasPrefix);
|
||||
|
||||
Ledger (const std::string & rawLedger, bool hasPrefix);
|
||||
|
||||
Ledger (bool dummy, Ledger & previous); // ledger after this one
|
||||
|
||||
Ledger (Ledger & target, bool isMutable); // snapshot
|
||||
|
||||
~Ledger ();
|
||||
|
||||
static Ledger::pointer getSQL (const std::string & sqlStatement);
|
||||
static Ledger::pointer getSQL1 (SqliteStatement*);
|
||||
static void getSQL2 (Ledger::ref);
|
||||
static Ledger::pointer getLastFullLedger ();
|
||||
static uint32 roundCloseTime (uint32 closeTime, uint32 closeResolution);
|
||||
|
||||
void updateHash ();
|
||||
void setClosed ()
|
||||
{
|
||||
mClosed = true;
|
||||
}
|
||||
void setValidated()
|
||||
{
|
||||
mValidated = true;
|
||||
}
|
||||
void setAccepted (uint32 closeTime, int closeResolution, bool correctCloseTime);
|
||||
void setAccepted ();
|
||||
void setImmutable ();
|
||||
bool isClosed ()
|
||||
{
|
||||
return mClosed;
|
||||
}
|
||||
bool isAccepted ()
|
||||
{
|
||||
return mAccepted;
|
||||
}
|
||||
bool isValidated ()
|
||||
{
|
||||
return mValidated;
|
||||
}
|
||||
bool isImmutable ()
|
||||
{
|
||||
return mImmutable;
|
||||
}
|
||||
bool isFixed ()
|
||||
{
|
||||
return mClosed || mImmutable;
|
||||
}
|
||||
void setFull ()
|
||||
{
|
||||
mTransactionMap->setLedgerSeq (mLedgerSeq);
|
||||
mAccountStateMap->setLedgerSeq (mLedgerSeq);
|
||||
}
|
||||
|
||||
// ledger signature operations
|
||||
void addRaw (Serializer & s) const;
|
||||
void setRaw (Serializer & s, bool hasPrefix);
|
||||
|
||||
uint256 getHash ();
|
||||
uint256 const& getParentHash () const
|
||||
{
|
||||
return mParentHash;
|
||||
}
|
||||
uint256 const& getTransHash () const
|
||||
{
|
||||
return mTransHash;
|
||||
}
|
||||
uint256 const& getAccountHash () const
|
||||
{
|
||||
return mAccountHash;
|
||||
}
|
||||
uint64 getTotalCoins () const
|
||||
{
|
||||
return mTotCoins;
|
||||
}
|
||||
void destroyCoins (uint64 fee)
|
||||
{
|
||||
mTotCoins -= fee;
|
||||
}
|
||||
uint32 getCloseTimeNC () const
|
||||
{
|
||||
return mCloseTime;
|
||||
}
|
||||
uint32 getParentCloseTimeNC () const
|
||||
{
|
||||
return mParentCloseTime;
|
||||
}
|
||||
uint32 getLedgerSeq () const
|
||||
{
|
||||
return mLedgerSeq;
|
||||
}
|
||||
int getCloseResolution () const
|
||||
{
|
||||
return mCloseResolution;
|
||||
}
|
||||
bool getCloseAgree () const
|
||||
{
|
||||
return (mCloseFlags & sLCF_NoConsensusTime) == 0;
|
||||
}
|
||||
|
||||
// close time functions
|
||||
void setCloseTime (uint32 ct)
|
||||
{
|
||||
assert (!mImmutable);
|
||||
mCloseTime = ct;
|
||||
}
|
||||
void setCloseTime (boost::posix_time::ptime);
|
||||
boost::posix_time::ptime getCloseTime () const;
|
||||
|
||||
// low level functions
|
||||
SHAMap::ref peekTransactionMap ()
|
||||
{
|
||||
return mTransactionMap;
|
||||
}
|
||||
SHAMap::ref peekAccountStateMap ()
|
||||
{
|
||||
return mAccountStateMap;
|
||||
}
|
||||
void dropCache ()
|
||||
{
|
||||
assert (isImmutable ());
|
||||
|
||||
if (mTransactionMap)
|
||||
mTransactionMap->dropCache ();
|
||||
|
||||
if (mAccountStateMap)
|
||||
mAccountStateMap->dropCache ();
|
||||
}
|
||||
|
||||
// ledger sync functions
|
||||
void setAcquiring (void);
|
||||
bool isAcquiring (void);
|
||||
bool isAcquiringTx (void);
|
||||
bool isAcquiringAS (void);
|
||||
|
||||
// Transaction Functions
|
||||
bool addTransaction (uint256 const & id, const Serializer & txn);
|
||||
bool addTransaction (uint256 const & id, const Serializer & txn, const Serializer & metaData);
|
||||
bool hasTransaction (uint256 const & TransID) const
|
||||
{
|
||||
return mTransactionMap->hasItem (TransID);
|
||||
}
|
||||
Transaction::pointer getTransaction (uint256 const & transID) const;
|
||||
bool getTransaction (uint256 const & transID, Transaction::pointer & txn, TransactionMetaSet::pointer & txMeta);
|
||||
bool getTransactionMeta (uint256 const & transID, TransactionMetaSet::pointer & txMeta);
|
||||
bool getMetaHex (uint256 const & transID, std::string & hex);
|
||||
|
||||
static SerializedTransaction::pointer getSTransaction (SHAMapItem::ref, SHAMapTreeNode::TNType);
|
||||
SerializedTransaction::pointer getSMTransaction (SHAMapItem::ref, SHAMapTreeNode::TNType,
|
||||
TransactionMetaSet::pointer & txMeta);
|
||||
|
||||
// high-level functions
|
||||
bool hasAccount (const RippleAddress & acctID);
|
||||
AccountState::pointer getAccountState (const RippleAddress & acctID);
|
||||
LedgerStateParms writeBack (LedgerStateParms parms, SLE::ref);
|
||||
SLE::pointer getAccountRoot (const uint160 & accountID);
|
||||
SLE::pointer getAccountRoot (const RippleAddress & naAccountID);
|
||||
void updateSkipList ();
|
||||
void visitAccountItems (const uint160 & acctID, FUNCTION_TYPE<void (SLE::ref)>);
|
||||
|
||||
// database functions (low-level)
|
||||
static Ledger::pointer loadByIndex (uint32 ledgerIndex);
|
||||
static Ledger::pointer loadByHash (uint256 const & ledgerHash);
|
||||
static uint256 getHashByIndex (uint32 index);
|
||||
static bool getHashesByIndex (uint32 index, uint256 & ledgerHash, uint256 & parentHash);
|
||||
static std::map< uint32, std::pair<uint256, uint256> > getHashesByIndex (uint32 minSeq, uint32 maxSeq);
|
||||
bool pendSaveValidated (bool isSynchronous, bool isCurrent);
|
||||
|
||||
// next/prev function
|
||||
SLE::pointer getSLE (uint256 const & uHash); // SLE is mutable
|
||||
SLE::pointer getSLEi (uint256 const & uHash); // SLE is immutable
|
||||
|
||||
// VFALCO NOTE These seem to let you walk the list of ledgers
|
||||
//
|
||||
uint256 getFirstLedgerIndex ();
|
||||
uint256 getLastLedgerIndex ();
|
||||
uint256 getNextLedgerIndex (uint256 const & uHash); // first node >hash
|
||||
uint256 getNextLedgerIndex (uint256 const & uHash, uint256 const & uEnd); // first node >hash, <end
|
||||
uint256 getPrevLedgerIndex (uint256 const & uHash); // last node <hash
|
||||
uint256 getPrevLedgerIndex (uint256 const & uHash, uint256 const & uBegin); // last node <hash, >begin
|
||||
|
||||
// Ledger hash table function
|
||||
static uint256 getLedgerHashIndex ();
|
||||
static uint256 getLedgerHashIndex (uint32 desiredLedgerIndex);
|
||||
static int getLedgerHashOffset (uint32 desiredLedgerIndex);
|
||||
static int getLedgerHashOffset (uint32 desiredLedgerIndex, uint32 currentLedgerIndex);
|
||||
uint256 getLedgerHash (uint32 ledgerIndex);
|
||||
std::vector< std::pair<uint32, uint256> > getLedgerHashes ();
|
||||
|
||||
static uint256 getLedgerFeatureIndex ();
|
||||
static uint256 getLedgerFeeIndex ();
|
||||
std::vector<uint256> getLedgerFeatures ();
|
||||
|
||||
std::vector<uint256> getNeededTransactionHashes (int max, SHAMapSyncFilter * filter);
|
||||
std::vector<uint256> getNeededAccountStateHashes (int max, SHAMapSyncFilter * filter);
|
||||
|
||||
// index calculation functions
|
||||
static uint256 getAccountRootIndex (const uint160 & uAccountID);
|
||||
|
||||
static uint256 getAccountRootIndex (const RippleAddress & account)
|
||||
{
|
||||
return getAccountRootIndex (account.getAccountID ());
|
||||
}
|
||||
|
||||
//
|
||||
// Generator Map functions
|
||||
//
|
||||
|
||||
SLE::pointer getGenerator (const uint160 & uGeneratorID);
|
||||
|
||||
static uint256 getGeneratorIndex (const uint160 & uGeneratorID);
|
||||
|
||||
//
|
||||
// Nickname functions
|
||||
//
|
||||
|
||||
static uint256 getNicknameHash (const std::string & strNickname)
|
||||
{
|
||||
Serializer s (strNickname);
|
||||
return s.getSHA256 ();
|
||||
}
|
||||
|
||||
NicknameState::pointer getNicknameState (uint256 const & uNickname);
|
||||
NicknameState::pointer getNicknameState (const std::string & strNickname)
|
||||
{
|
||||
return getNicknameState (getNicknameHash (strNickname));
|
||||
}
|
||||
|
||||
SLE::pointer getNickname (uint256 const & uNickname);
|
||||
SLE::pointer getNickname (const std::string & strNickname)
|
||||
{
|
||||
return getNickname (getNicknameHash (strNickname));
|
||||
}
|
||||
|
||||
static uint256 getNicknameIndex (uint256 const & uNickname);
|
||||
|
||||
//
|
||||
// Order book functions
|
||||
//
|
||||
|
||||
// Order book dirs have a base so we can use next to step through them in quality order.
|
||||
static bool isValidBook (const uint160 & uTakerPaysCurrency, const uint160 & uTakerPaysIssuerID,
|
||||
const uint160 & uTakerGetsCurrency, const uint160 & uTakerGetsIssuerID);
|
||||
|
||||
static uint256 getBookBase (const uint160 & uTakerPaysCurrency, const uint160 & uTakerPaysIssuerID,
|
||||
const uint160 & uTakerGetsCurrency, const uint160 & uTakerGetsIssuerID);
|
||||
|
||||
//
|
||||
// Offer functions
|
||||
//
|
||||
|
||||
SLE::pointer getOffer (uint256 const & uIndex);
|
||||
|
||||
SLE::pointer getOffer (const uint160 & uAccountID, uint32 uSequence)
|
||||
{
|
||||
return getOffer (getOfferIndex (uAccountID, uSequence));
|
||||
}
|
||||
|
||||
// The index of an offer.
|
||||
static uint256 getOfferIndex (const uint160 & uAccountID, uint32 uSequence);
|
||||
|
||||
//
|
||||
// Owner functions
|
||||
//
|
||||
|
||||
// VFALCO NOTE This is a simple math operation that converts the account ID
|
||||
// into a 256 bit object (I think....need to research this)
|
||||
//
|
||||
// All items controlled by an account are here: offers
|
||||
static uint256 getOwnerDirIndex (const uint160 & uAccountID);
|
||||
|
||||
//
|
||||
// Directory functions
|
||||
// Directories are doubly linked lists of nodes.
|
||||
|
||||
// Given a directory root and and index compute the index of a node.
|
||||
static uint256 getDirNodeIndex (uint256 const & uDirRoot, const uint64 uNodeIndex = 0);
|
||||
static void ownerDirDescriber (SLE::ref, const uint160 & owner);
|
||||
|
||||
// Return a node: root or normal
|
||||
SLE::pointer getDirNode (uint256 const & uNodeIndex);
|
||||
|
||||
//
|
||||
// Quality
|
||||
//
|
||||
|
||||
static uint256 getQualityIndex (uint256 const & uBase, const uint64 uNodeDir = 0);
|
||||
static uint256 getQualityNext (uint256 const & uBase);
|
||||
static uint64 getQuality (uint256 const & uBase);
|
||||
static void qualityDirDescriber (SLE::ref,
|
||||
const uint160 & uTakerPaysCurrency, const uint160 & uTakerPaysIssuer,
|
||||
const uint160 & uTakerGetsCurrency, const uint160 & uTakerGetsIssuer,
|
||||
const uint64 & uRate);
|
||||
|
||||
//
|
||||
// Ripple functions : credit lines
|
||||
//
|
||||
|
||||
// Index of node which is the ripple state between two accounts for a currency.
|
||||
// VFALCO NOTE Rename these to make it clear they are simple functions that
|
||||
// don't access global variables. e.g. "calculateKeyFromRippleStateAndAddress"
|
||||
//
|
||||
static uint256 getRippleStateIndex (const RippleAddress & naA, const RippleAddress & naB, const uint160 & uCurrency);
|
||||
static uint256 getRippleStateIndex (const uint160 & uiA, const uint160 & uiB, const uint160 & uCurrency)
|
||||
{
|
||||
return getRippleStateIndex (RippleAddress::createAccountID (uiA), RippleAddress::createAccountID (uiB), uCurrency);
|
||||
}
|
||||
|
||||
SLE::pointer getRippleState (uint256 const & uNode);
|
||||
|
||||
SLE::pointer getRippleState (const RippleAddress & naA, const RippleAddress & naB, const uint160 & uCurrency)
|
||||
{
|
||||
return getRippleState (getRippleStateIndex (naA, naB, uCurrency));
|
||||
}
|
||||
|
||||
SLE::pointer getRippleState (const uint160 & uiA, const uint160 & uiB, const uint160 & uCurrency)
|
||||
{
|
||||
return getRippleState (getRippleStateIndex (RippleAddress::createAccountID (uiA), RippleAddress::createAccountID (uiB), uCurrency));
|
||||
}
|
||||
|
||||
uint32 getReferenceFeeUnits ()
|
||||
{
|
||||
if (!mBaseFee) updateFees ();
|
||||
|
||||
return mReferenceFeeUnits;
|
||||
}
|
||||
|
||||
uint64 getBaseFee ()
|
||||
{
|
||||
if (!mBaseFee) updateFees ();
|
||||
|
||||
return mBaseFee;
|
||||
}
|
||||
|
||||
uint64 getReserve (int increments)
|
||||
{
|
||||
if (!mBaseFee) updateFees ();
|
||||
|
||||
return scaleFeeBase (static_cast<uint64> (increments) * mReserveIncrement + mReserveBase);
|
||||
}
|
||||
|
||||
uint64 getReserveInc ()
|
||||
{
|
||||
if (!mBaseFee) updateFees ();
|
||||
|
||||
return mReserveIncrement;
|
||||
}
|
||||
|
||||
uint64 scaleFeeBase (uint64 fee);
|
||||
uint64 scaleFeeLoad (uint64 fee, bool bAdmin);
|
||||
|
||||
static std::set<uint32> getPendingSaves();
|
||||
|
||||
Json::Value getJson (int options);
|
||||
void addJson (Json::Value&, int options);
|
||||
|
||||
bool walkLedger ();
|
||||
bool assertSane ();
|
||||
|
||||
protected:
|
||||
SLE::pointer getASNode (LedgerStateParms & parms, uint256 const & nodeID, LedgerEntryType let);
|
||||
|
||||
// returned SLE is immutable
|
||||
SLE::pointer getASNodeI (uint256 const & nodeID, LedgerEntryType let);
|
||||
|
||||
void saveValidatedLedgerAsync(Job&, bool current)
|
||||
{
|
||||
saveValidatedLedger(current);
|
||||
}
|
||||
void saveValidatedLedger (bool current);
|
||||
|
||||
void updateFees ();
|
||||
|
||||
private:
|
||||
void initializeFees ();
|
||||
|
||||
private:
|
||||
// The basic Ledger structure, can be opened, closed, or synching
|
||||
uint256 mHash;
|
||||
uint256 mParentHash;
|
||||
uint256 mTransHash;
|
||||
uint256 mAccountHash;
|
||||
uint64 mTotCoins;
|
||||
uint32 mLedgerSeq;
|
||||
uint32 mCloseTime; // when this ledger closed
|
||||
uint32 mParentCloseTime; // when the previous ledger closed
|
||||
int mCloseResolution; // the resolution for this ledger close time (2-120 seconds)
|
||||
uint32 mCloseFlags; // flags indicating how this ledger close took place
|
||||
bool mClosed, mValidated, mValidHash, mAccepted, mImmutable;
|
||||
|
||||
uint32 mReferenceFeeUnits; // Fee units for the reference transaction
|
||||
uint32 mReserveBase, mReserveIncrement; // Reserve basse and increment in fee units
|
||||
uint64 mBaseFee; // Ripple cost of the reference transaction
|
||||
|
||||
SHAMap::pointer mTransactionMap;
|
||||
SHAMap::pointer mAccountStateMap;
|
||||
|
||||
typedef RippleMutex StaticLockType;
|
||||
typedef StaticLockType::ScopedLockType StaticScopedLockType;
|
||||
// ledgers not fully saved, validated ledger present but DB may not be correct yet
|
||||
static StaticLockType sPendingSaveLock;
|
||||
|
||||
static std::set<uint32> sPendingSaves;
|
||||
};
|
||||
|
||||
inline LedgerStateParms operator| (const LedgerStateParms& l1, const LedgerStateParms& l2)
|
||||
{
|
||||
return static_cast<LedgerStateParms> (static_cast<int> (l1) | static_cast<int> (l2));
|
||||
}
|
||||
|
||||
inline LedgerStateParms operator& (const LedgerStateParms& l1, const LedgerStateParms& l2)
|
||||
{
|
||||
return static_cast<LedgerStateParms> (static_cast<int> (l1) & static_cast<int> (l2));
|
||||
}
|
||||
|
||||
#endif
|
||||
870
src/ripple_app/ledger/LedgerMaster.cpp
Normal file
870
src/ripple_app/ledger/LedgerMaster.cpp
Normal file
@@ -0,0 +1,870 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#define MIN_VALIDATION_RATIO 150 // 150/256ths of validations of previous ledger
|
||||
#define MAX_LEDGER_GAP 100 // Don't catch up more than 100 ledgers (cannot exceed 256)
|
||||
|
||||
SETUP_LOG (LedgerMaster)
|
||||
|
||||
uint32 LedgerMaster::getCurrentLedgerIndex ()
|
||||
{
|
||||
return mCurrentLedger->getLedgerSeq ();
|
||||
}
|
||||
|
||||
Ledger::ref LedgerMaster::getCurrentSnapshot ()
|
||||
{
|
||||
if (!mCurrentSnapshot || (mCurrentSnapshot->getHash () != mCurrentLedger->getHash ()))
|
||||
mCurrentSnapshot = boost::make_shared<Ledger> (boost::ref (*mCurrentLedger), false);
|
||||
|
||||
assert (mCurrentSnapshot->isImmutable ());
|
||||
return mCurrentSnapshot;
|
||||
}
|
||||
|
||||
int LedgerMaster::getPublishedLedgerAge ()
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
if (!mPubLedger)
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "No published ledger";
|
||||
return 999999;
|
||||
}
|
||||
|
||||
int64 ret = getApp().getOPs ().getCloseTimeNC ();
|
||||
ret -= static_cast<int64> (mPubLedger->getCloseTimeNC ());
|
||||
ret = std::max (0LL, ret);
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Published ledger age is " << ret;
|
||||
return static_cast<int> (ret);
|
||||
}
|
||||
|
||||
int LedgerMaster::getValidatedLedgerAge ()
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
if (!mValidLedger)
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "No validated ledger";
|
||||
return 999999;
|
||||
}
|
||||
|
||||
int64 ret = getApp().getOPs ().getCloseTimeNC ();
|
||||
ret -= static_cast<int64> (mValidLedger->getCloseTimeNC ());
|
||||
ret = std::max (0LL, ret);
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Validated ledger age is " << ret;
|
||||
return static_cast<int> (ret);
|
||||
}
|
||||
|
||||
bool LedgerMaster::isCaughtUp(std::string& reason)
|
||||
{
|
||||
if (getPublishedLedgerAge() > 180)
|
||||
{
|
||||
reason = "No recently-published ledger";
|
||||
return false;
|
||||
}
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
if (!mValidLedger || !mPubLedger)
|
||||
{
|
||||
reason = "No published ledger";
|
||||
return false;
|
||||
}
|
||||
if (mValidLedger->getLedgerSeq() > (mPubLedger->getLedgerSeq() + 3))
|
||||
{
|
||||
reason = "Published ledger lags validated ledger";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LedgerMaster::addHeldTransaction (Transaction::ref transaction)
|
||||
{
|
||||
// returns true if transaction was added
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mHeldTransactions.push_back (transaction->getSTransaction ());
|
||||
}
|
||||
|
||||
void LedgerMaster::pushLedger (Ledger::pointer newLedger)
|
||||
{
|
||||
// Caller should already have properly assembled this ledger into "ready-to-close" form --
|
||||
// all candidate transactions must already be applied
|
||||
WriteLog (lsINFO, LedgerMaster) << "PushLedger: " << newLedger->getHash ();
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mClosedLedger)
|
||||
{
|
||||
mClosedLedger->setClosed ();
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Finalizes: " << mClosedLedger->getHash ();
|
||||
}
|
||||
|
||||
mClosedLedger = mCurrentLedger;
|
||||
mCurrentLedger = newLedger;
|
||||
mEngine.setLedger (newLedger);
|
||||
}
|
||||
|
||||
if (getConfig().RUN_STANDALONE)
|
||||
{
|
||||
setFullLedger(newLedger, true, false);
|
||||
tryAdvance();
|
||||
}
|
||||
else
|
||||
checkAccept(newLedger);
|
||||
}
|
||||
|
||||
void LedgerMaster::pushLedger (Ledger::pointer newLCL, Ledger::pointer newOL)
|
||||
{
|
||||
assert (newLCL->isClosed () && newLCL->isAccepted ());
|
||||
assert (!newOL->isClosed () && !newOL->isAccepted ());
|
||||
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mClosedLedger = newLCL;
|
||||
mCurrentLedger = newOL;
|
||||
mEngine.setLedger (newOL);
|
||||
}
|
||||
|
||||
if (getConfig().RUN_STANDALONE)
|
||||
{
|
||||
setFullLedger(newLCL, true, false);
|
||||
tryAdvance();
|
||||
}
|
||||
else
|
||||
{
|
||||
mLedgerHistory.builtLedger (newLCL);
|
||||
checkAccept (newLCL);
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerMaster::switchLedgers (Ledger::pointer lastClosed, Ledger::pointer current)
|
||||
{
|
||||
assert (lastClosed && current);
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mClosedLedger = lastClosed;
|
||||
mClosedLedger->setClosed ();
|
||||
mClosedLedger->setAccepted ();
|
||||
mCurrentLedger = current;
|
||||
|
||||
assert (!mCurrentLedger->isClosed ());
|
||||
mEngine.setLedger (mCurrentLedger);
|
||||
}
|
||||
checkAccept (lastClosed);
|
||||
}
|
||||
|
||||
void LedgerMaster::storeLedger (Ledger::pointer ledger)
|
||||
{
|
||||
mLedgerHistory.addLedger (ledger);
|
||||
}
|
||||
|
||||
void LedgerMaster::forceValid (Ledger::pointer ledger)
|
||||
{
|
||||
ledger->setValidated();
|
||||
setFullLedger(ledger, true, false);
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerMaster::closeLedger (bool recover)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
Ledger::pointer closingLedger = mCurrentLedger;
|
||||
|
||||
if (recover)
|
||||
{
|
||||
int recovers = 0;
|
||||
|
||||
for (CanonicalTXSet::iterator it = mHeldTransactions.begin (), end = mHeldTransactions.end (); it != end; ++it)
|
||||
{
|
||||
try
|
||||
{
|
||||
TransactionEngineParams tepFlags = tapOPEN_LEDGER;
|
||||
|
||||
if (getApp().getHashRouter ().addSuppressionPeer (it->first.getTXID (), SF_SIGGOOD))
|
||||
tepFlags = static_cast<TransactionEngineParams> (tepFlags | tapNO_CHECK_SIGN);
|
||||
|
||||
bool didApply;
|
||||
mEngine.applyTransaction (*it->second, tepFlags, didApply);
|
||||
|
||||
if (didApply)
|
||||
++recovers;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// CHECKME: We got a few of these
|
||||
WriteLog (lsWARNING, LedgerMaster) << "Held transaction throws";
|
||||
}
|
||||
}
|
||||
|
||||
CondLog (recovers != 0, lsINFO, LedgerMaster) << "Recovered " << recovers << " held transactions";
|
||||
|
||||
// VFALCO TODO recreate the CanonicalTxSet object instead of resetting it
|
||||
mHeldTransactions.reset (closingLedger->getHash ());
|
||||
}
|
||||
|
||||
mCurrentLedger = boost::make_shared<Ledger> (boost::ref (*closingLedger), true);
|
||||
mEngine.setLedger (mCurrentLedger);
|
||||
|
||||
return Ledger::pointer (new Ledger (*closingLedger, true));
|
||||
}
|
||||
|
||||
TER LedgerMaster::doTransaction (SerializedTransaction::ref txn, TransactionEngineParams params, bool& didApply)
|
||||
{
|
||||
Ledger::pointer ledger;
|
||||
TER result;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
result = mEngine.applyTransaction (*txn, params, didApply);
|
||||
ledger = mEngine.getLedger ();
|
||||
}
|
||||
if (didApply)
|
||||
getApp().getOPs ().pubProposedTransaction (ledger, txn, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LedgerMaster::haveLedgerRange (uint32 from, uint32 to)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
uint32 prevMissing = mCompleteLedgers.prevMissing (to + 1);
|
||||
return (prevMissing == RangeSet::absent) || (prevMissing < from);
|
||||
}
|
||||
|
||||
bool LedgerMaster::haveLedger (uint32 seq)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mCompleteLedgers.hasValue (seq);
|
||||
}
|
||||
|
||||
bool LedgerMaster::getFullValidatedRange (uint32& minVal, uint32& maxVal)
|
||||
{ // Ledgers we have all the nodes for
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mPubLedger)
|
||||
return false;
|
||||
|
||||
maxVal = mPubLedger->getLedgerSeq ();
|
||||
|
||||
if (maxVal == 0)
|
||||
return false;
|
||||
|
||||
minVal = mCompleteLedgers.prevMissing (maxVal);
|
||||
|
||||
if (minVal == RangeSet::absent)
|
||||
minVal = maxVal;
|
||||
else
|
||||
++minVal;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LedgerMaster::getValidatedRange (uint32& minVal, uint32& maxVal)
|
||||
{ // Ledgers we have all the nodes for and are indexed
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mPubLedger)
|
||||
return false;
|
||||
|
||||
maxVal = mPubLedger->getLedgerSeq ();
|
||||
|
||||
if (maxVal == 0)
|
||||
return false;
|
||||
|
||||
minVal = mCompleteLedgers.prevMissing (maxVal);
|
||||
|
||||
if (minVal == RangeSet::absent)
|
||||
minVal = maxVal;
|
||||
else
|
||||
++minVal;
|
||||
|
||||
// Remove from the validated range any ledger sequences that may not be
|
||||
// fully updated in the database yet
|
||||
|
||||
std::set<uint32> sPendingSaves = Ledger::getPendingSaves();
|
||||
|
||||
if (!sPendingSaves.empty() && ((minVal != 0) || (maxVal != 0)))
|
||||
{
|
||||
// Ensure we shrink the tips as much as possible
|
||||
// If we have 7-9 and 8,9 are invalid, we don't want to see the 8 and shrink to just 9
|
||||
// because then we'll have nothing when we could have 7.
|
||||
while (sPendingSaves.count(maxVal) > 0)
|
||||
--maxVal;
|
||||
while (sPendingSaves.count(minVal) > 0)
|
||||
++minVal;
|
||||
|
||||
// Best effort for remaining exclusions
|
||||
BOOST_FOREACH(uint32 v, sPendingSaves)
|
||||
{
|
||||
if ((v >= minVal) && (v <= maxVal))
|
||||
{
|
||||
if (v > ((minVal + maxVal) / 2))
|
||||
maxVal = v - 1;
|
||||
else
|
||||
minVal = v + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (minVal > maxVal)
|
||||
minVal = maxVal = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LedgerMaster::tryFill (Ledger::pointer ledger)
|
||||
{
|
||||
uint32 seq = ledger->getLedgerSeq ();
|
||||
uint256 prevHash = ledger->getParentHash ();
|
||||
|
||||
std::map< uint32, std::pair<uint256, uint256> > ledgerHashes;
|
||||
|
||||
uint32 minHas = ledger->getLedgerSeq ();
|
||||
uint32 maxHas = ledger->getLedgerSeq ();
|
||||
|
||||
while (seq > 0)
|
||||
{
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
minHas = seq;
|
||||
--seq;
|
||||
|
||||
if (mCompleteLedgers.hasValue (seq))
|
||||
break;
|
||||
}
|
||||
|
||||
std::map< uint32, std::pair<uint256, uint256> >::iterator it = ledgerHashes.find (seq);
|
||||
|
||||
if (it == ledgerHashes.end ())
|
||||
{
|
||||
if (getApp().isShutdown ())
|
||||
return;
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mCompleteLedgers.setRange (minHas, maxHas);
|
||||
}
|
||||
maxHas = minHas;
|
||||
ledgerHashes = Ledger::getHashesByIndex ((seq < 500) ? 0 : (seq - 499), seq);
|
||||
it = ledgerHashes.find (seq);
|
||||
|
||||
if (it == ledgerHashes.end ())
|
||||
break;
|
||||
}
|
||||
|
||||
if (it->second.first != prevHash)
|
||||
break;
|
||||
|
||||
prevHash = it->second.second;
|
||||
}
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mCompleteLedgers.setRange (minHas, maxHas);
|
||||
mFillInProgress = 0;
|
||||
tryAdvance();
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerMaster::getFetchPack (Ledger::ref nextLedger)
|
||||
{
|
||||
Peer::pointer target;
|
||||
int count = 0;
|
||||
|
||||
std::vector<Peer::pointer> peerList = getApp().getPeers ().getPeerVector ();
|
||||
BOOST_FOREACH (const Peer::pointer & peer, peerList)
|
||||
{
|
||||
if (peer->hasRange (nextLedger->getLedgerSeq() - 1, nextLedger->getLedgerSeq()))
|
||||
{
|
||||
if (count++ == 0)
|
||||
target = peer;
|
||||
else if ((rand () % count) == 0)
|
||||
target = peer;
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
{
|
||||
protocol::TMGetObjectByHash tmBH;
|
||||
tmBH.set_query (true);
|
||||
tmBH.set_type (protocol::TMGetObjectByHash::otFETCH_PACK);
|
||||
tmBH.set_ledgerhash (nextLedger->getHash().begin (), 32);
|
||||
PackedMessage::pointer packet = boost::make_shared<PackedMessage> (tmBH, protocol::mtGET_OBJECTS);
|
||||
|
||||
target->sendPacket (packet, false);
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Requested fetch pack for " << nextLedger->getLedgerSeq() - 1;
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "No peer for fetch pack";
|
||||
}
|
||||
|
||||
bool LedgerMaster::shouldAcquire (uint32 currentLedger, uint32 ledgerHistory, uint32 candidateLedger)
|
||||
{
|
||||
bool ret;
|
||||
|
||||
if (candidateLedger >= currentLedger)
|
||||
ret = true;
|
||||
else
|
||||
ret = (currentLedger - candidateLedger) <= ledgerHistory;
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Missing ledger " << candidateLedger << (ret ? " should" : " should NOT") << " be acquired";
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LedgerMaster::fixMismatch (Ledger::ref ledger)
|
||||
{
|
||||
int invalidate = 0;
|
||||
|
||||
for (uint32 lSeq = ledger->getLedgerSeq () - 1; lSeq > 0; --lSeq)
|
||||
if (mCompleteLedgers.hasValue (lSeq))
|
||||
{
|
||||
uint256 hash = ledger->getLedgerHash (lSeq);
|
||||
|
||||
if (hash.isNonZero ())
|
||||
{
|
||||
// try to close the seam
|
||||
Ledger::pointer otherLedger = getLedgerBySeq (lSeq);
|
||||
|
||||
if (otherLedger && (otherLedger->getHash () == hash))
|
||||
{
|
||||
// we closed the seam
|
||||
CondLog (invalidate != 0, lsWARNING, LedgerMaster) << "Match at " << lSeq << ", " <<
|
||||
invalidate << " prior ledgers invalidated";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mCompleteLedgers.clearValue (lSeq);
|
||||
++invalidate;
|
||||
}
|
||||
|
||||
// all prior ledgers invalidated
|
||||
CondLog (invalidate != 0, lsWARNING, LedgerMaster) << "All " << invalidate << " prior ledgers invalidated";
|
||||
}
|
||||
|
||||
void LedgerMaster::setFullLedger (Ledger::pointer ledger, bool isSynchronous, bool isCurrent)
|
||||
{
|
||||
// A new ledger has been accepted as part of the trusted chain
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "Ledger " << ledger->getLedgerSeq () << " accepted :" << ledger->getHash ();
|
||||
assert (ledger->peekAccountStateMap ()->getHash ().isNonZero ());
|
||||
|
||||
ledger->setValidated();
|
||||
mLedgerHistory.addLedger(ledger);
|
||||
|
||||
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
|
||||
mCompleteLedgers.setValue (ledger->getLedgerSeq ());
|
||||
|
||||
ledger->pendSaveValidated (isSynchronous, isCurrent);
|
||||
|
||||
if (!mValidLedger || (ledger->getLedgerSeq() > mValidLedger->getLedgerSeq()))
|
||||
mValidLedger = ledger;
|
||||
if (!mPubLedger)
|
||||
mPubLedger = ledger;
|
||||
|
||||
if ((ledger->getLedgerSeq () != 0) && mCompleteLedgers.hasValue (ledger->getLedgerSeq () - 1))
|
||||
{
|
||||
// we think we have the previous ledger, double check
|
||||
Ledger::pointer prevLedger = getLedgerBySeq (ledger->getLedgerSeq () - 1);
|
||||
|
||||
if (!prevLedger || (prevLedger->getHash () != ledger->getParentHash ()))
|
||||
{
|
||||
WriteLog (lsWARNING, LedgerMaster) << "Acquired ledger invalidates previous ledger: " <<
|
||||
(prevLedger ? "hashMismatch" : "missingLedger");
|
||||
fixMismatch (ledger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerMaster::checkAccept (uint256 const& hash)
|
||||
{
|
||||
Ledger::pointer ledger = mLedgerHistory.getLedgerByHash (hash);
|
||||
|
||||
if (!ledger)
|
||||
{
|
||||
InboundLedger::pointer l = getApp().getInboundLedgers().findCreate(hash, 0, false);
|
||||
if (l->isComplete() && !l->isFailed())
|
||||
ledger = l->getLedger();
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "checkAccept triggers acquire " << hash.GetHex();
|
||||
}
|
||||
}
|
||||
|
||||
if (ledger)
|
||||
checkAccept (ledger);
|
||||
}
|
||||
|
||||
void LedgerMaster::checkAccept (Ledger::ref ledger)
|
||||
{
|
||||
// Can we advance the last fully-validated ledger? If so, can we publish?
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mValidLedger && (ledger->getLedgerSeq() <= mValidLedger->getLedgerSeq ()))
|
||||
return;
|
||||
|
||||
int minVal = mMinValidations;
|
||||
|
||||
if (mLastValidateHash.isNonZero ())
|
||||
{
|
||||
int val = getApp().getValidations ().getTrustedValidationCount (mLastValidateHash);
|
||||
val *= MIN_VALIDATION_RATIO;
|
||||
val /= 256;
|
||||
|
||||
if (val > minVal)
|
||||
minVal = val;
|
||||
}
|
||||
|
||||
if (getConfig ().RUN_STANDALONE)
|
||||
minVal = 0;
|
||||
|
||||
int tvc = getApp().getValidations().getTrustedValidationCount(ledger->getHash());
|
||||
if (tvc < minVal) // nothing we can do
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Only " << tvc << " validations for " << ledger->getHash();
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLog (lsINFO, LedgerMaster) << "Advancing accepted ledger to " << ledger->getLedgerSeq() << " with >= " << minVal << " validations";
|
||||
|
||||
mLastValidateHash = ledger->getHash();
|
||||
mLastValidateSeq = ledger->getLedgerSeq();
|
||||
|
||||
ledger->setValidated();
|
||||
mValidLedger = ledger;
|
||||
if (!mPubLedger)
|
||||
{
|
||||
ledger->pendSaveValidated(true, true);
|
||||
mPubLedger = ledger;
|
||||
}
|
||||
|
||||
uint64 fee, fee2, ref;
|
||||
ref = getApp().getFeeTrack().getLoadBase();
|
||||
int count = getApp().getValidations().getFeeAverage(ledger->getHash(), ref, fee);
|
||||
int count2 = getApp().getValidations().getFeeAverage(ledger->getParentHash(), ref, fee2);
|
||||
|
||||
if ((count + count2) == 0)
|
||||
getApp().getFeeTrack().setRemoteFee(ref);
|
||||
else
|
||||
getApp().getFeeTrack().setRemoteFee(((fee * count) + (fee2 * count2)) / (count + count2));
|
||||
|
||||
tryAdvance ();
|
||||
}
|
||||
|
||||
// Try to publish ledgers, acquire missing ledgers
|
||||
void LedgerMaster::advanceThread()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
assert (mValidLedger && mAdvanceThread);
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "advanceThread<";
|
||||
|
||||
do
|
||||
{
|
||||
mAdvanceWork = false; // If there's work to do, we'll make progress
|
||||
bool progress = false;
|
||||
|
||||
std::list<Ledger::pointer> pubLedgers = findNewLedgersToPublish (sl);
|
||||
if (pubLedgers.empty())
|
||||
{
|
||||
if (!getConfig().RUN_STANDALONE && !getApp().getFeeTrack().isLoadedLocal() &&
|
||||
(getApp().getJobQueue().getJobCount(jtPUBOLDLEDGER) < 10) &&
|
||||
(mValidLedger->getLedgerSeq() == mPubLedger->getLedgerSeq()))
|
||||
{ // We are in sync, so can acquire
|
||||
uint32 missing = mCompleteLedgers.prevMissing(mPubLedger->getLedgerSeq());
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance discovered missing " << missing;
|
||||
if ((missing != RangeSet::absent) && (missing > 0) &&
|
||||
shouldAcquire(mValidLedger->getLedgerSeq(), getConfig().LEDGER_HISTORY, missing) &&
|
||||
((mFillInProgress == 0) || (missing > mFillInProgress)))
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerMaster) << "advanceThread should acquire";
|
||||
sl.unlock();
|
||||
Ledger::pointer nextLedger = mLedgerHistory.getLedgerBySeq(missing + 1);
|
||||
if (nextLedger)
|
||||
{
|
||||
assert (nextLedger->getLedgerSeq() == (missing + 1));
|
||||
Ledger::pointer ledger = getLedgerByHash(nextLedger->getParentHash());
|
||||
if (!ledger)
|
||||
{
|
||||
if (!getApp().getInboundLedgers().isFailure(nextLedger->getParentHash()))
|
||||
{
|
||||
InboundLedger::pointer acq =
|
||||
getApp().getInboundLedgers().findCreate(nextLedger->getParentHash(),
|
||||
nextLedger->getLedgerSeq() - 1, false);
|
||||
if (acq->isComplete() && !acq->isFailed())
|
||||
ledger = acq->getLedger();
|
||||
else if ((missing > 40000) && getApp().getOPs().shouldFetchPack(missing))
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance want fetch pack " << missing;
|
||||
getFetchPack(nextLedger);
|
||||
}
|
||||
else
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance no fetch pack for " << missing;
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "tryAdvance found failed acquire";
|
||||
}
|
||||
if (ledger)
|
||||
{
|
||||
assert(ledger->getLedgerSeq() == missing);
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance acquired " << ledger->getLedgerSeq();
|
||||
setFullLedger(ledger, false, false);
|
||||
if ((mFillInProgress == 0) && (Ledger::getHashByIndex(ledger->getLedgerSeq() - 1) == ledger->getParentHash()))
|
||||
{ // Previous ledger is in DB
|
||||
sl.lock(__FILE__, __LINE__);
|
||||
mFillInProgress = ledger->getLedgerSeq();
|
||||
getApp().getJobQueue().addJob(jtADVANCE, "tryFill", BIND_TYPE (&LedgerMaster::tryFill, this, ledger));
|
||||
sl.unlock();
|
||||
}
|
||||
progress = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < getConfig().getSize(siLedgerFetch); ++i)
|
||||
{
|
||||
uint32 seq = missing - i;
|
||||
uint256 hash = nextLedger->getLedgerHash(seq);
|
||||
if (hash.isNonZero())
|
||||
getApp().getInboundLedgers().findCreate(hash, seq, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsFATAL, LedgerMaster) << "Unable to find ledger following prevMissing " << missing;
|
||||
WriteLog (lsFATAL, LedgerMaster) << "Pub:" << mPubLedger->getLedgerSeq() << " Val:" << mValidLedger->getLedgerSeq();
|
||||
WriteLog (lsFATAL, LedgerMaster) << "Ledgers: " << getApp().getLedgerMaster().getCompleteLedgers();
|
||||
mCompleteLedgers.clearValue (missing + 1);
|
||||
progress = true;
|
||||
}
|
||||
sl.lock(__FILE__, __LINE__);
|
||||
if (mValidLedger->getLedgerSeq() != mPubLedger->getLedgerSeq())
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerMaster) << "tryAdvance found last valid changed";
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance not fetching history";
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerMaster) << "tryAdvance found " << pubLedgers.size() << " ledgers to publish";
|
||||
BOOST_FOREACH(Ledger::ref ledger, pubLedgers)
|
||||
{
|
||||
sl.unlock();
|
||||
WriteLog(lsDEBUG, LedgerMaster) << "tryAdvance publishing seq " << ledger->getLedgerSeq();
|
||||
|
||||
setFullLedger(ledger, true, true);
|
||||
getApp().getOPs().pubLedger(ledger);
|
||||
|
||||
sl.lock(__FILE__, __LINE__);
|
||||
mPubLedger = ledger;
|
||||
progress = true;
|
||||
}
|
||||
|
||||
getApp().getOPs().clearNeedNetworkLedger();
|
||||
|
||||
if (!mPathFindThread)
|
||||
{
|
||||
mPathFindThread = true;
|
||||
getApp().getJobQueue ().addJob (jtUPDATE_PF, "updatePaths",
|
||||
BIND_TYPE (&LedgerMaster::updatePaths, this));
|
||||
}
|
||||
}
|
||||
if (progress)
|
||||
mAdvanceWork = true;
|
||||
} while (mAdvanceWork);
|
||||
|
||||
mAdvanceThread = false;
|
||||
WriteLog (lsTRACE, LedgerMaster) << "advanceThread>";
|
||||
}
|
||||
|
||||
std::list<Ledger::pointer> LedgerMaster::findNewLedgersToPublish(ScopedLockType& sl)
|
||||
{
|
||||
std::list<Ledger::pointer> ret;
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "findNewLedgersToPublish<";
|
||||
if (!mPubLedger)
|
||||
{
|
||||
WriteLog (lsINFO, LedgerMaster) << "First published ledger will be " << mValidLedger->getLedgerSeq();
|
||||
ret.push_back (mValidLedger);
|
||||
}
|
||||
else if (mValidLedger->getLedgerSeq () > (mPubLedger->getLedgerSeq () + MAX_LEDGER_GAP))
|
||||
{
|
||||
WriteLog (lsWARNING, LedgerMaster) << "Gap in validated ledger stream " << mPubLedger->getLedgerSeq () << " - " <<
|
||||
mValidLedger->getLedgerSeq () - 1;
|
||||
ret.push_back (mValidLedger);
|
||||
}
|
||||
else if (mValidLedger->getLedgerSeq () > mPubLedger->getLedgerSeq ())
|
||||
{
|
||||
int acqCount = 0;
|
||||
|
||||
uint32 pubSeq = mPubLedger->getLedgerSeq() + 1; // Next sequence to publish
|
||||
uint32 valSeq = mValidLedger->getLedgerSeq();
|
||||
Ledger::pointer valLedger = mValidLedger;
|
||||
|
||||
sl.unlock();
|
||||
for (uint32 seq = pubSeq; seq <= valSeq; ++seq)
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerMaster) << "Trying to fetch/publish valid ledger " << seq;
|
||||
|
||||
Ledger::pointer ledger;
|
||||
uint256 hash = valLedger->getLedgerHash (seq);
|
||||
|
||||
if (seq == valSeq)
|
||||
{ // We need to publish the ledger we just fully validated
|
||||
ledger = valLedger;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hash.isZero ())
|
||||
{
|
||||
WriteLog (lsFATAL, LedgerMaster) << "Ledger: " << valSeq << " does not have hash for " << seq;
|
||||
assert (false);
|
||||
}
|
||||
|
||||
ledger = mLedgerHistory.getLedgerByHash (hash);
|
||||
}
|
||||
|
||||
if (!ledger && (++acqCount < 4))
|
||||
{ // We can try to acquire the ledger we need
|
||||
InboundLedger::pointer acq = getApp().getInboundLedgers ().findCreate (hash, seq, false);
|
||||
|
||||
if (!acq->isDone ())
|
||||
{
|
||||
nothing ();
|
||||
}
|
||||
else if (acq->isComplete () && !acq->isFailed ())
|
||||
{
|
||||
ledger = acq->getLedger();
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsWARNING, LedgerMaster) << "Failed to acquire a published ledger";
|
||||
getApp().getInboundLedgers().dropLedger(hash);
|
||||
acq = getApp().getInboundLedgers().findCreate(hash, seq, false);
|
||||
if (acq->isComplete())
|
||||
{
|
||||
if (acq->isFailed())
|
||||
getApp().getInboundLedgers().dropLedger(hash);
|
||||
else
|
||||
ledger = acq->getLedger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ledger && (ledger->getLedgerSeq() == pubSeq))
|
||||
{ // We acquired the next ledger we need to publish
|
||||
ledger->setValidated();
|
||||
ret.push_back (ledger);
|
||||
++pubSeq;
|
||||
}
|
||||
|
||||
}
|
||||
sl.lock(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
WriteLog (lsTRACE, LedgerMaster) << "findNewLedgersToPublish> " << ret.size();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LedgerMaster::tryAdvance()
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
|
||||
// Can't advance without at least one fully-valid ledger
|
||||
mAdvanceWork = true;
|
||||
if (!mAdvanceThread && mValidLedger)
|
||||
{
|
||||
mAdvanceThread = true;
|
||||
getApp().getJobQueue ().addJob (jtADVANCE, "advanceLedger",
|
||||
BIND_TYPE (&LedgerMaster::advanceThread, this));
|
||||
}
|
||||
}
|
||||
|
||||
uint256 LedgerMaster::getLedgerHash(uint32 desiredSeq, Ledger::ref knownGoodLedger)
|
||||
{ // Get the hash of the valid ledger with a particular sequence, given a subsequent ledger known valid
|
||||
|
||||
assert(desiredSeq < knownGoodLedger->getLedgerSeq());
|
||||
|
||||
uint256 hash = knownGoodLedger->getLedgerHash(desiredSeq);
|
||||
|
||||
if (hash.isZero ())
|
||||
{ // Not directly in the given ledger
|
||||
|
||||
uint32 seq = (desiredSeq + 255) % 256;
|
||||
assert(seq < desiredSeq);
|
||||
|
||||
uint256 i = knownGoodLedger->getLedgerHash(seq);
|
||||
if (i.isNonZero())
|
||||
{
|
||||
Ledger::pointer l = getLedgerByHash(i);
|
||||
if (l)
|
||||
{
|
||||
hash = l->getLedgerHash(desiredSeq);
|
||||
assert (hash.isNonZero());
|
||||
}
|
||||
}
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
void LedgerMaster::updatePaths ()
|
||||
{
|
||||
Ledger::pointer lastLedger;
|
||||
|
||||
do
|
||||
{
|
||||
bool newOnly = true;
|
||||
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mPathLedger || (mPathLedger->getLedgerSeq() < mValidLedger->getLedgerSeq()))
|
||||
{ // We have a new valid ledger since the last full pathfinding
|
||||
newOnly = false;
|
||||
mPathLedger = mValidLedger;
|
||||
lastLedger = mPathLedger;
|
||||
}
|
||||
else if (mPathFindNewRequest)
|
||||
{ // We have a new request but no new ledger
|
||||
newOnly = true;
|
||||
lastLedger = boost::make_shared<Ledger> (boost::ref (*mCurrentLedger), false);
|
||||
}
|
||||
else
|
||||
{ // Nothing to do
|
||||
mPathFindThread = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mPathFindNewRequest = false;
|
||||
}
|
||||
|
||||
// VFALCO TODO Fix this global variable
|
||||
PathRequest::updateAll (lastLedger, newOnly);
|
||||
|
||||
}
|
||||
while (1);
|
||||
}
|
||||
|
||||
void LedgerMaster::newPathRequest ()
|
||||
{
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mPathFindNewRequest = true;
|
||||
|
||||
if (!mPathFindThread)
|
||||
{
|
||||
mPathFindThread = true;
|
||||
getApp().getJobQueue ().addJob (jtUPDATE_PF, "updatePaths",
|
||||
BIND_TYPE (&LedgerMaster::updatePaths, this));
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
239
src/ripple_app/ledger/LedgerMaster.h
Normal file
239
src/ripple_app/ledger/LedgerMaster.h
Normal file
@@ -0,0 +1,239 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGERMASTER_H
|
||||
#define RIPPLE_LEDGERMASTER_H
|
||||
|
||||
// Tracks the current ledger and any ledgers in the process of closing
|
||||
// Tracks ledger history
|
||||
// Tracks held transactions
|
||||
|
||||
// VFALCO TODO Rename to Ledgers
|
||||
// It sounds like this holds all the ledgers...
|
||||
//
|
||||
class LedgerMaster : LeakChecked <LedgerMaster>
|
||||
{
|
||||
public:
|
||||
typedef FUNCTION_TYPE <void (Ledger::ref)> callback;
|
||||
|
||||
public:
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
|
||||
LedgerMaster ()
|
||||
: mLock (this, "LedgerMaster", __FILE__, __LINE__)
|
||||
, mHeldTransactions (uint256 ())
|
||||
, mMinValidations (0)
|
||||
, mLastValidateSeq (0)
|
||||
, mAdvanceThread (false)
|
||||
, mAdvanceWork (false)
|
||||
, mFillInProgress (0)
|
||||
, mPathFindThread (false)
|
||||
, mPathFindNewRequest (false)
|
||||
{
|
||||
}
|
||||
|
||||
~LedgerMaster ()
|
||||
{
|
||||
}
|
||||
|
||||
uint32 getCurrentLedgerIndex ();
|
||||
|
||||
LockType& peekMutex ()
|
||||
{
|
||||
return mLock;
|
||||
}
|
||||
|
||||
// The current ledger is the ledger we believe new transactions should go in
|
||||
Ledger::ref getCurrentLedger ()
|
||||
{
|
||||
return mCurrentLedger;
|
||||
}
|
||||
|
||||
// An immutable snapshot of the current ledger
|
||||
Ledger::ref getCurrentSnapshot ();
|
||||
|
||||
// The finalized ledger is the last closed/accepted ledger
|
||||
Ledger::ref getClosedLedger ()
|
||||
{
|
||||
return mClosedLedger;
|
||||
}
|
||||
|
||||
// The validated ledger is the last fully validated ledger
|
||||
Ledger::ref getValidatedLedger ()
|
||||
{
|
||||
return mValidLedger;
|
||||
}
|
||||
|
||||
// This is the last ledger we published to clients and can lag the validated ledger
|
||||
Ledger::ref getPublishedLedger ()
|
||||
{
|
||||
return mPubLedger;
|
||||
}
|
||||
|
||||
int getPublishedLedgerAge ();
|
||||
int getValidatedLedgerAge ();
|
||||
bool isCaughtUp(std::string& reason);
|
||||
|
||||
TER doTransaction (SerializedTransaction::ref txn, TransactionEngineParams params, bool& didApply);
|
||||
|
||||
int getMinValidations ()
|
||||
{
|
||||
return mMinValidations;
|
||||
}
|
||||
void setMinValidations (int v)
|
||||
{
|
||||
mMinValidations = v;
|
||||
}
|
||||
|
||||
void pushLedger (Ledger::pointer newLedger);
|
||||
void pushLedger (Ledger::pointer newLCL, Ledger::pointer newOL);
|
||||
void storeLedger (Ledger::pointer);
|
||||
void forceValid (Ledger::pointer);
|
||||
|
||||
void setFullLedger (Ledger::pointer ledger, bool isSynchronous, bool isCurrent);
|
||||
|
||||
void switchLedgers (Ledger::pointer lastClosed, Ledger::pointer newCurrent);
|
||||
|
||||
std::string getCompleteLedgers ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mCompleteLedgers.toString ();
|
||||
}
|
||||
|
||||
Ledger::pointer closeLedger (bool recoverHeldTransactions);
|
||||
|
||||
uint256 getHashBySeq (uint32 index)
|
||||
{
|
||||
uint256 hash = mLedgerHistory.getLedgerHash (index);
|
||||
|
||||
if (hash.isNonZero ())
|
||||
return hash;
|
||||
|
||||
return Ledger::getHashByIndex (index);
|
||||
}
|
||||
|
||||
Ledger::pointer getLedgerBySeq (uint32 index)
|
||||
{
|
||||
if (mCurrentLedger && (mCurrentLedger->getLedgerSeq () == index))
|
||||
return mCurrentLedger;
|
||||
|
||||
if (mClosedLedger && (mClosedLedger->getLedgerSeq () == index))
|
||||
return mClosedLedger;
|
||||
|
||||
Ledger::pointer ret = mLedgerHistory.getLedgerBySeq (index);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ScopedLockType ml (mLock, __FILE__, __LINE__);
|
||||
mCompleteLedgers.clearValue (index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ledger::pointer getLedgerByHash (uint256 const& hash)
|
||||
{
|
||||
if (hash.isZero ())
|
||||
return boost::make_shared<Ledger> (boost::ref (*mCurrentLedger), false);
|
||||
|
||||
if (mCurrentLedger && (mCurrentLedger->getHash () == hash))
|
||||
return boost::make_shared<Ledger> (boost::ref (*mCurrentLedger), false);
|
||||
|
||||
if (mClosedLedger && (mClosedLedger->getHash () == hash))
|
||||
return mClosedLedger;
|
||||
|
||||
return mLedgerHistory.getLedgerByHash (hash);
|
||||
}
|
||||
|
||||
void setLedgerRangePresent (uint32 minV, uint32 maxV)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mCompleteLedgers.setRange (minV, maxV);
|
||||
}
|
||||
|
||||
uint256 getLedgerHash(uint32 desiredSeq, Ledger::ref knownGoodLedger);
|
||||
|
||||
void addHeldTransaction (Transaction::ref trans);
|
||||
void fixMismatch (Ledger::ref ledger);
|
||||
|
||||
bool haveLedgerRange (uint32 from, uint32 to);
|
||||
bool haveLedger (uint32 seq);
|
||||
bool getValidatedRange (uint32& minVal, uint32& maxVal);
|
||||
bool getFullValidatedRange (uint32& minVal, uint32& maxVal);
|
||||
|
||||
void tune (int size, int age)
|
||||
{
|
||||
mLedgerHistory.tune (size, age);
|
||||
}
|
||||
void sweep ()
|
||||
{
|
||||
mLedgerHistory.sweep ();
|
||||
}
|
||||
float getCacheHitRate ()
|
||||
{
|
||||
return mLedgerHistory.getCacheHitRate ();
|
||||
}
|
||||
|
||||
void addValidateCallback (callback& c)
|
||||
{
|
||||
mOnValidate.push_back (c);
|
||||
}
|
||||
|
||||
void checkAccept (Ledger::ref ledger);
|
||||
void checkAccept (uint256 const& hash);
|
||||
|
||||
void tryAdvance ();
|
||||
void newPathRequest ();
|
||||
|
||||
static bool shouldAcquire (uint32 currentLedgerID, uint32 ledgerHistory, uint32 targetLedger);
|
||||
|
||||
private:
|
||||
std::list<Ledger::pointer> findNewLedgersToPublish(ScopedLockType& sl);
|
||||
|
||||
void applyFutureTransactions (uint32 ledgerIndex);
|
||||
bool isValidTransaction (Transaction::ref trans);
|
||||
bool isTransactionOnFutureList (Transaction::ref trans);
|
||||
|
||||
void getFetchPack (Ledger::ref have);
|
||||
void tryFill (Ledger::pointer);
|
||||
void advanceThread ();
|
||||
void updatePaths ();
|
||||
|
||||
private:
|
||||
LockType mLock;
|
||||
|
||||
TransactionEngine mEngine;
|
||||
|
||||
Ledger::pointer mCurrentLedger; // The ledger we are currently processiong
|
||||
Ledger::pointer mCurrentSnapshot; // Snapshot of the current ledger
|
||||
Ledger::pointer mClosedLedger; // The ledger that most recently closed
|
||||
Ledger::pointer mValidLedger; // The highest-sequence ledger we have fully accepted
|
||||
Ledger::pointer mPubLedger; // The last ledger we have published
|
||||
Ledger::pointer mPathLedger; // The last ledger we did pathfinding against
|
||||
|
||||
LedgerHistory mLedgerHistory;
|
||||
|
||||
CanonicalTXSet mHeldTransactions;
|
||||
|
||||
RangeSet mCompleteLedgers;
|
||||
|
||||
int mMinValidations; // The minimum validations to publish a ledger
|
||||
uint256 mLastValidateHash;
|
||||
uint32 mLastValidateSeq;
|
||||
std::list<callback> mOnValidate; // Called when a ledger has enough validations
|
||||
|
||||
std::list<Ledger::pointer> mPubLedgers; // List of ledgers to publish
|
||||
bool mAdvanceThread; // Publish thread is running
|
||||
bool mAdvanceWork; // Publish thread has work to do
|
||||
int mFillInProgress;
|
||||
|
||||
bool mPathFindThread; // Pathfind thread is running
|
||||
bool mPathFindNewLedger;
|
||||
bool mPathFindNewRequest;
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
103
src/ripple_app/ledger/LedgerProposal.cpp
Normal file
103
src/ripple_app/ledger/LedgerProposal.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
LedgerProposal::LedgerProposal (uint256 const& pLgr, uint32 seq, uint256 const& tx, uint32 closeTime,
|
||||
const RippleAddress& naPeerPublic, uint256 const& suppression) :
|
||||
mPreviousLedger (pLgr), mCurrentHash (tx), mSuppression (suppression), mCloseTime (closeTime),
|
||||
mProposeSeq (seq), mPublicKey (naPeerPublic)
|
||||
{
|
||||
// XXX Validate key.
|
||||
// if (!mKey->SetPubKey(pubKey))
|
||||
// throw std::runtime_error("Invalid public key in proposal");
|
||||
|
||||
mPeerID = mPublicKey.getNodeID ();
|
||||
mTime = boost::posix_time::second_clock::universal_time ();
|
||||
}
|
||||
|
||||
|
||||
LedgerProposal::LedgerProposal (const RippleAddress& naPub, const RippleAddress& naPriv,
|
||||
uint256 const& prevLgr, uint256 const& position, uint32 closeTime) :
|
||||
mPreviousLedger (prevLgr), mCurrentHash (position), mCloseTime (closeTime), mProposeSeq (0),
|
||||
mPublicKey (naPub), mPrivateKey (naPriv)
|
||||
{
|
||||
mPeerID = mPublicKey.getNodeID ();
|
||||
mTime = boost::posix_time::second_clock::universal_time ();
|
||||
}
|
||||
|
||||
LedgerProposal::LedgerProposal (uint256 const& prevLgr, uint256 const& position, uint32 closeTime) :
|
||||
mPreviousLedger (prevLgr), mCurrentHash (position), mCloseTime (closeTime), mProposeSeq (0)
|
||||
{
|
||||
mTime = boost::posix_time::second_clock::universal_time ();
|
||||
}
|
||||
|
||||
uint256 LedgerProposal::getSigningHash () const
|
||||
{
|
||||
Serializer s ((32 + 32 + 32 + 256 + 256) / 8);
|
||||
|
||||
s.add32 (getConfig ().SIGN_PROPOSAL);
|
||||
s.add32 (mProposeSeq);
|
||||
s.add32 (mCloseTime);
|
||||
s.add256 (mPreviousLedger);
|
||||
s.add256 (mCurrentHash);
|
||||
|
||||
return s.getSHA512Half ();
|
||||
}
|
||||
|
||||
bool LedgerProposal::checkSign (const std::string& signature, uint256 const& signingHash)
|
||||
{
|
||||
return mPublicKey.verifyNodePublic (signingHash, signature);
|
||||
}
|
||||
|
||||
bool LedgerProposal::changePosition (uint256 const& newPosition, uint32 closeTime)
|
||||
{
|
||||
if (mProposeSeq == seqLeave)
|
||||
return false;
|
||||
|
||||
mCurrentHash = newPosition;
|
||||
mCloseTime = closeTime;
|
||||
mTime = boost::posix_time::second_clock::universal_time ();
|
||||
++mProposeSeq;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LedgerProposal::bowOut ()
|
||||
{
|
||||
mTime = boost::posix_time::second_clock::universal_time ();
|
||||
mProposeSeq = seqLeave;
|
||||
}
|
||||
|
||||
Blob LedgerProposal::sign (void)
|
||||
{
|
||||
Blob ret;
|
||||
|
||||
mPrivateKey.signNodePrivate (getSigningHash (), ret);
|
||||
// XXX If this can fail, find out sooner.
|
||||
// if (!mPrivateKey.signNodePrivate(getSigningHash(), ret))
|
||||
// throw std::runtime_error("unable to sign proposal");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value LedgerProposal::getJson () const
|
||||
{
|
||||
Json::Value ret = Json::objectValue;
|
||||
ret["previous_ledger"] = mPreviousLedger.GetHex ();
|
||||
|
||||
if (mProposeSeq != seqLeave)
|
||||
{
|
||||
ret["transaction_hash"] = mCurrentHash.GetHex ();
|
||||
ret["propose_seq"] = mProposeSeq;
|
||||
}
|
||||
|
||||
ret["close_time"] = mCloseTime;
|
||||
|
||||
if (mPublicKey.isValid ())
|
||||
ret["peer_id"] = mPublicKey.humanNodePublic ();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
125
src/ripple_app/ledger/LedgerProposal.h
Normal file
125
src/ripple_app/ledger/LedgerProposal.h
Normal file
@@ -0,0 +1,125 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef __PROPOSELEDGER__
|
||||
#define __PROPOSELEDGER__
|
||||
|
||||
class LedgerProposal
|
||||
: public CountedObject <LedgerProposal>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "LedgerProposal"; }
|
||||
|
||||
static const uint32 seqLeave = 0xffffffff; // leaving the consensus process
|
||||
|
||||
typedef boost::shared_ptr<LedgerProposal> pointer;
|
||||
typedef const pointer& ref;
|
||||
|
||||
// proposal from peer
|
||||
LedgerProposal (uint256 const & prevLgr, uint32 proposeSeq, uint256 const & propose,
|
||||
uint32 closeTime, const RippleAddress & naPeerPublic, uint256 const & suppress);
|
||||
|
||||
// our first proposal
|
||||
LedgerProposal (const RippleAddress & pubKey, const RippleAddress & privKey,
|
||||
uint256 const & prevLedger, uint256 const & position, uint32 closeTime);
|
||||
|
||||
// an unsigned "dummy" proposal for nodes not validating
|
||||
LedgerProposal (uint256 const & prevLedger, uint256 const & position, uint32 closeTime);
|
||||
|
||||
uint256 getSigningHash () const;
|
||||
bool checkSign (const std::string & signature, uint256 const & signingHash);
|
||||
bool checkSign (const std::string & signature)
|
||||
{
|
||||
return checkSign (signature, getSigningHash ());
|
||||
}
|
||||
bool checkSign ()
|
||||
{
|
||||
return checkSign (mSignature, getSigningHash ());
|
||||
}
|
||||
|
||||
const uint160& getPeerID () const
|
||||
{
|
||||
return mPeerID;
|
||||
}
|
||||
uint256 const& getCurrentHash () const
|
||||
{
|
||||
return mCurrentHash;
|
||||
}
|
||||
uint256 const& getPrevLedger () const
|
||||
{
|
||||
return mPreviousLedger;
|
||||
}
|
||||
uint256 const& getHashRouter () const
|
||||
{
|
||||
return mSuppression;
|
||||
}
|
||||
uint32 getProposeSeq () const
|
||||
{
|
||||
return mProposeSeq;
|
||||
}
|
||||
uint32 getCloseTime () const
|
||||
{
|
||||
return mCloseTime;
|
||||
}
|
||||
const RippleAddress& peekPublic () const
|
||||
{
|
||||
return mPublicKey;
|
||||
}
|
||||
Blob getPubKey () const
|
||||
{
|
||||
return mPublicKey.getNodePublic ();
|
||||
}
|
||||
Blob sign ();
|
||||
|
||||
void setPrevLedger (uint256 const & prevLedger)
|
||||
{
|
||||
mPreviousLedger = prevLedger;
|
||||
}
|
||||
void setSignature (const std::string & signature)
|
||||
{
|
||||
mSignature = signature;
|
||||
}
|
||||
bool hasSignature ()
|
||||
{
|
||||
return !mSignature.empty ();
|
||||
}
|
||||
bool isPrevLedger (uint256 const & pl)
|
||||
{
|
||||
return mPreviousLedger == pl;
|
||||
}
|
||||
bool isBowOut ()
|
||||
{
|
||||
return mProposeSeq == seqLeave;
|
||||
}
|
||||
|
||||
const boost::posix_time::ptime getCreateTime ()
|
||||
{
|
||||
return mTime;
|
||||
}
|
||||
bool isStale (boost::posix_time::ptime cutoff)
|
||||
{
|
||||
return mTime <= cutoff;
|
||||
}
|
||||
|
||||
bool changePosition (uint256 const & newPosition, uint32 newCloseTime);
|
||||
void bowOut ();
|
||||
Json::Value getJson () const;
|
||||
|
||||
private:
|
||||
uint256 mPreviousLedger, mCurrentHash, mSuppression;
|
||||
uint32 mCloseTime, mProposeSeq;
|
||||
|
||||
uint160 mPeerID;
|
||||
RippleAddress mPublicKey;
|
||||
RippleAddress mPrivateKey; // If ours
|
||||
|
||||
std::string mSignature; // set only if needed
|
||||
boost::posix_time::ptime mTime;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
156
src/ripple_app/ledger/LedgerTiming.cpp
Normal file
156
src/ripple_app/ledger/LedgerTiming.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// VFALCO Should rename ContinuousLedgerTiming to LedgerTiming
|
||||
|
||||
struct LedgerTiming; // for Log
|
||||
|
||||
SETUP_LOG (LedgerTiming)
|
||||
|
||||
// NOTE: First and last times must be repeated
|
||||
int ContinuousLedgerTiming::LedgerTimeResolution[] = { 10, 10, 20, 30, 60, 90, 120, 120 };
|
||||
|
||||
// Called when a ledger is open and no close is in progress -- when a transaction is received and no close
|
||||
// is in process, or when a close completes. Returns the number of seconds the ledger should be be open.
|
||||
bool ContinuousLedgerTiming::shouldClose (
|
||||
bool anyTransactions,
|
||||
int previousProposers, // proposers in the last closing
|
||||
int proposersClosed, // proposers who have currently closed this ledgers
|
||||
int proposersValidated, // proposers who have validated the last closed ledger
|
||||
int previousMSeconds, // milliseconds the previous ledger took to reach consensus
|
||||
int currentMSeconds, // milliseconds since the previous ledger closed
|
||||
int openMSeconds, // milliseconds since the previous LCL was computed
|
||||
int idleInterval) // network's desired idle interval
|
||||
{
|
||||
if ((previousMSeconds < -1000) || (previousMSeconds > 600000) ||
|
||||
(currentMSeconds < -1000) || (currentMSeconds > 600000))
|
||||
{
|
||||
WriteLog (lsWARNING, LedgerTiming) <<
|
||||
boost::str (boost::format ("CLC::shouldClose range Trans=%s, Prop: %d/%d, Secs: %d (last:%d)")
|
||||
% (anyTransactions ? "yes" : "no") % previousProposers % proposersClosed
|
||||
% currentMSeconds % previousMSeconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!anyTransactions)
|
||||
{
|
||||
// no transactions so far this interval
|
||||
if (proposersClosed > (previousProposers / 4)) // did we miss a transaction?
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerTiming) << "no transactions, many proposers: now (" << proposersClosed << " closed, "
|
||||
<< previousProposers << " before)";
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0 // This false triggers on the genesis ledger
|
||||
|
||||
if (previousMSeconds > (1000 * (LEDGER_IDLE_INTERVAL + 2))) // the last ledger was very slow to close
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerTiming) << "was slow to converge (p=" << (previousMSeconds) << ")";
|
||||
|
||||
if (previousMSeconds < 2000)
|
||||
return previousMSeconds;
|
||||
|
||||
return previousMSeconds - 1000;
|
||||
}
|
||||
|
||||
#endif
|
||||
return currentMSeconds >= (idleInterval * 1000); // normal idle
|
||||
}
|
||||
|
||||
if ((openMSeconds < LEDGER_MIN_CLOSE) && ((proposersClosed + proposersValidated) < (previousProposers / 2 )))
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerTiming) << "Must wait minimum time before closing";
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((currentMSeconds < previousMSeconds) && ((proposersClosed + proposersValidated) < previousProposers))
|
||||
{
|
||||
WriteLog (lsDEBUG, LedgerTiming) << "We are waiting for more closes/validations";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // this ledger should close now
|
||||
}
|
||||
|
||||
// Returns whether we have a consensus or not. If so, we expect all honest nodes
|
||||
// to already have everything they need to accept a consensus. Our vote is 'locked in'.
|
||||
bool ContinuousLedgerTiming::haveConsensus (
|
||||
int previousProposers, // proposers in the last closing (not including us)
|
||||
int currentProposers, // proposers in this closing so far (not including us)
|
||||
int currentAgree, // proposers who agree with us
|
||||
int currentFinished, // proposers who have validated a ledger after this one
|
||||
int previousAgreeTime, // how long it took to agree on the last ledger
|
||||
int currentAgreeTime, // how long we've been trying to agree
|
||||
bool forReal, // deciding whether to stop consensus process
|
||||
bool& failed) // we can't reach a consensus
|
||||
{
|
||||
WriteLog (lsTRACE, LedgerTiming) << boost::str (boost::format ("CLC::haveConsensus: prop=%d/%d agree=%d validated=%d time=%d/%d%s") %
|
||||
currentProposers % previousProposers % currentAgree % currentFinished % currentAgreeTime % previousAgreeTime %
|
||||
(forReal ? "" : "X"));
|
||||
|
||||
if (currentAgreeTime <= LEDGER_MIN_CONSENSUS)
|
||||
return false;
|
||||
|
||||
if (currentProposers < (previousProposers * 3 / 4))
|
||||
{
|
||||
// Less than 3/4 of the last ledger's proposers are present, we may need more time
|
||||
if (currentAgreeTime < (previousAgreeTime + LEDGER_MIN_CONSENSUS))
|
||||
{
|
||||
CondLog (forReal, lsTRACE, LedgerTiming) << "too fast, not enough proposers";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If 80% of current proposers (plus us) agree on a set, we have consensus
|
||||
if (((currentAgree * 100 + 100) / (currentProposers + 1)) > 80)
|
||||
{
|
||||
CondLog (forReal, lsINFO, LedgerTiming) << "normal consensus";
|
||||
failed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If 80% of the nodes on your UNL have moved on, you should declare consensus
|
||||
if (((currentFinished * 100) / (currentProposers + 1)) > 80)
|
||||
{
|
||||
CondLog (forReal, lsWARNING, LedgerTiming) << "We see no consensus, but 80% of nodes have moved on";
|
||||
failed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// no consensus yet
|
||||
CondLog (forReal, lsTRACE, LedgerTiming) << "no consensus";
|
||||
return false;
|
||||
}
|
||||
|
||||
int ContinuousLedgerTiming::getNextLedgerTimeResolution (int previousResolution, bool previousAgree, int ledgerSeq)
|
||||
{
|
||||
assert (ledgerSeq);
|
||||
|
||||
if ((!previousAgree) && ((ledgerSeq % LEDGER_RES_DECREASE) == 0))
|
||||
{
|
||||
// reduce resolution
|
||||
int i = 1;
|
||||
|
||||
while (LedgerTimeResolution[i] != previousResolution)
|
||||
++i;
|
||||
|
||||
return LedgerTimeResolution[i + 1];
|
||||
}
|
||||
|
||||
if ((previousAgree) && ((ledgerSeq % LEDGER_RES_INCREASE) == 0))
|
||||
{
|
||||
// increase resolution
|
||||
int i = 1;
|
||||
|
||||
while (LedgerTimeResolution[i] != previousResolution)
|
||||
++i;
|
||||
|
||||
return LedgerTimeResolution[i - 1];
|
||||
}
|
||||
|
||||
return previousResolution;
|
||||
}
|
||||
87
src/ripple_app/ledger/LedgerTiming.h
Normal file
87
src/ripple_app/ledger/LedgerTiming.h
Normal file
@@ -0,0 +1,87 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef __LEDGERTIMING__
|
||||
#define __LEDGERTIMING__
|
||||
|
||||
// The number of seconds a ledger may remain idle before closing
|
||||
# define LEDGER_IDLE_INTERVAL 15
|
||||
|
||||
// The number of seconds a validation remains current after its ledger's close time
|
||||
// This is a safety to protect against very old validations and the time it takes to adjust
|
||||
// the close time accuracy window
|
||||
# define LEDGER_VAL_INTERVAL 300
|
||||
|
||||
// The number of seconds before a close time that we consider a validation acceptable
|
||||
// This protects against extreme clock errors
|
||||
# define LEDGER_EARLY_INTERVAL 180
|
||||
|
||||
// The number of milliseconds we wait minimum to ensure participation
|
||||
# define LEDGER_MIN_CONSENSUS 2000
|
||||
|
||||
// The number of milliseconds we wait minimum to ensure others have computed the LCL
|
||||
# define LEDGER_MIN_CLOSE 2000
|
||||
|
||||
// Initial resolution of ledger close time
|
||||
# define LEDGER_TIME_ACCURACY 30
|
||||
|
||||
// How often to increase resolution
|
||||
# define LEDGER_RES_INCREASE 8
|
||||
|
||||
// How often to decrease resolution
|
||||
# define LEDGER_RES_DECREASE 1
|
||||
|
||||
// How often we check state or change positions (in milliseconds)
|
||||
# define LEDGER_GRANULARITY 1000
|
||||
|
||||
// The percentage of active trusted validators that must be able to
|
||||
// keep up with the network or we consider the network overloaded
|
||||
# define LEDGER_NET_RATIO 70
|
||||
|
||||
// How long we consider a proposal fresh
|
||||
# define PROPOSE_FRESHNESS 20
|
||||
|
||||
// How often we force generating a new proposal to keep ours fresh
|
||||
# define PROPOSE_INTERVAL 12
|
||||
|
||||
// Avalanche tuning
|
||||
# define AV_INIT_CONSENSUS_PCT 50 // percentage of nodes on our UNL that must vote yes
|
||||
|
||||
# define AV_MID_CONSENSUS_TIME 50 // percentage of previous close time before we advance
|
||||
# define AV_MID_CONSENSUS_PCT 65 // percentage of nodes that most vote yes after advancing
|
||||
|
||||
# define AV_LATE_CONSENSUS_TIME 85 // percentage of previous close time before we advance
|
||||
# define AV_LATE_CONSENSUS_PCT 70 // percentage of nodes that most vote yes after advancing
|
||||
|
||||
# define AV_STUCK_CONSENSUS_TIME 200
|
||||
# define AV_STUCK_CONSENSUS_PCT 95
|
||||
|
||||
# define AV_CT_CONSENSUS_PCT 75
|
||||
|
||||
class ContinuousLedgerTiming
|
||||
{
|
||||
public:
|
||||
|
||||
static int LedgerTimeResolution[];
|
||||
|
||||
// Returns the number of seconds the ledger was or should be open
|
||||
// Call when a consensus is reached and when any transaction is relayed to be added
|
||||
static bool shouldClose (
|
||||
bool anyTransactions,
|
||||
int previousProposers, int proposersClosed, int proposerersValidated,
|
||||
int previousMSeconds, int currentMSeconds, int openMSeconds,
|
||||
int idleInterval);
|
||||
|
||||
static bool haveConsensus (
|
||||
int previousProposers, int currentProposers,
|
||||
int currentAgree, int currentClosed,
|
||||
int previousAgreeTime, int currentAgreeTime,
|
||||
bool forReal, bool& failed);
|
||||
|
||||
static int getNextLedgerTimeResolution (int previousResolution, bool previousAgree, int ledgerSeq);
|
||||
};
|
||||
|
||||
#endif
|
||||
264
src/ripple_app/ledger/OrderBookDB.cpp
Normal file
264
src/ripple_app/ledger/OrderBookDB.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (OrderBookDB)
|
||||
|
||||
OrderBookDB::OrderBookDB ()
|
||||
: mLock (this, "OrderBookDB", __FILE__, __LINE__)
|
||||
, mSeq (0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OrderBookDB::invalidate ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mSeq = 0;
|
||||
}
|
||||
|
||||
void OrderBookDB::setup (Ledger::ref ledger)
|
||||
{
|
||||
boost::unordered_set<uint256> mSeen;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if ((mSeq != 0) && (ledger->getLedgerSeq () >= mSeq) && ((ledger->getLedgerSeq() - mSeq) < 10))
|
||||
return;
|
||||
|
||||
mSeq = ledger->getLedgerSeq ();
|
||||
|
||||
LoadEvent::autoptr ev = getApp().getJobQueue ().getLoadEventAP (jtOB_SETUP, "OrderBookDB::setup");
|
||||
|
||||
mDestMap.clear ();
|
||||
mSourceMap.clear ();
|
||||
mXRPBooks.clear ();
|
||||
|
||||
WriteLog (lsDEBUG, OrderBookDB) << "OrderBookDB>";
|
||||
|
||||
// walk through the entire ledger looking for orderbook entries
|
||||
int books = 0;
|
||||
uint256 currentIndex = ledger->getFirstLedgerIndex ();
|
||||
|
||||
while (currentIndex.isNonZero ())
|
||||
{
|
||||
SLE::pointer entry = ledger->getSLEi (currentIndex);
|
||||
|
||||
if ((entry->getType () == ltDIR_NODE) && (entry->isFieldPresent (sfExchangeRate)) &&
|
||||
(entry->getFieldH256 (sfRootIndex) == currentIndex))
|
||||
{
|
||||
const uint160& ci = entry->getFieldH160 (sfTakerPaysCurrency);
|
||||
const uint160& co = entry->getFieldH160 (sfTakerGetsCurrency);
|
||||
const uint160& ii = entry->getFieldH160 (sfTakerPaysIssuer);
|
||||
const uint160& io = entry->getFieldH160 (sfTakerGetsIssuer);
|
||||
|
||||
uint256 index = Ledger::getBookBase (ci, ii, co, io);
|
||||
|
||||
if (mSeen.insert (index).second)
|
||||
{
|
||||
// VFALCO TODO Reduce the clunkiness of these parameter wrappers
|
||||
OrderBook::pointer book = boost::make_shared<OrderBook> (boost::cref (index),
|
||||
boost::cref (ci), boost::cref (co), boost::cref (ii), boost::cref (io));
|
||||
|
||||
mSourceMap[currencyIssuer_ct (ci, ii)].push_back (book);
|
||||
mDestMap[currencyIssuer_ct (co, io)].push_back (book);
|
||||
if (co.isZero())
|
||||
mXRPBooks.insert(currencyIssuer_ct (ci, ii));
|
||||
++books;
|
||||
}
|
||||
}
|
||||
|
||||
currentIndex = ledger->getNextLedgerIndex (currentIndex);
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, OrderBookDB) << "OrderBookDB< " << books << " books found";
|
||||
}
|
||||
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
void OrderBookDB::getBooksByTakerPays (const uint160& issuerID, const uint160& currencyID,
|
||||
std::vector<OrderBook::pointer>& bookRet)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
boost::unordered_map< currencyIssuer_t, std::vector<OrderBook::pointer> >::const_iterator
|
||||
it = mSourceMap.find (currencyIssuer_ct (currencyID, issuerID));
|
||||
|
||||
if (it != mSourceMap.end ())
|
||||
bookRet = it->second;
|
||||
else
|
||||
bookRet.clear ();
|
||||
}
|
||||
|
||||
bool OrderBookDB::isBookToXRP(const uint160& issuerID, const uint160& currencyID)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
return mXRPBooks.count(currencyIssuer_ct(currencyID, issuerID)) > 0;
|
||||
}
|
||||
|
||||
// return list of all orderbooks that give this issuerID and currencyID
|
||||
void OrderBookDB::getBooksByTakerGets (const uint160& issuerID, const uint160& currencyID,
|
||||
std::vector<OrderBook::pointer>& bookRet)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
boost::unordered_map< currencyIssuer_t, std::vector<OrderBook::pointer> >::const_iterator
|
||||
it = mDestMap.find (currencyIssuer_ct (currencyID, issuerID));
|
||||
|
||||
if (it != mDestMap.end ())
|
||||
bookRet = it->second;
|
||||
else
|
||||
bookRet.clear ();
|
||||
}
|
||||
|
||||
BookListeners::pointer OrderBookDB::makeBookListeners (const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
BookListeners::pointer ret = getBookListeners (currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
ret = boost::make_shared<BookListeners> ();
|
||||
mListeners[issuerPays][issuerGets][currencyPays][currencyGets] = ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BookListeners::pointer OrderBookDB::getBookListeners (const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets)
|
||||
{
|
||||
BookListeners::pointer ret;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > > >::iterator
|
||||
it0 = mListeners.find (issuerPays);
|
||||
|
||||
if (it0 == mListeners.end ())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > >::iterator
|
||||
it1 = (*it0).second.find (issuerGets);
|
||||
|
||||
if (it1 == (*it0).second.end ())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, std::map<uint160, BookListeners::pointer> >::iterator it2 = (*it1).second.find (currencyPays);
|
||||
|
||||
if (it2 == (*it1).second.end ())
|
||||
return ret;
|
||||
|
||||
std::map<uint160, BookListeners::pointer>::iterator it3 = (*it2).second.find (currencyGets);
|
||||
|
||||
if (it3 == (*it2).second.end ())
|
||||
return ret;
|
||||
|
||||
return (*it3).second;
|
||||
}
|
||||
|
||||
// Based on the meta, send the meta to the streams that are listening
|
||||
// We need to determine which streams a given meta effects
|
||||
void OrderBookDB::processTxn (Ledger::ref ledger, const AcceptedLedgerTx& alTx, Json::Value& jvObj)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (alTx.getResult () == tesSUCCESS)
|
||||
{
|
||||
// check if this is an offer or an offer cancel or a payment that consumes an offer
|
||||
//check to see what the meta looks like
|
||||
BOOST_FOREACH (STObject & node, alTx.getMeta ()->getNodes ())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (node.getFieldU16 (sfLedgerEntryType) == ltOFFER)
|
||||
{
|
||||
SField* field = NULL;
|
||||
|
||||
if (node.getFName () == sfModifiedNode)
|
||||
{
|
||||
field = &sfPreviousFields;
|
||||
}
|
||||
else if (node.getFName () == sfCreatedNode)
|
||||
{
|
||||
field = &sfNewFields;
|
||||
}
|
||||
else if (node.getFName () == sfDeletedNode)
|
||||
{
|
||||
field = &sfFinalFields;
|
||||
}
|
||||
|
||||
if (field)
|
||||
{
|
||||
const STObject* data = dynamic_cast<const STObject*> (node.peekAtPField (*field));
|
||||
|
||||
if (data)
|
||||
{
|
||||
const STAmount& takerGets = data->getFieldAmount (sfTakerGets);
|
||||
const uint160& currencyGets = takerGets.getCurrency ();
|
||||
const uint160& issuerGets = takerGets.getIssuer ();
|
||||
|
||||
const STAmount& takerPays = data->getFieldAmount (sfTakerPays);
|
||||
const uint160& currencyPays = takerPays.getCurrency ();
|
||||
const uint160& issuerPays = takerPays.getIssuer ();
|
||||
|
||||
// determine the OrderBook
|
||||
BookListeners::pointer book =
|
||||
getBookListeners (currencyPays, currencyGets, issuerPays, issuerGets);
|
||||
|
||||
if (book)
|
||||
book->publish (jvObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
WriteLog (lsINFO, OrderBookDB) << "Fields not found in OrderBookDB::processTxn";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
BookListeners::BookListeners ()
|
||||
: mLock (this, "BookListeners", __FILE__, __LINE__)
|
||||
{
|
||||
}
|
||||
|
||||
void BookListeners::addSubscriber (InfoSub::ref sub)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mListeners[sub->getSeq ()] = sub;
|
||||
}
|
||||
|
||||
void BookListeners::removeSubscriber (uint64 seq)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mListeners.erase (seq);
|
||||
}
|
||||
|
||||
void BookListeners::publish (Json::Value& jvObj)
|
||||
{
|
||||
Json::FastWriter jfwWriter;
|
||||
std::string sObj = jfwWriter.write (jvObj);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
NetworkOPs::SubMapType::const_iterator it = mListeners.begin ();
|
||||
|
||||
while (it != mListeners.end ())
|
||||
{
|
||||
InfoSub::pointer p = it->second.lock ();
|
||||
|
||||
if (p)
|
||||
{
|
||||
p->send (jvObj, sObj, true);
|
||||
++it;
|
||||
}
|
||||
else
|
||||
it = mListeners.erase (it);
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
82
src/ripple_app/ledger/OrderBookDB.h
Normal file
82
src/ripple_app/ledger/OrderBookDB.h
Normal file
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef ORDERBOOK_DB_H
|
||||
#define ORDERBOOK_DB_H
|
||||
|
||||
//
|
||||
// XXX Eventually make this cached and just update it as transactions come in.
|
||||
// But, for now it is probably faster to just generate it each time.
|
||||
//
|
||||
|
||||
typedef std::pair<uint160, uint160> currencyIssuer_t;
|
||||
|
||||
#ifdef C11X
|
||||
typedef std::pair<const uint160&, const uint160&> currencyIssuer_ct;
|
||||
#else
|
||||
typedef std::pair<uint160, uint160> currencyIssuer_ct; // C++ defect 106
|
||||
#endif
|
||||
|
||||
class BookListeners
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<BookListeners> pointer;
|
||||
|
||||
BookListeners ();
|
||||
void addSubscriber (InfoSub::ref sub);
|
||||
void removeSubscriber (uint64 sub);
|
||||
void publish (Json::Value& jvObj);
|
||||
|
||||
private:
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
boost::unordered_map<uint64, InfoSub::wptr> mListeners;
|
||||
};
|
||||
|
||||
class OrderBookDB : LeakChecked <OrderBookDB>
|
||||
{
|
||||
public:
|
||||
OrderBookDB ();
|
||||
void setup (Ledger::ref ledger);
|
||||
void invalidate ();
|
||||
|
||||
// return list of all orderbooks that want this issuerID and currencyID
|
||||
void getBooksByTakerPays (const uint160& issuerID, const uint160& currencyID,
|
||||
std::vector<OrderBook::pointer>& bookRet);
|
||||
void getBooksByTakerGets (const uint160& issuerID, const uint160& currencyID,
|
||||
std::vector<OrderBook::pointer>& bookRet);
|
||||
|
||||
bool isBookToXRP (const uint160& issuerID, const uint160& currencyID);
|
||||
|
||||
BookListeners::pointer getBookListeners (const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
|
||||
BookListeners::pointer makeBookListeners (const uint160& currencyPays, const uint160& currencyGets,
|
||||
const uint160& issuerPays, const uint160& issuerGets);
|
||||
|
||||
// see if this txn effects any orderbook
|
||||
void processTxn (Ledger::ref ledger, const AcceptedLedgerTx& alTx, Json::Value& jvObj);
|
||||
|
||||
private:
|
||||
boost::unordered_map< currencyIssuer_t, std::vector<OrderBook::pointer> > mSourceMap; // by ci/ii
|
||||
boost::unordered_map< currencyIssuer_t, std::vector<OrderBook::pointer> > mDestMap; // by co/io
|
||||
boost::unordered_set< currencyIssuer_t > mXRPBooks; // does an order book to XRP exist
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
|
||||
// issuerPays, issuerGets, currencyPays, currencyGets
|
||||
std::map<uint160, std::map<uint160, std::map<uint160, std::map<uint160, BookListeners::pointer> > > > mListeners;
|
||||
|
||||
uint32 mSeq;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
144
src/ripple_app/ledger/SerializedValidation.cpp
Normal file
144
src/ripple_app/ledger/SerializedValidation.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SerializedValidation::SerializedValidation (SerializerIterator& sit, bool checkSignature)
|
||||
: STObject (getFormat (), sit, sfValidation)
|
||||
, mTrusted (false)
|
||||
{
|
||||
mNodeID = RippleAddress::createNodePublic (getFieldVL (sfSigningPubKey)).getNodeID ();
|
||||
assert (mNodeID.isNonZero ());
|
||||
|
||||
if (checkSignature && !isValid ())
|
||||
{
|
||||
Log (lsTRACE) << "Invalid validation " << getJson (0);
|
||||
throw std::runtime_error ("Invalid validation");
|
||||
}
|
||||
}
|
||||
|
||||
SerializedValidation::SerializedValidation (
|
||||
uint256 const& ledgerHash, uint32 signTime,
|
||||
const RippleAddress& raPub, bool isFull)
|
||||
: STObject (getFormat (), sfValidation)
|
||||
, mTrusted (false)
|
||||
{
|
||||
// Does not sign
|
||||
setFieldH256 (sfLedgerHash, ledgerHash);
|
||||
setFieldU32 (sfSigningTime, signTime);
|
||||
|
||||
setFieldVL (sfSigningPubKey, raPub.getNodePublic ());
|
||||
mNodeID = raPub.getNodeID ();
|
||||
assert (mNodeID.isNonZero ());
|
||||
|
||||
if (!isFull)
|
||||
setFlag (kFullFlag);
|
||||
}
|
||||
|
||||
void SerializedValidation::sign (const RippleAddress& raPriv)
|
||||
{
|
||||
uint256 signingHash;
|
||||
sign (signingHash, raPriv);
|
||||
}
|
||||
|
||||
void SerializedValidation::sign (uint256& signingHash, const RippleAddress& raPriv)
|
||||
{
|
||||
signingHash = getSigningHash ();
|
||||
Blob signature;
|
||||
raPriv.signNodePrivate (signingHash, signature);
|
||||
setFieldVL (sfSignature, signature);
|
||||
}
|
||||
|
||||
uint256 SerializedValidation::getSigningHash () const
|
||||
{
|
||||
return STObject::getSigningHash (getConfig ().SIGN_VALIDATION);
|
||||
}
|
||||
|
||||
uint256 SerializedValidation::getLedgerHash () const
|
||||
{
|
||||
return getFieldH256 (sfLedgerHash);
|
||||
}
|
||||
|
||||
uint32 SerializedValidation::getSignTime () const
|
||||
{
|
||||
return getFieldU32 (sfSigningTime);
|
||||
}
|
||||
|
||||
uint32 SerializedValidation::getFlags () const
|
||||
{
|
||||
return getFieldU32 (sfFlags);
|
||||
}
|
||||
|
||||
bool SerializedValidation::isValid () const
|
||||
{
|
||||
return isValid (getSigningHash ());
|
||||
}
|
||||
|
||||
bool SerializedValidation::isValid (uint256 const& signingHash) const
|
||||
{
|
||||
try
|
||||
{
|
||||
RippleAddress raPublicKey = RippleAddress::createNodePublic (getFieldVL (sfSigningPubKey));
|
||||
return raPublicKey.isValid () && raPublicKey.verifyNodePublic (signingHash, getFieldVL (sfSignature));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Log (lsINFO) << "exception validating validation";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
RippleAddress SerializedValidation::getSignerPublic () const
|
||||
{
|
||||
RippleAddress a;
|
||||
a.setNodePublic (getFieldVL (sfSigningPubKey));
|
||||
return a;
|
||||
}
|
||||
|
||||
bool SerializedValidation::isFull () const
|
||||
{
|
||||
return (getFlags () & kFullFlag) != 0;
|
||||
}
|
||||
|
||||
Blob SerializedValidation::getSignature () const
|
||||
{
|
||||
return getFieldVL (sfSignature);
|
||||
}
|
||||
|
||||
Blob SerializedValidation::getSigned () const
|
||||
{
|
||||
Serializer s;
|
||||
add (s);
|
||||
return s.peekData ();
|
||||
}
|
||||
|
||||
SOTemplate const& SerializedValidation::getFormat ()
|
||||
{
|
||||
struct FormatHolder
|
||||
{
|
||||
SOTemplate format;
|
||||
|
||||
FormatHolder ()
|
||||
{
|
||||
format.push_back (SOElement (sfFlags, SOE_REQUIRED));
|
||||
format.push_back (SOElement (sfLedgerHash, SOE_REQUIRED));
|
||||
format.push_back (SOElement (sfLedgerSequence, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfCloseTime, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfLoadFee, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfFeatures, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfBaseFee, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfReserveBase, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfReserveIncrement, SOE_OPTIONAL));
|
||||
format.push_back (SOElement (sfSigningTime, SOE_REQUIRED));
|
||||
format.push_back (SOElement (sfSigningPubKey, SOE_REQUIRED));
|
||||
format.push_back (SOElement (sfSignature, SOE_OPTIONAL));
|
||||
}
|
||||
};
|
||||
|
||||
static FormatHolder holder;
|
||||
|
||||
return holder.format;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
82
src/ripple_app/ledger/SerializedValidation.h
Normal file
82
src/ripple_app/ledger/SerializedValidation.h
Normal file
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SERIALIZEDVALIDATION_H
|
||||
#define RIPPLE_SERIALIZEDVALIDATION_H
|
||||
|
||||
class SerializedValidation
|
||||
: public STObject
|
||||
, public CountedObject <SerializedValidation>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "SerializedValidation"; }
|
||||
|
||||
typedef boost::shared_ptr<SerializedValidation> pointer;
|
||||
typedef const boost::shared_ptr<SerializedValidation>& ref;
|
||||
|
||||
enum
|
||||
{
|
||||
kFullFlag = 0x1
|
||||
};
|
||||
|
||||
// These throw if the object is not valid
|
||||
SerializedValidation (SerializerIterator & sit, bool checkSignature = true);
|
||||
|
||||
// Does not sign the validation
|
||||
SerializedValidation (uint256 const & ledgerHash, uint32 signTime, const RippleAddress & raPub, bool isFull);
|
||||
|
||||
uint256 getLedgerHash () const;
|
||||
uint32 getSignTime () const;
|
||||
uint32 getFlags () const;
|
||||
RippleAddress getSignerPublic () const;
|
||||
uint160 getNodeID () const
|
||||
{
|
||||
return mNodeID;
|
||||
}
|
||||
bool isValid () const;
|
||||
bool isFull () const;
|
||||
bool isTrusted () const
|
||||
{
|
||||
return mTrusted;
|
||||
}
|
||||
uint256 getSigningHash () const;
|
||||
bool isValid (uint256 const& ) const;
|
||||
|
||||
void setTrusted ()
|
||||
{
|
||||
mTrusted = true;
|
||||
}
|
||||
Blob getSigned () const;
|
||||
Blob getSignature () const;
|
||||
void sign (uint256 & signingHash, const RippleAddress & raPrivate);
|
||||
void sign (const RippleAddress & raPrivate);
|
||||
|
||||
// The validation this replaced
|
||||
uint256 const& getPreviousHash ()
|
||||
{
|
||||
return mPreviousHash;
|
||||
}
|
||||
bool isPreviousHash (uint256 const & h) const
|
||||
{
|
||||
return mPreviousHash == h;
|
||||
}
|
||||
void setPreviousHash (uint256 const & h)
|
||||
{
|
||||
mPreviousHash = h;
|
||||
}
|
||||
|
||||
private:
|
||||
static SOTemplate const& getFormat ();
|
||||
|
||||
void setNode ();
|
||||
|
||||
uint256 mPreviousHash;
|
||||
uint160 mNodeID;
|
||||
bool mTrusted;
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
46
src/ripple_app/ledger/ripple_AcceptedLedger.cpp
Normal file
46
src/ripple_app/ledger/ripple_AcceptedLedger.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
TaggedCacheType <uint256, AcceptedLedger, UptimeTimerAdapter> AcceptedLedger::s_cache ("AcceptedLedger", 4, 60);
|
||||
|
||||
AcceptedLedger::AcceptedLedger (Ledger::ref ledger) : mLedger (ledger)
|
||||
{
|
||||
SHAMap& txSet = *ledger->peekTransactionMap ();
|
||||
|
||||
for (SHAMapItem::pointer item = txSet.peekFirstItem (); !!item; item = txSet.peekNextItem (item->getTag ()))
|
||||
{
|
||||
SerializerIterator sit (item->peekSerializer ());
|
||||
insert (boost::make_shared<AcceptedLedgerTx> (ledger->getLedgerSeq (), boost::ref (sit)));
|
||||
}
|
||||
}
|
||||
|
||||
AcceptedLedger::pointer AcceptedLedger::makeAcceptedLedger (Ledger::ref ledger)
|
||||
{
|
||||
AcceptedLedger::pointer ret = s_cache.fetch (ledger->getHash ());
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = AcceptedLedger::pointer (new AcceptedLedger (ledger));
|
||||
s_cache.canonicalize (ledger->getHash (), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AcceptedLedger::insert (AcceptedLedgerTx::ref at)
|
||||
{
|
||||
assert (mMap.find (at->getIndex ()) == mMap.end ());
|
||||
mMap.insert (std::make_pair (at->getIndex (), at));
|
||||
}
|
||||
|
||||
AcceptedLedgerTx::pointer AcceptedLedger::getTxn (int i) const
|
||||
{
|
||||
map_t::const_iterator it = mMap.find (i);
|
||||
|
||||
if (it == mMap.end ())
|
||||
return AcceptedLedgerTx::pointer ();
|
||||
|
||||
return it->second;
|
||||
}
|
||||
79
src/ripple_app/ledger/ripple_AcceptedLedger.h
Normal file
79
src/ripple_app/ledger/ripple_AcceptedLedger.h
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_ACCEPTEDLEDGER_H
|
||||
#define RIPPLE_ACCEPTEDLEDGER_H
|
||||
|
||||
/** A ledger that has become irrevocable.
|
||||
|
||||
An accepted ledger is a ledger that has a sufficient number of
|
||||
validations to convince the local server that it is irrevocable.
|
||||
|
||||
The existence of an accepted ledger implies all preceding ledgers
|
||||
are accepted.
|
||||
*/
|
||||
/* VFALCO TODO digest this terminology clarification:
|
||||
Closed and accepted refer to ledgers that have not passed the
|
||||
validation threshold yet. Once they pass the threshold, they are
|
||||
"Validated". Closed just means its close time has passed and no
|
||||
new transactions can get in. "Accepted" means we believe it to be
|
||||
the result of the a consensus process (though haven't validated
|
||||
it yet).
|
||||
*/
|
||||
class AcceptedLedger
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<AcceptedLedger> pointer;
|
||||
typedef const pointer& ret;
|
||||
typedef std::map<int, AcceptedLedgerTx::pointer> map_t; // Must be an ordered map!
|
||||
typedef map_t::value_type value_type;
|
||||
typedef map_t::const_iterator const_iterator;
|
||||
|
||||
public:
|
||||
static pointer makeAcceptedLedger (Ledger::ref ledger);
|
||||
static void sweep ()
|
||||
{
|
||||
s_cache.sweep ();
|
||||
}
|
||||
|
||||
Ledger::ref getLedger () const
|
||||
{
|
||||
return mLedger;
|
||||
}
|
||||
const map_t& getMap () const
|
||||
{
|
||||
return mMap;
|
||||
}
|
||||
|
||||
int getLedgerSeq () const
|
||||
{
|
||||
return mLedger->getLedgerSeq ();
|
||||
}
|
||||
int getTxnCount () const
|
||||
{
|
||||
return mMap.size ();
|
||||
}
|
||||
|
||||
static float getCacheHitRate ()
|
||||
{
|
||||
return s_cache.getHitRate ();
|
||||
}
|
||||
|
||||
AcceptedLedgerTx::pointer getTxn (int) const;
|
||||
|
||||
private:
|
||||
explicit AcceptedLedger (Ledger::ref ledger);
|
||||
|
||||
void insert (AcceptedLedgerTx::ref);
|
||||
|
||||
private:
|
||||
static TaggedCacheType <uint256, AcceptedLedger, UptimeTimerAdapter> s_cache;
|
||||
|
||||
Ledger::pointer mLedger;
|
||||
map_t mMap;
|
||||
};
|
||||
|
||||
#endif
|
||||
61
src/ripple_app/ledger/ripple_AcceptedLedgerTx.cpp
Normal file
61
src/ripple_app/ledger/ripple_AcceptedLedgerTx.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
AcceptedLedgerTx::AcceptedLedgerTx (uint32 seq, SerializerIterator& sit)
|
||||
{
|
||||
Serializer txnSer (sit.getVL ());
|
||||
SerializerIterator txnIt (txnSer);
|
||||
|
||||
mTxn = boost::make_shared<SerializedTransaction> (boost::ref (txnIt));
|
||||
mRawMeta = sit.getVL ();
|
||||
mMeta = boost::make_shared<TransactionMetaSet> (mTxn->getTransactionID (), seq, mRawMeta);
|
||||
mAffected = mMeta->getAffectedAccounts ();
|
||||
mResult = mMeta->getResultTER ();
|
||||
buildJson ();
|
||||
}
|
||||
|
||||
AcceptedLedgerTx::AcceptedLedgerTx (SerializedTransaction::ref txn, TransactionMetaSet::ref met) :
|
||||
mTxn (txn), mMeta (met), mAffected (met->getAffectedAccounts ())
|
||||
{
|
||||
mResult = mMeta->getResultTER ();
|
||||
buildJson ();
|
||||
}
|
||||
|
||||
AcceptedLedgerTx::AcceptedLedgerTx (SerializedTransaction::ref txn, TER result) :
|
||||
mTxn (txn), mResult (result), mAffected (txn->getMentionedAccounts ())
|
||||
{
|
||||
buildJson ();
|
||||
}
|
||||
|
||||
std::string AcceptedLedgerTx::getEscMeta () const
|
||||
{
|
||||
assert (!mRawMeta.empty ());
|
||||
return sqlEscape (mRawMeta);
|
||||
}
|
||||
|
||||
void AcceptedLedgerTx::buildJson ()
|
||||
{
|
||||
mJson = Json::objectValue;
|
||||
mJson["transaction"] = mTxn->getJson (0);
|
||||
|
||||
if (mMeta)
|
||||
{
|
||||
mJson["meta"] = mMeta->getJson (0);
|
||||
mJson["raw_meta"] = strHex (mRawMeta);
|
||||
}
|
||||
|
||||
mJson["result"] = transHuman (mResult);
|
||||
|
||||
if (!mAffected.empty ())
|
||||
{
|
||||
Json::Value& affected = (mJson["affected"] = Json::arrayValue);
|
||||
BOOST_FOREACH (const RippleAddress & ra, mAffected)
|
||||
{
|
||||
affected.append (ra.humanAccountID ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
97
src/ripple_app/ledger/ripple_AcceptedLedgerTx.h
Normal file
97
src/ripple_app/ledger/ripple_AcceptedLedgerTx.h
Normal file
@@ -0,0 +1,97 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_ACCEPTEDLEDGERTX_H
|
||||
#define RIPPLE_ACCEPTEDLEDGERTX_H
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
A transaction that is in a closed ledger.
|
||||
|
||||
Description
|
||||
|
||||
An accepted ledger transaction contains additional information that the
|
||||
server needs to tell clients about the transaction. For example,
|
||||
- The transaction in JSON form
|
||||
- Which accounts are affected
|
||||
* This is used by InfoSub to report to clients
|
||||
- Cached stuff
|
||||
|
||||
@code
|
||||
@endcode
|
||||
|
||||
@see {uri}
|
||||
|
||||
@ingroup ripple_ledger
|
||||
*/
|
||||
class AcceptedLedgerTx
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr <AcceptedLedgerTx> pointer;
|
||||
typedef const pointer& ref;
|
||||
|
||||
public:
|
||||
AcceptedLedgerTx (LedgerIndex ledgerSeq, SerializerIterator& sit);
|
||||
AcceptedLedgerTx (SerializedTransaction::ref, TransactionMetaSet::ref);
|
||||
AcceptedLedgerTx (SerializedTransaction::ref, TER result);
|
||||
|
||||
SerializedTransaction::ref getTxn () const
|
||||
{
|
||||
return mTxn;
|
||||
}
|
||||
TransactionMetaSet::ref getMeta () const
|
||||
{
|
||||
return mMeta;
|
||||
}
|
||||
std::vector <RippleAddress> const& getAffected () const
|
||||
{
|
||||
return mAffected;
|
||||
}
|
||||
|
||||
TxID getTransactionID () const
|
||||
{
|
||||
return mTxn->getTransactionID ();
|
||||
}
|
||||
TxType getTxnType () const
|
||||
{
|
||||
return mTxn->getTxnType ();
|
||||
}
|
||||
TER getResult () const
|
||||
{
|
||||
return mResult;
|
||||
}
|
||||
uint32 getTxnSeq () const
|
||||
{
|
||||
return mMeta->getIndex ();
|
||||
}
|
||||
|
||||
bool isApplied () const
|
||||
{
|
||||
return !!mMeta;
|
||||
}
|
||||
int getIndex () const
|
||||
{
|
||||
return mMeta ? mMeta->getIndex () : 0;
|
||||
}
|
||||
std::string getEscMeta () const;
|
||||
Json::Value getJson () const
|
||||
{
|
||||
return mJson;
|
||||
}
|
||||
|
||||
private:
|
||||
SerializedTransaction::pointer mTxn;
|
||||
TransactionMetaSet::pointer mMeta;
|
||||
TER mResult;
|
||||
std::vector <RippleAddress> mAffected;
|
||||
Blob mRawMeta;
|
||||
Json::Value mJson;
|
||||
|
||||
void buildJson ();
|
||||
};
|
||||
|
||||
#endif
|
||||
855
src/ripple_app/ledger/ripple_InboundLedger.cpp
Normal file
855
src/ripple_app/ledger/ripple_InboundLedger.cpp
Normal file
@@ -0,0 +1,855 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (InboundLedger)
|
||||
|
||||
// VFALCO TODO replace macros
|
||||
#define LA_DEBUG
|
||||
#define LEDGER_ACQUIRE_TIMEOUT 6000 // millisecond for each ledger timeout
|
||||
#define LEDGER_TIMEOUT_COUNT 10 // how many timeouts before we giveup
|
||||
#define LEDGER_TIMEOUT_AGGRESSIVE 6 // how many timeouts before we get aggressive
|
||||
|
||||
InboundLedger::InboundLedger (uint256 const& hash, uint32 seq)
|
||||
: PeerSet (hash, LEDGER_ACQUIRE_TIMEOUT, false)
|
||||
, mHaveBase (false)
|
||||
, mHaveState (false)
|
||||
, mHaveTransactions (false)
|
||||
, mAborted (false)
|
||||
, mSignaled (false)
|
||||
, mByHash (true)
|
||||
, mWaitCount (0)
|
||||
, mSeq (seq)
|
||||
{
|
||||
#ifdef LA_DEBUG
|
||||
WriteLog (lsTRACE, InboundLedger) << "Acquiring ledger " << mHash;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool InboundLedger::checkLocal ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!isDone () && tryLocal())
|
||||
{
|
||||
done();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
InboundLedger::~InboundLedger ()
|
||||
{
|
||||
}
|
||||
|
||||
bool InboundLedger::tryLocal ()
|
||||
{
|
||||
// return value: true = no more work to do
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
// Nothing we can do without the ledger base
|
||||
NodeObject::pointer node = getApp().getNodeStore ().fetch (mHash);
|
||||
|
||||
if (!node)
|
||||
{
|
||||
Blob data;
|
||||
|
||||
if (!getApp().getOPs ().getFetchPack (mHash, data))
|
||||
return false;
|
||||
|
||||
WriteLog (lsTRACE, InboundLedger) << "Ledger base found in fetch pack";
|
||||
mLedger = boost::make_shared<Ledger> (data, true);
|
||||
getApp().getNodeStore ().store (hotLEDGER, mLedger->getLedgerSeq (), data, mHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
mLedger = boost::make_shared<Ledger> (strCopy (node->getData ()), true);
|
||||
}
|
||||
|
||||
if (mLedger->getHash () != mHash)
|
||||
{
|
||||
// We know for a fact the ledger can never be acquired
|
||||
WriteLog (lsWARNING, InboundLedger) << mHash << " cannot be a ledger";
|
||||
mFailed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
mHaveBase = true;
|
||||
}
|
||||
|
||||
if (!mHaveTransactions)
|
||||
{
|
||||
if (mLedger->getTransHash ().isZero ())
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "No TXNs to fetch";
|
||||
mHaveTransactions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
TransactionStateSF filter (mLedger->getLedgerSeq ());
|
||||
|
||||
if (mLedger->peekTransactionMap ()->fetchRoot (mLedger->getTransHash (), &filter))
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Got root txn map locally";
|
||||
std::vector<uint256> h = mLedger->getNeededTransactionHashes (1, &filter);
|
||||
|
||||
if (h.empty ())
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Had full txn map locally";
|
||||
mHaveTransactions = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mHaveState)
|
||||
{
|
||||
if (mLedger->getAccountHash ().isZero ())
|
||||
{
|
||||
WriteLog (lsFATAL, InboundLedger) << "We are acquiring a ledger with a zero account hash";
|
||||
mHaveState = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AccountStateSF filter (mLedger->getLedgerSeq ());
|
||||
|
||||
if (mLedger->peekAccountStateMap ()->fetchRoot (mLedger->getAccountHash (), &filter))
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Got root AS map locally";
|
||||
std::vector<uint256> h = mLedger->getNeededAccountStateHashes (1, &filter);
|
||||
|
||||
if (h.empty ())
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Had full AS map locally";
|
||||
mHaveState = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mHaveTransactions && mHaveState)
|
||||
{
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Had everything locally";
|
||||
mComplete = true;
|
||||
mLedger->setClosed ();
|
||||
mLedger->setImmutable ();
|
||||
}
|
||||
|
||||
return mComplete;
|
||||
}
|
||||
|
||||
void InboundLedger::onTimer (bool wasProgress, ScopedLockType&)
|
||||
{
|
||||
mRecentTXNodes.clear ();
|
||||
mRecentASNodes.clear ();
|
||||
|
||||
if (getTimeouts () > LEDGER_TIMEOUT_COUNT)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Too many timeouts( " << getTimeouts () << ") for ledger " << mHash;
|
||||
setFailed ();
|
||||
done ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wasProgress)
|
||||
{
|
||||
if (isDone())
|
||||
{
|
||||
WriteLog (lsINFO, InboundLedger) << "Already done " << mHash;
|
||||
return;
|
||||
}
|
||||
checkLocal();
|
||||
if (isDone())
|
||||
{
|
||||
WriteLog (lsINFO, InboundLedger) << "Completed fetch " << mHash;
|
||||
return;
|
||||
}
|
||||
|
||||
mAggressive = true;
|
||||
mByHash = true;
|
||||
int pc = getPeerCount ();
|
||||
WriteLog (lsDEBUG, InboundLedger) << "No progress(" << pc << ") for ledger " << mHash;
|
||||
|
||||
trigger (Peer::pointer ());
|
||||
if (pc < 4)
|
||||
addPeers ();
|
||||
}
|
||||
}
|
||||
|
||||
void InboundLedger::awaitData ()
|
||||
{
|
||||
++mWaitCount;
|
||||
}
|
||||
|
||||
void InboundLedger::noAwaitData ()
|
||||
{ // subtract one if mWaitCount is greater than zero
|
||||
do
|
||||
{
|
||||
int j = mWaitCount.get();
|
||||
if (j <= 0)
|
||||
return;
|
||||
if (mWaitCount.compareAndSetBool(j - 1, j))
|
||||
return;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
void InboundLedger::addPeers ()
|
||||
{
|
||||
std::vector<Peer::pointer> peerList = getApp().getPeers ().getPeerVector ();
|
||||
|
||||
int vSize = peerList.size ();
|
||||
|
||||
if (vSize == 0)
|
||||
return;
|
||||
|
||||
// We traverse the peer list in random order so as not to favor any particular peer
|
||||
int firstPeer = rand () % vSize;
|
||||
|
||||
int found = 0;
|
||||
|
||||
for (int i = 0; i < vSize; ++i)
|
||||
{
|
||||
Peer::ref peer = peerList[ (i + firstPeer) % vSize];
|
||||
|
||||
if (peer->hasLedger (getHash (), mSeq))
|
||||
{
|
||||
if (peerHas (peer) && (++found > 6))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
for (int i = 0; (i < 6) && (i < vSize); ++i)
|
||||
peerHas (peerList[ (i + firstPeer) % vSize]);
|
||||
}
|
||||
|
||||
boost::weak_ptr<PeerSet> InboundLedger::pmDowncast ()
|
||||
{
|
||||
return boost::dynamic_pointer_cast<PeerSet> (shared_from_this ());
|
||||
}
|
||||
|
||||
static void LADispatch (
|
||||
Job& job,
|
||||
InboundLedger::pointer la,
|
||||
std::vector< FUNCTION_TYPE<void (InboundLedger::pointer)> > trig)
|
||||
{
|
||||
if (la->isComplete() && !la->isFailed())
|
||||
getApp().getLedgerMaster().checkAccept(la->getLedger());
|
||||
getApp().getLedgerMaster().tryAdvance();
|
||||
for (unsigned int i = 0; i < trig.size (); ++i)
|
||||
trig[i] (la);
|
||||
}
|
||||
|
||||
void InboundLedger::done ()
|
||||
{
|
||||
if (mSignaled)
|
||||
return;
|
||||
|
||||
mSignaled = true;
|
||||
touch ();
|
||||
|
||||
WriteLog (lsTRACE, InboundLedger) << "Done acquiring ledger " << mHash;
|
||||
|
||||
assert (isComplete () || isFailed ());
|
||||
|
||||
std::vector< FUNCTION_TYPE<void (InboundLedger::pointer)> > triggers;
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
triggers.swap (mOnComplete);
|
||||
}
|
||||
|
||||
if (isComplete () && !isFailed () && mLedger)
|
||||
{
|
||||
mLedger->setClosed ();
|
||||
mLedger->setImmutable ();
|
||||
getApp().getLedgerMaster ().storeLedger (mLedger);
|
||||
}
|
||||
else
|
||||
getApp().getInboundLedgers ().logFailure (mHash);
|
||||
|
||||
// We hold the PeerSet lock, so must dispatch
|
||||
getApp().getJobQueue ().addJob (jtLEDGER_DATA, "triggers",
|
||||
BIND_TYPE (LADispatch, P_1, shared_from_this (), triggers));
|
||||
}
|
||||
|
||||
bool InboundLedger::addOnComplete (FUNCTION_TYPE<void (InboundLedger::pointer)> trigger)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (isDone ())
|
||||
return false;
|
||||
|
||||
mOnComplete.push_back (trigger);
|
||||
return true;
|
||||
}
|
||||
|
||||
void InboundLedger::trigger (Peer::ref peer)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (isDone ())
|
||||
{
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Trigger on ledger: " << mHash <<
|
||||
(mAborted ? " aborted" : "") << (mComplete ? " completed" : "") << (mFailed ? " failed" : "");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((mWaitCount.get() > 0) && peer)
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Skipping peer";
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldLog (lsTRACE, InboundLedger))
|
||||
{
|
||||
if (peer)
|
||||
WriteLog (lsTRACE, InboundLedger) << "Trigger acquiring ledger " << mHash << " from " << peer->getIP ();
|
||||
else
|
||||
WriteLog (lsTRACE, InboundLedger) << "Trigger acquiring ledger " << mHash;
|
||||
|
||||
if (mComplete || mFailed)
|
||||
WriteLog (lsTRACE, InboundLedger) << "complete=" << mComplete << " failed=" << mFailed;
|
||||
else
|
||||
WriteLog (lsTRACE, InboundLedger) << "base=" << mHaveBase << " tx=" << mHaveTransactions << " as=" << mHaveState;
|
||||
}
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
tryLocal ();
|
||||
|
||||
if (mFailed)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << " failed local for " << mHash;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protocol::TMGetLedger tmGL;
|
||||
tmGL.set_ledgerhash (mHash.begin (), mHash.size ());
|
||||
|
||||
if (getTimeouts () != 0)
|
||||
{
|
||||
tmGL.set_querytype (protocol::qtINDIRECT);
|
||||
|
||||
if (!isProgress () && !mFailed && mByHash && (getTimeouts () > LEDGER_TIMEOUT_AGGRESSIVE))
|
||||
{
|
||||
std::vector<neededHash_t> need = getNeededHashes ();
|
||||
|
||||
if (!need.empty ())
|
||||
{
|
||||
protocol::TMGetObjectByHash tmBH;
|
||||
tmBH.set_query (true);
|
||||
tmBH.set_ledgerhash (mHash.begin (), mHash.size ());
|
||||
bool typeSet = false;
|
||||
BOOST_FOREACH (neededHash_t & p, need)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Want: " << p.second;
|
||||
|
||||
if (!typeSet)
|
||||
{
|
||||
tmBH.set_type (p.first);
|
||||
typeSet = true;
|
||||
}
|
||||
|
||||
if (p.first == tmBH.type ())
|
||||
{
|
||||
protocol::TMIndexedObject* io = tmBH.add_objects ();
|
||||
io->set_hash (p.second.begin (), p.second.size ());
|
||||
}
|
||||
}
|
||||
PackedMessage::pointer packet = boost::make_shared<PackedMessage> (tmBH, protocol::mtGET_OBJECTS);
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
for (boost::unordered_map<uint64, int>::iterator it = mPeers.begin (), end = mPeers.end ();
|
||||
it != end; ++it)
|
||||
{
|
||||
Peer::pointer iPeer = getApp().getPeers ().getPeerById (it->first);
|
||||
|
||||
if (iPeer)
|
||||
{
|
||||
mByHash = false;
|
||||
iPeer->sendPacket (packet, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
WriteLog (lsINFO, InboundLedger) << "Attempting by hash fetch for ledger " << mHash;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsINFO, InboundLedger) << "getNeededHashes says acquire is complete";
|
||||
mHaveBase = true;
|
||||
mHaveTransactions = true;
|
||||
mHaveState = true;
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mHaveBase && !mFailed)
|
||||
{
|
||||
tmGL.set_itype (protocol::liBASE);
|
||||
WriteLog (lsTRACE, InboundLedger) << "Sending base request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest (tmGL, peer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLedger)
|
||||
tmGL.set_ledgerseq (mLedger->getLedgerSeq ());
|
||||
|
||||
if (mHaveBase && !mHaveTransactions && !mFailed)
|
||||
{
|
||||
assert (mLedger);
|
||||
|
||||
if (mLedger->peekTransactionMap ()->getHash ().isZero ())
|
||||
{
|
||||
// we need the root node
|
||||
tmGL.set_itype (protocol::liTX_NODE);
|
||||
* (tmGL.add_nodeids ()) = SHAMapNode ().getRawString ();
|
||||
WriteLog (lsTRACE, InboundLedger) << "Sending TX root request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest (tmGL, peer);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<SHAMapNode> nodeIDs;
|
||||
std::vector<uint256> nodeHashes;
|
||||
nodeIDs.reserve (256);
|
||||
nodeHashes.reserve (256);
|
||||
TransactionStateSF filter (mSeq);
|
||||
mLedger->peekTransactionMap ()->getMissingNodes (nodeIDs, nodeHashes, 256, &filter);
|
||||
|
||||
if (nodeIDs.empty ())
|
||||
{
|
||||
if (!mLedger->peekTransactionMap ()->isValid ())
|
||||
mFailed = true;
|
||||
else
|
||||
{
|
||||
mHaveTransactions = true;
|
||||
|
||||
if (mHaveState)
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mAggressive)
|
||||
filterNodes (nodeIDs, nodeHashes, mRecentTXNodes, 128, !isProgress ());
|
||||
|
||||
if (!nodeIDs.empty ())
|
||||
{
|
||||
tmGL.set_itype (protocol::liTX_NODE);
|
||||
BOOST_FOREACH (SHAMapNode const& it, nodeIDs)
|
||||
{
|
||||
* (tmGL.add_nodeids ()) = it.getRawString ();
|
||||
}
|
||||
WriteLog (lsTRACE, InboundLedger) << "Sending TX node " << nodeIDs.size ()
|
||||
<< " request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest (tmGL, peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mHaveBase && !mHaveState && !mFailed)
|
||||
{
|
||||
assert (mLedger);
|
||||
|
||||
if (mLedger->peekAccountStateMap ()->getHash ().isZero ())
|
||||
{
|
||||
// we need the root node
|
||||
tmGL.set_itype (protocol::liAS_NODE);
|
||||
* (tmGL.add_nodeids ()) = SHAMapNode ().getRawString ();
|
||||
WriteLog (lsTRACE, InboundLedger) << "Sending AS root request to " << (peer ? "selected peer" : "all peers");
|
||||
sendRequest (tmGL, peer);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<SHAMapNode> nodeIDs;
|
||||
std::vector<uint256> nodeHashes;
|
||||
nodeIDs.reserve (256);
|
||||
nodeHashes.reserve (256);
|
||||
AccountStateSF filter (mSeq);
|
||||
mLedger->peekAccountStateMap ()->getMissingNodes (nodeIDs, nodeHashes, 256, &filter);
|
||||
|
||||
if (nodeIDs.empty ())
|
||||
{
|
||||
if (!mLedger->peekAccountStateMap ()->isValid ())
|
||||
mFailed = true;
|
||||
else
|
||||
{
|
||||
mHaveState = true;
|
||||
|
||||
if (mHaveTransactions)
|
||||
mComplete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mAggressive)
|
||||
filterNodes (nodeIDs, nodeHashes, mRecentASNodes, 128, !isProgress ());
|
||||
|
||||
if (!nodeIDs.empty ())
|
||||
{
|
||||
tmGL.set_itype (protocol::liAS_NODE);
|
||||
BOOST_FOREACH (SHAMapNode const& it, nodeIDs)
|
||||
{
|
||||
* (tmGL.add_nodeids ()) = it.getRawString ();
|
||||
}
|
||||
WriteLog (lsTRACE, InboundLedger) << "Sending AS node " << nodeIDs.size ()
|
||||
<< " request to " << (peer ? "selected peer" : "all peers");
|
||||
CondLog (nodeIDs.size () == 1, lsTRACE, InboundLedger) << "AS node: " << nodeIDs[0];
|
||||
sendRequest (tmGL, peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mComplete || mFailed)
|
||||
{
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Done:" << (mComplete ? " complete" : "") << (mFailed ? " failed " : " ")
|
||||
<< mLedger->getLedgerSeq ();
|
||||
sl.unlock ();
|
||||
done ();
|
||||
}
|
||||
}
|
||||
|
||||
void InboundLedger::filterNodes (std::vector<SHAMapNode>& nodeIDs, std::vector<uint256>& nodeHashes,
|
||||
std::set<SHAMapNode>& recentNodes, int max, bool aggressive)
|
||||
{
|
||||
// ask for new nodes in preference to ones we've already asked for
|
||||
assert (nodeIDs.size () == nodeHashes.size ());
|
||||
|
||||
std::vector<bool> duplicates;
|
||||
duplicates.reserve (nodeIDs.size ());
|
||||
|
||||
int dupCount = 0;
|
||||
|
||||
BOOST_FOREACH(SHAMapNode const& nodeID, nodeIDs)
|
||||
{
|
||||
if (recentNodes.count (nodeID) != 0)
|
||||
{
|
||||
duplicates.push_back (true);
|
||||
++dupCount;
|
||||
}
|
||||
else
|
||||
duplicates.push_back (false);
|
||||
}
|
||||
|
||||
if (dupCount == nodeIDs.size ())
|
||||
{
|
||||
// all duplicates
|
||||
if (!aggressive)
|
||||
{
|
||||
nodeIDs.clear ();
|
||||
nodeHashes.clear ();
|
||||
WriteLog (lsTRACE, InboundLedger) << "filterNodes: all are duplicates";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (dupCount > 0)
|
||||
{
|
||||
// some, but not all, duplicates
|
||||
int insertPoint = 0;
|
||||
|
||||
for (unsigned int i = 0; i < nodeIDs.size (); ++i)
|
||||
if (!duplicates[i])
|
||||
{
|
||||
// Keep this node
|
||||
if (insertPoint != i)
|
||||
{
|
||||
nodeIDs[insertPoint] = nodeIDs[i];
|
||||
nodeHashes[insertPoint] = nodeHashes[i];
|
||||
}
|
||||
|
||||
++insertPoint;
|
||||
}
|
||||
|
||||
WriteLog (lsTRACE, InboundLedger) << "filterNodes " << nodeIDs.size () << " to " << insertPoint;
|
||||
nodeIDs.resize (insertPoint);
|
||||
nodeHashes.resize (insertPoint);
|
||||
}
|
||||
|
||||
if (nodeIDs.size () > max)
|
||||
{
|
||||
nodeIDs.resize (max);
|
||||
nodeHashes.resize (max);
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const SHAMapNode & n, nodeIDs)
|
||||
{
|
||||
recentNodes.insert (n);
|
||||
}
|
||||
}
|
||||
|
||||
bool InboundLedger::takeBase (const std::string& data) // data must not have hash prefix
|
||||
{
|
||||
// Return value: true=normal, false=bad data
|
||||
#ifdef LA_DEBUG
|
||||
WriteLog (lsTRACE, InboundLedger) << "got base acquiring ledger " << mHash;
|
||||
#endif
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mComplete || mFailed || mHaveBase)
|
||||
return true;
|
||||
|
||||
mLedger = boost::make_shared<Ledger> (data, false);
|
||||
|
||||
if (mLedger->getHash () != mHash)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Acquire hash mismatch";
|
||||
WriteLog (lsWARNING, InboundLedger) << mLedger->getHash () << "!=" << mHash;
|
||||
mLedger.reset ();
|
||||
#ifdef TRUST_NETWORK
|
||||
assert (false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
mHaveBase = true;
|
||||
|
||||
Serializer s (data.size () + 4);
|
||||
s.add32 (HashPrefix::ledgerMaster);
|
||||
s.addRaw (data);
|
||||
getApp().getNodeStore ().store (hotLEDGER, mLedger->getLedgerSeq (), s.modData (), mHash);
|
||||
|
||||
progress ();
|
||||
|
||||
if (!mLedger->getTransHash ())
|
||||
mHaveTransactions = true;
|
||||
|
||||
if (!mLedger->getAccountHash ())
|
||||
mHaveState = true;
|
||||
|
||||
mLedger->setAcquiring ();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InboundLedger::takeTxNode (const std::list<SHAMapNode>& nodeIDs,
|
||||
const std::list< Blob >& data, SHAMapAddNode& san)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mHaveBase)
|
||||
return false;
|
||||
|
||||
if (mHaveTransactions || mFailed)
|
||||
return true;
|
||||
|
||||
std::list<SHAMapNode>::const_iterator nodeIDit = nodeIDs.begin ();
|
||||
std::list< Blob >::const_iterator nodeDatait = data.begin ();
|
||||
TransactionStateSF tFilter (mLedger->getLedgerSeq ());
|
||||
|
||||
while (nodeIDit != nodeIDs.end ())
|
||||
{
|
||||
if (nodeIDit->isRoot ())
|
||||
{
|
||||
if (!san.combine (mLedger->peekTransactionMap ()->addRootNode (mLedger->getTransHash (), *nodeDatait,
|
||||
snfWIRE, &tFilter)))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!san.combine (mLedger->peekTransactionMap ()->addKnownNode (*nodeIDit, *nodeDatait, &tFilter)))
|
||||
return false;
|
||||
}
|
||||
|
||||
++nodeIDit;
|
||||
++nodeDatait;
|
||||
}
|
||||
|
||||
if (!mLedger->peekTransactionMap ()->isSynching ())
|
||||
{
|
||||
mHaveTransactions = true;
|
||||
|
||||
if (mHaveState)
|
||||
{
|
||||
mComplete = true;
|
||||
done ();
|
||||
}
|
||||
}
|
||||
|
||||
progress ();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InboundLedger::takeAsNode (const std::list<SHAMapNode>& nodeIDs,
|
||||
const std::list< Blob >& data, SHAMapAddNode& san)
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "got ASdata (" << nodeIDs.size () << ") acquiring ledger " << mHash;
|
||||
CondLog (nodeIDs.size () == 1, lsTRACE, InboundLedger) << "got AS node: " << nodeIDs.front ();
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Don't have ledger base";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mHaveState || mFailed)
|
||||
return true;
|
||||
|
||||
std::list<SHAMapNode>::const_iterator nodeIDit = nodeIDs.begin ();
|
||||
std::list< Blob >::const_iterator nodeDatait = data.begin ();
|
||||
AccountStateSF tFilter (mLedger->getLedgerSeq ());
|
||||
|
||||
while (nodeIDit != nodeIDs.end ())
|
||||
{
|
||||
if (nodeIDit->isRoot ())
|
||||
{
|
||||
if (!san.combine (mLedger->peekAccountStateMap ()->addRootNode (mLedger->getAccountHash (),
|
||||
*nodeDatait, snfWIRE, &tFilter)))
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Bad ledger base";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!san.combine (mLedger->peekAccountStateMap ()->addKnownNode (*nodeIDit, *nodeDatait, &tFilter)))
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Unable to add AS node";
|
||||
return false;
|
||||
}
|
||||
|
||||
++nodeIDit;
|
||||
++nodeDatait;
|
||||
}
|
||||
|
||||
if (!mLedger->peekAccountStateMap ()->isSynching ())
|
||||
{
|
||||
mHaveState = true;
|
||||
|
||||
if (mHaveTransactions)
|
||||
{
|
||||
mComplete = true;
|
||||
done ();
|
||||
}
|
||||
}
|
||||
|
||||
progress ();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InboundLedger::takeAsRootNode (Blob const& data, SHAMapAddNode& san)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mFailed || mHaveState)
|
||||
return true;
|
||||
|
||||
if (!mHaveBase)
|
||||
return false;
|
||||
|
||||
AccountStateSF tFilter (mLedger->getLedgerSeq ());
|
||||
return san.combine (
|
||||
mLedger->peekAccountStateMap ()->addRootNode (mLedger->getAccountHash (), data, snfWIRE, &tFilter));
|
||||
}
|
||||
|
||||
bool InboundLedger::takeTxRootNode (Blob const& data, SHAMapAddNode& san)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mFailed || mHaveState)
|
||||
return true;
|
||||
|
||||
if (!mHaveBase)
|
||||
return false;
|
||||
|
||||
TransactionStateSF tFilter (mLedger->getLedgerSeq ());
|
||||
return san.combine (
|
||||
mLedger->peekTransactionMap ()->addRootNode (mLedger->getTransHash (), data, snfWIRE, &tFilter));
|
||||
}
|
||||
|
||||
std::vector<InboundLedger::neededHash_t> InboundLedger::getNeededHashes ()
|
||||
{
|
||||
std::vector<neededHash_t> ret;
|
||||
|
||||
if (!mHaveBase)
|
||||
{
|
||||
ret.push_back (std::make_pair (protocol::TMGetObjectByHash::otLEDGER, mHash));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!mHaveState)
|
||||
{
|
||||
AccountStateSF filter (mLedger->getLedgerSeq ());
|
||||
std::vector<uint256> v = mLedger->getNeededAccountStateHashes (4, &filter);
|
||||
BOOST_FOREACH (uint256 const & h, v)
|
||||
{
|
||||
ret.push_back (std::make_pair (protocol::TMGetObjectByHash::otSTATE_NODE, h));
|
||||
}
|
||||
}
|
||||
|
||||
if (!mHaveTransactions)
|
||||
{
|
||||
TransactionStateSF filter (mLedger->getLedgerSeq ());
|
||||
std::vector<uint256> v = mLedger->getNeededTransactionHashes (4, &filter);
|
||||
BOOST_FOREACH (uint256 const & h, v)
|
||||
{
|
||||
ret.push_back (std::make_pair (protocol::TMGetObjectByHash::otTRANSACTION_NODE, h));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value InboundLedger::getJson (int)
|
||||
{
|
||||
Json::Value ret (Json::objectValue);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
ret["hash"] = mHash.GetHex ();
|
||||
|
||||
if (mComplete)
|
||||
ret["complete"] = true;
|
||||
|
||||
if (mFailed)
|
||||
ret["failed"] = true;
|
||||
|
||||
if (!mComplete && !mFailed)
|
||||
ret["peers"] = static_cast<int>(mPeers.size());
|
||||
|
||||
ret["have_base"] = mHaveBase;
|
||||
|
||||
if (mHaveBase)
|
||||
{
|
||||
ret["have_state"] = mHaveState;
|
||||
ret["have_transactions"] = mHaveTransactions;
|
||||
}
|
||||
|
||||
if (mAborted)
|
||||
ret["aborted"] = true;
|
||||
|
||||
ret["timeouts"] = getTimeouts ();
|
||||
|
||||
if (mHaveBase && !mHaveState)
|
||||
{
|
||||
Json::Value hv (Json::arrayValue);
|
||||
std::vector<uint256> v = mLedger->getNeededAccountStateHashes (16, NULL);
|
||||
BOOST_FOREACH (uint256 const & h, v)
|
||||
{
|
||||
hv.append (h.GetHex ());
|
||||
}
|
||||
ret["needed_state_hashes"] = hv;
|
||||
}
|
||||
|
||||
if (mHaveBase && !mHaveTransactions)
|
||||
{
|
||||
Json::Value hv (Json::arrayValue);
|
||||
std::vector<uint256> v = mLedger->getNeededTransactionHashes (16, NULL);
|
||||
BOOST_FOREACH (uint256 const & h, v)
|
||||
{
|
||||
hv.append (h.GetHex ());
|
||||
}
|
||||
ret["needed_transaction_hashes"] = hv;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
113
src/ripple_app/ledger/ripple_InboundLedger.h
Normal file
113
src/ripple_app/ledger/ripple_InboundLedger.h
Normal file
@@ -0,0 +1,113 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_INBOUNDLEDGER_H
|
||||
#define RIPPLE_INBOUNDLEDGER_H
|
||||
|
||||
// VFALCO TODO Rename to InboundLedger
|
||||
// A ledger we are trying to acquire
|
||||
class InboundLedger
|
||||
: public PeerSet
|
||||
, public boost::enable_shared_from_this <InboundLedger>
|
||||
, public CountedObject <InboundLedger>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "InboundLedger"; }
|
||||
|
||||
typedef boost::shared_ptr <InboundLedger> pointer;
|
||||
|
||||
public:
|
||||
InboundLedger (uint256 const& hash, uint32 seq);
|
||||
|
||||
virtual ~InboundLedger ();
|
||||
|
||||
bool isBase () const
|
||||
{
|
||||
return mHaveBase;
|
||||
}
|
||||
bool isAcctStComplete () const
|
||||
{
|
||||
return mHaveState;
|
||||
}
|
||||
bool isTransComplete () const
|
||||
{
|
||||
return mHaveTransactions;
|
||||
}
|
||||
bool isDone () const
|
||||
{
|
||||
return mAborted || isComplete () || isFailed ();
|
||||
}
|
||||
Ledger::ref getLedger ()
|
||||
{
|
||||
return mLedger;
|
||||
}
|
||||
void abort ()
|
||||
{
|
||||
mAborted = true;
|
||||
}
|
||||
uint32 getSeq ()
|
||||
{
|
||||
return mSeq;
|
||||
}
|
||||
|
||||
// VFALCO TODO Make this the Listener / Observer pattern
|
||||
bool addOnComplete (FUNCTION_TYPE<void (InboundLedger::pointer)>);
|
||||
|
||||
bool takeBase (const std::string& data);
|
||||
bool takeTxNode (const std::list<SHAMapNode>& IDs, const std::list<Blob >& data,
|
||||
SHAMapAddNode&);
|
||||
bool takeTxRootNode (Blob const& data, SHAMapAddNode&);
|
||||
bool takeAsNode (const std::list<SHAMapNode>& IDs, const std::list<Blob >& data,
|
||||
SHAMapAddNode&);
|
||||
bool takeAsRootNode (Blob const& data, SHAMapAddNode&);
|
||||
void trigger (Peer::ref);
|
||||
bool tryLocal ();
|
||||
void addPeers ();
|
||||
void awaitData ();
|
||||
void noAwaitData ();
|
||||
bool checkLocal ();
|
||||
|
||||
typedef std::pair <protocol::TMGetObjectByHash::ObjectType, uint256> neededHash_t;
|
||||
|
||||
std::vector<neededHash_t> getNeededHashes ();
|
||||
|
||||
static void filterNodes (std::vector<SHAMapNode>& nodeIDs, std::vector<uint256>& nodeHashes,
|
||||
std::set<SHAMapNode>& recentNodes, int max, bool aggressive);
|
||||
|
||||
Json::Value getJson (int);
|
||||
|
||||
private:
|
||||
void done ();
|
||||
|
||||
void onTimer (bool progress, ScopedLockType& peerSetLock);
|
||||
|
||||
void newPeer (Peer::ref peer)
|
||||
{
|
||||
trigger (peer);
|
||||
}
|
||||
|
||||
boost::weak_ptr <PeerSet> pmDowncast ();
|
||||
|
||||
private:
|
||||
Ledger::pointer mLedger;
|
||||
bool mHaveBase;
|
||||
bool mHaveState;
|
||||
bool mHaveTransactions;
|
||||
bool mAborted;
|
||||
bool mSignaled;
|
||||
bool mByHash;
|
||||
beast::Atomic<int> mWaitCount;
|
||||
uint32 mSeq;
|
||||
|
||||
std::set <SHAMapNode> mRecentTXNodes;
|
||||
std::set <SHAMapNode> mRecentASNodes;
|
||||
|
||||
std::vector <FUNCTION_TYPE <void (InboundLedger::pointer)> > mOnComplete;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
351
src/ripple_app/ledger/ripple_InboundLedgers.cpp
Normal file
351
src/ripple_app/ledger/ripple_InboundLedgers.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
typedef std::pair<uint256, InboundLedger::pointer> u256_acq_pair;
|
||||
|
||||
InboundLedgers::InboundLedgers ()
|
||||
: mLock (this, "InboundLedger", __FILE__, __LINE__)
|
||||
, mRecentFailures ("LedgerAcquireRecentFailures", 0, kReacquireIntervalSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
InboundLedger::pointer InboundLedgers::findCreate (uint256 const& hash, uint32 seq, bool couldBeNew)
|
||||
{
|
||||
assert (hash.isNonZero ());
|
||||
InboundLedger::pointer ret;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
boost::unordered_map<uint256, InboundLedger::pointer>::iterator it = mLedgers.find (hash);
|
||||
if (it != mLedgers.end ())
|
||||
{
|
||||
ret = it->second;
|
||||
// FIXME: Should set the sequence if it's not set
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = boost::make_shared<InboundLedger> (hash, seq);
|
||||
assert (ret);
|
||||
mLedgers.insert (std::make_pair (hash, ret));
|
||||
|
||||
if (!ret->tryLocal())
|
||||
{
|
||||
ret->addPeers ();
|
||||
ret->setTimer (); // Cannot call in constructor
|
||||
}
|
||||
else if (!ret->isFailed ())
|
||||
{
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Acquiring ledger we already have locally: " << hash;
|
||||
Ledger::pointer ledger = ret->getLedger ();
|
||||
ledger->setClosed ();
|
||||
ledger->setImmutable ();
|
||||
getApp().getLedgerMaster ().storeLedger (ledger);
|
||||
if (couldBeNew)
|
||||
getApp().getLedgerMaster().checkAccept(ledger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
InboundLedger::pointer InboundLedgers::find (uint256 const& hash)
|
||||
{
|
||||
assert (hash.isNonZero ());
|
||||
|
||||
InboundLedger::pointer ret;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
boost::unordered_map<uint256, InboundLedger::pointer>::iterator it = mLedgers.find (hash);
|
||||
if (it != mLedgers.end ())
|
||||
{
|
||||
ret = it->second;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool InboundLedgers::hasLedger (uint256 const& hash)
|
||||
{
|
||||
assert (hash.isNonZero ());
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mLedgers.find (hash) != mLedgers.end ();
|
||||
}
|
||||
|
||||
void InboundLedgers::dropLedger (uint256 const& hash)
|
||||
{
|
||||
assert (hash.isNonZero ());
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mLedgers.erase (hash);
|
||||
|
||||
}
|
||||
|
||||
bool InboundLedgers::awaitLedgerData (uint256 const& ledgerHash)
|
||||
{
|
||||
InboundLedger::pointer ledger = find (ledgerHash);
|
||||
|
||||
if (!ledger)
|
||||
return false;
|
||||
|
||||
ledger->awaitData ();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
This gets called when
|
||||
"We got some data from an inbound ledger"
|
||||
|
||||
inboundLedgerTrigger:
|
||||
"What do we do with this partial data?"
|
||||
Figures out what to do with the responses to our requests for information.
|
||||
|
||||
*/
|
||||
// means "We got some data from an inbound ledger"
|
||||
void InboundLedgers::gotLedgerData (Job&, uint256 hash,
|
||||
boost::shared_ptr<protocol::TMLedgerData> packet_ptr, boost::weak_ptr<Peer> wPeer)
|
||||
{
|
||||
protocol::TMLedgerData& packet = *packet_ptr;
|
||||
Peer::pointer peer = wPeer.lock ();
|
||||
|
||||
WriteLog (lsTRACE, InboundLedger) << "Got data (" << packet.nodes ().size () << ") for acquiring ledger: " << hash;
|
||||
|
||||
InboundLedger::pointer ledger = find (hash);
|
||||
|
||||
if (!ledger)
|
||||
{
|
||||
WriteLog (lsTRACE, InboundLedger) << "Got data for ledger we're not acquiring";
|
||||
|
||||
if (peer)
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ledger->noAwaitData ();
|
||||
|
||||
if (!peer)
|
||||
return;
|
||||
|
||||
if (packet.type () == protocol::liBASE)
|
||||
{
|
||||
if (packet.nodes_size () < 1)
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Got empty base data";
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ledger->takeBase (packet.nodes (0).nodedata ()))
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Got invalid base data";
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
SHAMapAddNode san = SHAMapAddNode::useful ();
|
||||
|
||||
if ((packet.nodes ().size () > 1) && !ledger->takeAsRootNode (strCopy (packet.nodes (1).nodedata ()), san))
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Included ASbase invalid";
|
||||
}
|
||||
|
||||
if ((packet.nodes ().size () > 2) && !ledger->takeTxRootNode (strCopy (packet.nodes (2).nodedata ()), san))
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Included TXbase invalid";
|
||||
}
|
||||
|
||||
if (!san.isInvalid ())
|
||||
{
|
||||
ledger->progress ();
|
||||
ledger->trigger (peer);
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Peer sends invalid base data";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((packet.type () == protocol::liTX_NODE) || (packet.type () == protocol::liAS_NODE))
|
||||
{
|
||||
std::list<SHAMapNode> nodeIDs;
|
||||
std::list< Blob > nodeData;
|
||||
|
||||
if (packet.nodes ().size () <= 0)
|
||||
{
|
||||
WriteLog (lsINFO, InboundLedger) << "Got response with no nodes";
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < packet.nodes ().size (); ++i)
|
||||
{
|
||||
const protocol::TMLedgerNode& node = packet.nodes (i);
|
||||
|
||||
if (!node.has_nodeid () || !node.has_nodedata ())
|
||||
{
|
||||
WriteLog (lsWARNING, InboundLedger) << "Got bad node";
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
nodeIDs.push_back (SHAMapNode (node.nodeid ().data (), node.nodeid ().size ()));
|
||||
nodeData.push_back (Blob (node.nodedata ().begin (), node.nodedata ().end ()));
|
||||
}
|
||||
|
||||
SHAMapAddNode ret;
|
||||
|
||||
if (packet.type () == protocol::liTX_NODE)
|
||||
ledger->takeTxNode (nodeIDs, nodeData, ret);
|
||||
else
|
||||
ledger->takeAsNode (nodeIDs, nodeData, ret);
|
||||
|
||||
if (!ret.isInvalid ())
|
||||
{
|
||||
ledger->progress ();
|
||||
ledger->trigger (peer);
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, InboundLedger) << "Peer sends invalid node data";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WriteLog (lsWARNING, InboundLedger) << "Not sure what ledger data we got";
|
||||
peer->applyLoadCharge (LT_InvalidRequest);
|
||||
}
|
||||
|
||||
void InboundLedgers::sweep ()
|
||||
{
|
||||
mRecentFailures.sweep ();
|
||||
|
||||
int const now = UptimeTimer::getInstance ().getElapsedSeconds ();
|
||||
|
||||
// Make a list of things to sweep, while holding the lock
|
||||
std::vector <MapType::mapped_type> stuffToSweep;
|
||||
std::size_t total;
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
MapType::iterator it (mLedgers.begin ());
|
||||
total = mLedgers.size ();
|
||||
stuffToSweep.reserve (total);
|
||||
|
||||
while (it != mLedgers.end ())
|
||||
{
|
||||
if (it->second->getLastAction () > now)
|
||||
{
|
||||
it->second->touch ();
|
||||
++it;
|
||||
}
|
||||
else if ((it->second->getLastAction () + 60) < now)
|
||||
{
|
||||
stuffToSweep.push_back (it->second);
|
||||
// shouldn't cause the actual final delete
|
||||
// since we are holding a reference in the vector.
|
||||
it = mLedgers.erase (it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, InboundLedger) <<
|
||||
"Sweeped " << stuffToSweep.size () <<
|
||||
" out of " << total << " inbound ledgers.";
|
||||
}
|
||||
|
||||
int InboundLedgers::getFetchCount (int& timeoutCount)
|
||||
{
|
||||
timeoutCount = 0;
|
||||
int ret = 0;
|
||||
|
||||
std::vector<u256_acq_pair> inboundLedgers;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
inboundLedgers.reserve(mLedgers.size());
|
||||
BOOST_FOREACH (const u256_acq_pair & it, mLedgers)
|
||||
{
|
||||
inboundLedgers.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const u256_acq_pair & it, inboundLedgers)
|
||||
{
|
||||
if (it.second->isActive ())
|
||||
{
|
||||
++ret;
|
||||
timeoutCount += it.second->getTimeouts ();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void InboundLedgers::gotFetchPack (Job&)
|
||||
{
|
||||
std::vector<InboundLedger::pointer> acquires;
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
acquires.reserve (mLedgers.size ());
|
||||
BOOST_FOREACH (const u256_acq_pair & it, mLedgers)
|
||||
{
|
||||
assert (it.second);
|
||||
acquires.push_back (it.second);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const InboundLedger::pointer & acquire, acquires)
|
||||
{
|
||||
acquire->checkLocal ();
|
||||
}
|
||||
}
|
||||
|
||||
void InboundLedgers::clearFailures ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
mRecentFailures.clear();
|
||||
mLedgers.clear();
|
||||
}
|
||||
|
||||
Json::Value InboundLedgers::getInfo()
|
||||
{
|
||||
Json::Value ret(Json::objectValue);
|
||||
|
||||
std::vector<u256_acq_pair> acquires;
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
acquires.reserve (mLedgers.size ());
|
||||
BOOST_FOREACH (const u256_acq_pair & it, mLedgers)
|
||||
{
|
||||
assert (it.second);
|
||||
acquires.push_back (it);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_FOREACH (const u256_acq_pair& it, acquires)
|
||||
{
|
||||
uint32 seq = it.second->getSeq();
|
||||
if (seq > 1)
|
||||
ret[lexicalCastThrow <std::string>(seq)] = it.second->getJson(0);
|
||||
else
|
||||
ret[it.first.GetHex()] = it.second->getJson(0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
74
src/ripple_app/ledger/ripple_InboundLedgers.h
Normal file
74
src/ripple_app/ledger/ripple_InboundLedgers.h
Normal file
@@ -0,0 +1,74 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_INBOUNDLEDGERS_H
|
||||
#define RIPPLE_INBOUNDLEDGERS_H
|
||||
|
||||
/** Manages the lifetime of inbound ledgers.
|
||||
|
||||
@see InboundLedger
|
||||
*/
|
||||
// VFALCO TODO Rename to InboundLedgers
|
||||
// VFALCO TODO Create abstract interface
|
||||
class InboundLedgers : LeakChecked <InboundLedger>
|
||||
{
|
||||
public:
|
||||
// How long before we try again to acquire the same ledger
|
||||
static const int kReacquireIntervalSeconds = 300;
|
||||
|
||||
InboundLedgers ();
|
||||
|
||||
// VFALCO TODO Should this be called findOrAdd ?
|
||||
//
|
||||
InboundLedger::pointer findCreate (uint256 const& hash, uint32 seq, bool bCouldBeNew);
|
||||
|
||||
InboundLedger::pointer find (uint256 const& hash);
|
||||
|
||||
bool hasLedger (LedgerHash const& ledgerHash);
|
||||
|
||||
void dropLedger (LedgerHash const& ledgerHash);
|
||||
|
||||
bool awaitLedgerData (LedgerHash const& ledgerHash);
|
||||
|
||||
// VFALCO TODO Why is hash passed by value?
|
||||
// VFALCO TODO Remove the dependency on the Peer object.
|
||||
//
|
||||
void gotLedgerData (Job&,
|
||||
LedgerHash hash,
|
||||
boost::shared_ptr <protocol::TMLedgerData> packet,
|
||||
boost::weak_ptr<Peer> peer);
|
||||
|
||||
int getFetchCount (int& timeoutCount);
|
||||
|
||||
void logFailure (uint256 const& h)
|
||||
{
|
||||
mRecentFailures.add (h);
|
||||
}
|
||||
|
||||
bool isFailure (uint256 const& h)
|
||||
{
|
||||
return mRecentFailures.isPresent (h, false);
|
||||
}
|
||||
|
||||
void clearFailures();
|
||||
|
||||
Json::Value getInfo();
|
||||
|
||||
void gotFetchPack (Job&);
|
||||
void sweep ();
|
||||
|
||||
private:
|
||||
typedef boost::unordered_map <uint256, InboundLedger::pointer> MapType;
|
||||
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
MapType mLedgers;
|
||||
KeyCache <uint256, UptimeTimerAdapter> mRecentFailures;
|
||||
};
|
||||
|
||||
#endif
|
||||
1645
src/ripple_app/ledger/ripple_LedgerEntrySet.cpp
Normal file
1645
src/ripple_app/ledger/ripple_LedgerEntrySet.cpp
Normal file
File diff suppressed because it is too large
Load Diff
271
src/ripple_app/ledger/ripple_LedgerEntrySet.h
Normal file
271
src/ripple_app/ledger/ripple_LedgerEntrySet.h
Normal file
@@ -0,0 +1,271 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGERENTRYSET_H
|
||||
#define RIPPLE_LEDGERENTRYSET_H
|
||||
|
||||
enum TransactionEngineParams
|
||||
{
|
||||
tapNONE = 0x00,
|
||||
|
||||
tapNO_CHECK_SIGN = 0x01, // Signature already checked
|
||||
|
||||
tapOPEN_LEDGER = 0x10, // Transaction is running against an open ledger
|
||||
// true = failures are not forwarded, check transaction fee
|
||||
// false = debit ledger for consumed funds
|
||||
|
||||
tapRETRY = 0x20, // This is not the transaction's last pass
|
||||
// Transaction can be retried, soft failures allowed
|
||||
|
||||
tapADMIN = 0x400, // Transaction came from a privileged source
|
||||
};
|
||||
|
||||
enum LedgerEntryAction
|
||||
{
|
||||
taaNONE,
|
||||
taaCACHED, // Unmodified.
|
||||
taaMODIFY, // Modifed, must have previously been taaCACHED.
|
||||
taaDELETE, // Delete, must have previously been taaDELETE or taaMODIFY.
|
||||
taaCREATE, // Newly created.
|
||||
};
|
||||
|
||||
class LedgerEntrySetEntry
|
||||
: public CountedObject <LedgerEntrySetEntry>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "LedgerEntrySetEntry"; }
|
||||
|
||||
SLE::pointer mEntry;
|
||||
LedgerEntryAction mAction;
|
||||
int mSeq;
|
||||
|
||||
LedgerEntrySetEntry (SLE::ref e, LedgerEntryAction a, int s)
|
||||
: mEntry (e)
|
||||
, mAction (a)
|
||||
, mSeq (s)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/** An LES is a LedgerEntrySet.
|
||||
|
||||
It's a view into a ledger used while a transaction is processing.
|
||||
The transaction manipulates the LES rather than the ledger
|
||||
(because it's cheaper, can be checkpointed, and so on). When the
|
||||
transaction finishes, the LES is committed into the ledger to make
|
||||
the modifications. The transaction metadata is built from the LES too.
|
||||
*/
|
||||
class LedgerEntrySet
|
||||
: public CountedObject <LedgerEntrySet>
|
||||
{
|
||||
public:
|
||||
static char const* getCountedObjectName () { return "LedgerEntrySet"; }
|
||||
|
||||
LedgerEntrySet (Ledger::ref ledger, TransactionEngineParams tep, bool immutable = false) :
|
||||
mLedger (ledger), mParams (tep), mSeq (0), mImmutable (immutable)
|
||||
{
|
||||
}
|
||||
|
||||
LedgerEntrySet () : mParams (tapNONE), mSeq (0), mImmutable (false)
|
||||
{
|
||||
}
|
||||
|
||||
// set functions
|
||||
void setImmutable ()
|
||||
{
|
||||
mImmutable = true;
|
||||
}
|
||||
|
||||
bool isImmutable () const
|
||||
{
|
||||
return mImmutable;
|
||||
}
|
||||
|
||||
LedgerEntrySet duplicate () const; // Make a duplicate of this set
|
||||
|
||||
void setTo (const LedgerEntrySet&); // Set this set to have the same contents as another
|
||||
|
||||
void swapWith (LedgerEntrySet&); // Swap the contents of two sets
|
||||
|
||||
void invalidate ()
|
||||
{
|
||||
mLedger.reset ();
|
||||
}
|
||||
|
||||
bool isValid () const
|
||||
{
|
||||
return mLedger != nullptr;
|
||||
}
|
||||
|
||||
int getSeq () const
|
||||
{
|
||||
return mSeq;
|
||||
}
|
||||
|
||||
TransactionEngineParams getParams () const
|
||||
{
|
||||
return mParams;
|
||||
}
|
||||
|
||||
void bumpSeq ()
|
||||
{
|
||||
++mSeq;
|
||||
}
|
||||
|
||||
void init (Ledger::ref ledger, uint256 const & transactionID, uint32 ledgerID, TransactionEngineParams params);
|
||||
|
||||
void clear ();
|
||||
|
||||
Ledger::pointer& getLedger ()
|
||||
{
|
||||
return mLedger;
|
||||
}
|
||||
|
||||
Ledger::ref getLedgerRef () const
|
||||
{
|
||||
return mLedger;
|
||||
}
|
||||
|
||||
// basic entry functions
|
||||
SLE::pointer getEntry (uint256 const & index, LedgerEntryAction&);
|
||||
LedgerEntryAction hasEntry (uint256 const & index) const;
|
||||
void entryCache (SLE::ref); // Add this entry to the cache
|
||||
void entryCreate (SLE::ref); // This entry will be created
|
||||
void entryDelete (SLE::ref); // This entry will be deleted
|
||||
void entryModify (SLE::ref); // This entry will be modified
|
||||
bool hasChanges (); // True if LES has any changes
|
||||
|
||||
// higher-level ledger functions
|
||||
SLE::pointer entryCreate (LedgerEntryType letType, uint256 const & uIndex);
|
||||
SLE::pointer entryCache (LedgerEntryType letType, uint256 const & uIndex);
|
||||
|
||||
// Directory functions.
|
||||
TER dirAdd (
|
||||
uint64 & uNodeDir, // Node of entry.
|
||||
uint256 const & uRootIndex,
|
||||
uint256 const & uLedgerIndex,
|
||||
FUNCTION_TYPE<void (SLE::ref)> fDescriber);
|
||||
|
||||
TER dirDelete (
|
||||
const bool bKeepRoot,
|
||||
const uint64 & uNodeDir, // Node item is mentioned in.
|
||||
uint256 const & uRootIndex,
|
||||
uint256 const & uLedgerIndex, // Item being deleted
|
||||
const bool bStable,
|
||||
const bool bSoft);
|
||||
|
||||
bool dirFirst (uint256 const & uRootIndex, SLE::pointer & sleNode, unsigned int & uDirEntry, uint256 & uEntryIndex);
|
||||
bool dirNext (uint256 const & uRootIndex, SLE::pointer & sleNode, unsigned int & uDirEntry, uint256 & uEntryIndex);
|
||||
TER dirCount (uint256 const & uDirIndex, uint32 & uCount);
|
||||
|
||||
uint256 getNextLedgerIndex (uint256 const & uHash);
|
||||
uint256 getNextLedgerIndex (uint256 const & uHash, uint256 const & uEnd);
|
||||
|
||||
void ownerCountAdjust (const uint160 & uOwnerID, int iAmount, SLE::ref sleAccountRoot = SLE::pointer ());
|
||||
|
||||
// Offer functions.
|
||||
TER offerDelete (uint256 const & uOfferIndex);
|
||||
TER offerDelete (SLE::ref sleOffer, uint256 const & uOfferIndex, const uint160 & uOwnerID);
|
||||
|
||||
// Balance functions.
|
||||
uint32 rippleTransferRate (const uint160 & uIssuerID);
|
||||
uint32 rippleTransferRate (const uint160 & uSenderID, const uint160 & uReceiverID, const uint160 & uIssuerID);
|
||||
STAmount rippleOwed (const uint160 & uToAccountID, const uint160 & uFromAccountID, const uint160 & uCurrencyID);
|
||||
STAmount rippleLimit (const uint160 & uToAccountID, const uint160 & uFromAccountID, const uint160 & uCurrencyID);
|
||||
uint32 rippleQualityIn (const uint160 & uToAccountID, const uint160 & uFromAccountID, const uint160 & uCurrencyID,
|
||||
SField::ref sfLow = sfLowQualityIn, SField::ref sfHigh = sfHighQualityIn);
|
||||
uint32 rippleQualityOut (const uint160 & uToAccountID, const uint160 & uFromAccountID, const uint160 & uCurrencyID)
|
||||
{
|
||||
return rippleQualityIn (uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut);
|
||||
}
|
||||
|
||||
STAmount rippleHolds (const uint160 & uAccountID, const uint160 & uCurrencyID, const uint160 & uIssuerID);
|
||||
STAmount rippleTransferFee (const uint160 & uSenderID, const uint160 & uReceiverID, const uint160 & uIssuerID, const STAmount & saAmount);
|
||||
TER rippleCredit (const uint160 & uSenderID, const uint160 & uReceiverID, const STAmount & saAmount, bool bCheckIssuer = true);
|
||||
TER rippleSend (const uint160 & uSenderID, const uint160 & uReceiverID, const STAmount & saAmount, STAmount & saActual);
|
||||
|
||||
STAmount accountHolds (const uint160 & uAccountID, const uint160 & uCurrencyID, const uint160 & uIssuerID);
|
||||
STAmount accountFunds (const uint160 & uAccountID, const STAmount & saDefault);
|
||||
TER accountSend (const uint160 & uSenderID, const uint160 & uReceiverID, const STAmount & saAmount);
|
||||
|
||||
TER trustCreate (
|
||||
const bool bSrcHigh,
|
||||
const uint160 & uSrcAccountID,
|
||||
const uint160 & uDstAccountID,
|
||||
uint256 const & uIndex,
|
||||
SLE::ref sleAccount,
|
||||
const bool bAuth,
|
||||
const STAmount & saSrcBalance,
|
||||
const STAmount & saSrcLimit,
|
||||
const uint32 uSrcQualityIn = 0,
|
||||
const uint32 uSrcQualityOut = 0);
|
||||
TER trustDelete (SLE::ref sleRippleState, const uint160 & uLowAccountID, const uint160 & uHighAccountID);
|
||||
|
||||
Json::Value getJson (int) const;
|
||||
void calcRawMeta (Serializer&, TER result, uint32 index);
|
||||
|
||||
// iterator functions
|
||||
typedef std::map<uint256, LedgerEntrySetEntry>::iterator iterator;
|
||||
typedef std::map<uint256, LedgerEntrySetEntry>::const_iterator const_iterator;
|
||||
bool isEmpty () const
|
||||
{
|
||||
return mEntries.empty ();
|
||||
}
|
||||
std::map<uint256, LedgerEntrySetEntry>::const_iterator begin () const
|
||||
{
|
||||
return mEntries.begin ();
|
||||
}
|
||||
std::map<uint256, LedgerEntrySetEntry>::const_iterator end () const
|
||||
{
|
||||
return mEntries.end ();
|
||||
}
|
||||
std::map<uint256, LedgerEntrySetEntry>::iterator begin ()
|
||||
{
|
||||
return mEntries.begin ();
|
||||
}
|
||||
std::map<uint256, LedgerEntrySetEntry>::iterator end ()
|
||||
{
|
||||
return mEntries.end ();
|
||||
}
|
||||
|
||||
static bool intersect (const LedgerEntrySet & lesLeft, const LedgerEntrySet & lesRight);
|
||||
|
||||
private:
|
||||
Ledger::pointer mLedger;
|
||||
std::map<uint256, LedgerEntrySetEntry> mEntries; // cannot be unordered!
|
||||
TransactionMetaSet mSet;
|
||||
TransactionEngineParams mParams;
|
||||
int mSeq;
|
||||
bool mImmutable;
|
||||
|
||||
LedgerEntrySet (Ledger::ref ledger, const std::map<uint256, LedgerEntrySetEntry>& e,
|
||||
const TransactionMetaSet & s, int m) :
|
||||
mLedger (ledger), mEntries (e), mSet (s), mParams (tapNONE), mSeq (m), mImmutable (false)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
SLE::pointer getForMod (uint256 const & node, Ledger::ref ledger,
|
||||
boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadTx (const RippleAddress & threadTo, Ledger::ref ledger,
|
||||
boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadTx (SLE::ref threadTo, Ledger::ref ledger, boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
|
||||
bool threadOwners (SLE::ref node, Ledger::ref ledger, boost::unordered_map<uint256, SLE::pointer>& newMods);
|
||||
};
|
||||
|
||||
inline LedgerEntrySet::iterator range_begin (LedgerEntrySet& x)
|
||||
{
|
||||
return x.begin ();
|
||||
}
|
||||
inline LedgerEntrySet::iterator range_end (LedgerEntrySet& x)
|
||||
{
|
||||
return x.end ();
|
||||
}
|
||||
|
||||
#endif
|
||||
182
src/ripple_app/ledger/ripple_LedgerHistory.cpp
Normal file
182
src/ripple_app/ledger/ripple_LedgerHistory.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// VFALCO TODO replace macros
|
||||
|
||||
#ifndef CACHED_LEDGER_NUM
|
||||
#define CACHED_LEDGER_NUM 96
|
||||
#endif
|
||||
|
||||
#ifndef CACHED_LEDGER_AGE
|
||||
#define CACHED_LEDGER_AGE 120
|
||||
#endif
|
||||
|
||||
// FIXME: Need to clean up ledgers by index at some point
|
||||
|
||||
LedgerHistory::LedgerHistory ()
|
||||
: mLedgersByHash ("LedgerCache", CACHED_LEDGER_NUM, CACHED_LEDGER_AGE)
|
||||
, mConsensusValidated ("ConsensusValidated", 64, 300)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void LedgerHistory::addLedger (Ledger::pointer ledger)
|
||||
{
|
||||
assert (ledger && ledger->isImmutable ());
|
||||
assert (ledger->peekAccountStateMap ()->getHash ().isNonZero ());
|
||||
|
||||
TaggedCache::ScopedLockType sl (mLedgersByHash.peekMutex (), __FILE__, __LINE__);
|
||||
|
||||
mLedgersByHash.canonicalize (ledger->getHash(), ledger, true);
|
||||
if (ledger->isValidated())
|
||||
mLedgersByIndex[ledger->getLedgerSeq()] = ledger->getHash();
|
||||
}
|
||||
|
||||
uint256 LedgerHistory::getLedgerHash (uint32 index)
|
||||
{
|
||||
TaggedCache::ScopedLockType sl (mLedgersByHash.peekMutex (), __FILE__, __LINE__);
|
||||
std::map<uint32, uint256>::iterator it (mLedgersByIndex.find (index));
|
||||
|
||||
if (it != mLedgersByIndex.end ())
|
||||
return it->second;
|
||||
|
||||
sl.unlock ();
|
||||
return uint256 ();
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::getLedgerBySeq (uint32 index)
|
||||
{
|
||||
TaggedCache::ScopedLockType sl (mLedgersByHash.peekMutex (), __FILE__, __LINE__);
|
||||
std::map<uint32, uint256>::iterator it (mLedgersByIndex.find (index));
|
||||
|
||||
if (it != mLedgersByIndex.end ())
|
||||
{
|
||||
uint256 hash = it->second;
|
||||
sl.unlock ();
|
||||
return getLedgerByHash (hash);
|
||||
}
|
||||
|
||||
sl.unlock ();
|
||||
|
||||
Ledger::pointer ret (Ledger::loadByIndex (index));
|
||||
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
assert (ret->getLedgerSeq () == index);
|
||||
|
||||
sl.lock (__FILE__, __LINE__);
|
||||
assert (ret->isImmutable ());
|
||||
mLedgersByHash.canonicalize (ret->getHash (), ret);
|
||||
mLedgersByIndex[ret->getLedgerSeq ()] = ret->getHash ();
|
||||
return (ret->getLedgerSeq () == index) ? ret : Ledger::pointer ();
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::getLedgerByHash (uint256 const& hash)
|
||||
{
|
||||
Ledger::pointer ret = mLedgersByHash.fetch (hash);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
assert (ret->isImmutable ());
|
||||
assert (ret->getHash () == hash); // FIXME: We seem to be getting these
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = Ledger::loadByHash (hash);
|
||||
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
assert (ret->isImmutable ());
|
||||
assert (ret->getHash () == hash);
|
||||
mLedgersByHash.canonicalize (ret->getHash (), ret);
|
||||
assert (ret->getHash () == hash);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Ledger::pointer LedgerHistory::canonicalizeLedger (Ledger::pointer ledger, bool save)
|
||||
{
|
||||
assert (ledger->isImmutable ());
|
||||
uint256 h (ledger->getHash ());
|
||||
|
||||
if (!save)
|
||||
{
|
||||
// return input ledger if not in map, otherwise, return corresponding map ledger
|
||||
Ledger::pointer ret = mLedgersByHash.fetch (h);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ledger;
|
||||
}
|
||||
|
||||
// save input ledger in map if not in map, otherwise return corresponding map ledger
|
||||
TaggedCache::ScopedLockType sl (mLedgersByHash.peekMutex (), __FILE__, __LINE__);
|
||||
mLedgersByHash.canonicalize (h, ledger);
|
||||
|
||||
if (ledger->isValidated ())
|
||||
mLedgersByIndex[ledger->getLedgerSeq ()] = ledger->getHash ();
|
||||
|
||||
return ledger;
|
||||
}
|
||||
|
||||
void LedgerHistory::builtLedger (Ledger::ref ledger)
|
||||
{
|
||||
LedgerIndex index = ledger->getLedgerSeq();
|
||||
LedgerHash hash = ledger->getHash();
|
||||
assert (!hash.isZero());
|
||||
TaggedCache::ScopedLockType sl(mConsensusValidated.peekMutex(), __FILE__, __LINE__);
|
||||
|
||||
boost::shared_ptr< std::pair< LedgerHash, LedgerHash > > entry = boost::make_shared<std::pair< LedgerHash, LedgerHash >>();
|
||||
mConsensusValidated.canonicalize(index, entry, false);
|
||||
|
||||
if (entry->first != hash)
|
||||
{
|
||||
if (entry->first.isNonZero() && (entry->first != hash))
|
||||
{
|
||||
WriteLog (lsERROR, LedgerMaster) << "MISMATCH: seq=" << index << " built:" << entry->first << " then:" << hash;
|
||||
}
|
||||
if (entry->second.isNonZero() && (entry->second != hash))
|
||||
{
|
||||
WriteLog (lsERROR, LedgerMaster) << "MISMATCH: seq=" << index << " validated:" << entry->second << " accepted:" << hash;
|
||||
}
|
||||
entry->first = hash;
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerHistory::validatedLedger (Ledger::ref ledger)
|
||||
{
|
||||
LedgerIndex index = ledger->getLedgerSeq();
|
||||
LedgerHash hash = ledger->getHash();
|
||||
assert (!hash.isZero());
|
||||
TaggedCache::ScopedLockType sl(mConsensusValidated.peekMutex(), __FILE__, __LINE__);
|
||||
|
||||
boost::shared_ptr< std::pair< LedgerHash, LedgerHash > > entry = boost::make_shared<std::pair< LedgerHash, LedgerHash >>();
|
||||
mConsensusValidated.canonicalize(index, entry, false);
|
||||
|
||||
if (entry->second != hash)
|
||||
{
|
||||
if (entry->second.isNonZero() && (entry->second != hash))
|
||||
{
|
||||
WriteLog (lsERROR, LedgerMaster) << "MISMATCH: seq=" << index << " validated:" << entry->second << " then:" << hash;
|
||||
}
|
||||
if (entry->first.isNonZero() && (entry->first != hash))
|
||||
{
|
||||
WriteLog (lsERROR, LedgerMaster) << "MISMATCH: seq=" << index << " built:" << entry->first << " validated:" << hash;
|
||||
}
|
||||
entry->second = hash;
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerHistory::tune (int size, int age)
|
||||
{
|
||||
mLedgersByHash.setTargetSize (size);
|
||||
mLedgersByHash.setTargetAge (age);
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
52
src/ripple_app/ledger/ripple_LedgerHistory.h
Normal file
52
src/ripple_app/ledger/ripple_LedgerHistory.h
Normal file
@@ -0,0 +1,52 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGERHISTORY_H
|
||||
#define RIPPLE_LEDGERHISTORY_H
|
||||
|
||||
// VFALCO TODO Rename to OldLedgers ?
|
||||
class LedgerHistory : LeakChecked <LedgerHistory>
|
||||
{
|
||||
public:
|
||||
LedgerHistory ();
|
||||
|
||||
void addLedger (Ledger::pointer ledger);
|
||||
|
||||
float getCacheHitRate ()
|
||||
{
|
||||
return mLedgersByHash.getHitRate ();
|
||||
}
|
||||
|
||||
Ledger::pointer getLedgerBySeq (LedgerIndex ledgerIndex);
|
||||
|
||||
// VFALCO NOTE shouldn't this call the function above?
|
||||
LedgerHash getLedgerHash (LedgerIndex ledgerIndex);
|
||||
|
||||
Ledger::pointer getLedgerByHash (LedgerHash const& ledgerHash);
|
||||
|
||||
Ledger::pointer canonicalizeLedger (Ledger::pointer ledger, bool cache);
|
||||
|
||||
void tune (int size, int age);
|
||||
|
||||
void sweep ()
|
||||
{
|
||||
mLedgersByHash.sweep ();
|
||||
mConsensusValidated.sweep ();
|
||||
}
|
||||
|
||||
void builtLedger (Ledger::ref);
|
||||
void validatedLedger (Ledger::ref);
|
||||
|
||||
private:
|
||||
TaggedCacheType <LedgerHash, Ledger, UptimeTimerAdapter> mLedgersByHash;
|
||||
TaggedCacheType <LedgerIndex, std::pair< LedgerHash, LedgerHash >, UptimeTimerAdapter> mConsensusValidated;
|
||||
|
||||
|
||||
// Maps ledger indexes to the corresponding hash.
|
||||
std::map <LedgerIndex, LedgerHash> mLedgersByIndex; // validated ledgers
|
||||
};
|
||||
|
||||
#endif
|
||||
178
src/ripple_app/main/ParameterTable.cpp
Normal file
178
src/ripple_app/main/ParameterTable.cpp
Normal 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
|
||||
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 = lexicalCastThrow <int> (value.asString ());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
error = Json::objectValue;
|
||||
error["error"] = "Cannot convert to integer";
|
||||
error["value"] = value;
|
||||
return false;
|
||||
}
|
||||
86
src/ripple_app/main/ParameterTable.h
Normal file
86
src/ripple_app/main/ParameterTable.h
Normal 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
|
||||
1352
src/ripple_app/main/ripple_Application.cpp
Normal file
1352
src/ripple_app/main/ripple_Application.cpp
Normal file
File diff suppressed because it is too large
Load Diff
123
src/ripple_app/main/ripple_Application.h
Normal file
123
src/ripple_app/main/ripple_Application.h
Normal file
@@ -0,0 +1,123 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 Peers;
|
||||
class UniqueNodeList;
|
||||
class Validators;
|
||||
|
||||
class NodeStore;
|
||||
class JobQueue;
|
||||
class InboundLedgers;
|
||||
class LedgerMaster;
|
||||
class LoadManager;
|
||||
class NetworkOPs;
|
||||
class OrderBookDB;
|
||||
class ProofOfWorkFactory;
|
||||
class SerializedLedgerEntry;
|
||||
class TransactionMaster;
|
||||
class TxQueue;
|
||||
class LocalCredentials;
|
||||
|
||||
class DatabaseCon;
|
||||
|
||||
typedef TaggedCacheType <uint256, Blob , UptimeTimerAdapter> NodeCache;
|
||||
typedef TaggedCacheType <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
|
||||
*/
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
|
||||
virtual LockType& 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 LoadManager& getLoadManager () = 0;
|
||||
virtual Peers& getPeers () = 0;
|
||||
virtual ProofOfWorkFactory& getProofOfWorkFactory () = 0;
|
||||
virtual UniqueNodeList& getUNL () = 0;
|
||||
virtual Validations& 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 TransactionMaster& getMasterTransaction () = 0;
|
||||
virtual TxQueue& getTxQueue () = 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
|
||||
50
src/ripple_app/main/ripple_FatalErrorReporter.cpp
Normal file
50
src/ripple_app/main/ripple_FatalErrorReporter.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifdef TWICE
|
||||
#error die
|
||||
#endif
|
||||
|
||||
#define TWICE
|
||||
|
||||
FatalErrorReporter::FatalErrorReporter ()
|
||||
{
|
||||
FatalError::setReporter (*this);
|
||||
}
|
||||
|
||||
FatalErrorReporter::~FatalErrorReporter ()
|
||||
{
|
||||
FatalError::resetReporter (*this);
|
||||
}
|
||||
|
||||
void FatalErrorReporter::reportMessage (String& formattedMessage)
|
||||
{
|
||||
Log::out() << formattedMessage.toRawUTF8 ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class FatalErrorReporterTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
FatalErrorReporterTests () : UnitTest ("FatalErrorReporter", "ripple", runManual)
|
||||
{
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
beginTestCase ("report");
|
||||
|
||||
FatalErrorReporter reporter;
|
||||
|
||||
// We don't really expect the program to run after this
|
||||
// but the unit test is here so you can manually test it.
|
||||
|
||||
FatalError ("The unit test intentionally failed", __FILE__, __LINE__);
|
||||
}
|
||||
};
|
||||
|
||||
static FatalErrorReporterTests fatalErrorReporterTests;
|
||||
30
src/ripple_app/main/ripple_FatalErrorReporter.h
Normal file
30
src/ripple_app/main/ripple_FatalErrorReporter.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_FATALERRORREPORTER_H_INCLUDED
|
||||
#define RIPPLE_FATALERRORREPORTER_H_INCLUDED
|
||||
|
||||
/** FatalError reporter.
|
||||
|
||||
This writes the details to standard error and the log. The reporter is
|
||||
installed for the lifetime of the object so typically you would put this
|
||||
at the top of main().
|
||||
|
||||
An alternative is to make it a global variable but for this to cover all
|
||||
possible cases, there can be no other global variables with non trivial
|
||||
constructors that can report a fatal error. Also, the Log would need
|
||||
to be guaranteed to be set up for this handler to work.
|
||||
*/
|
||||
class FatalErrorReporter : public FatalError::Reporter
|
||||
{
|
||||
public:
|
||||
FatalErrorReporter ();
|
||||
~FatalErrorReporter ();
|
||||
|
||||
void reportMessage (String& formattedMessage);
|
||||
};
|
||||
|
||||
#endif
|
||||
437
src/ripple_app/main/ripple_LoadManager.cpp
Normal file
437
src/ripple_app/main/ripple_LoadManager.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (LoadManager)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class LoadManagerImp
|
||||
: public LoadManager
|
||||
, 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:
|
||||
LoadManagerImp ()
|
||||
: mLock (this, "LoadManagerImp", __FILE__, __LINE__)
|
||||
, 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:
|
||||
~LoadManagerImp ()
|
||||
{
|
||||
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
|
||||
{
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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
|
||||
{
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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 ();
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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 ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mDeadLock = UptimeTimer::getInstance ().getElapsedSeconds ();
|
||||
}
|
||||
|
||||
void activateDeadlockDetector ()
|
||||
{
|
||||
mArmed = true;
|
||||
}
|
||||
|
||||
static void logDeadlock (int dlTime)
|
||||
{
|
||||
WriteLog (lsWARNING, LoadManager) << "Server stalled for " << dlTime << " seconds.";
|
||||
|
||||
#if RIPPLE_TRACK_MUTEXES
|
||||
StringArray report;
|
||||
TrackedMutex::generateGlobalBlockedReport (report);
|
||||
if (report.size () > 0)
|
||||
{
|
||||
report.insert (0, String::empty);
|
||||
report.insert (-1, String::empty);
|
||||
Log::print (report);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// VFALCO TODO These used to be public but are apparently not used. Find out why.
|
||||
/*
|
||||
int getCreditRate () const
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mCreditRate;
|
||||
}
|
||||
|
||||
int getCreditLimit () const
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mCreditLimit;
|
||||
}
|
||||
|
||||
int getDebitWarn () const
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mDebitWarn;
|
||||
}
|
||||
|
||||
int getDebitLimit () const
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return mDebitLimit;
|
||||
}
|
||||
|
||||
void setCreditRate (int r)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mCreditRate = r;
|
||||
}
|
||||
|
||||
void setCreditLimit (int r)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mCreditLimit = r;
|
||||
}
|
||||
|
||||
void setDebitWarn (int r)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mDebitWarn = r;
|
||||
}
|
||||
|
||||
void setDebitLimit (int r)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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?
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
// 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
|
||||
//
|
||||
int const reportingIntervalSeconds = 10;
|
||||
if (mArmed && (timeSpentDeadlocked >= reportingIntervalSeconds))
|
||||
{
|
||||
// Report the deadlocked condition every 10 seconds
|
||||
if ((timeSpentDeadlocked % reportingIntervalSeconds) == 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:
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
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
|
||||
|
||||
std::vector <Cost> mCosts;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
LoadManager* LoadManager::New ()
|
||||
{
|
||||
ScopedPointer <LoadManager> object (new LoadManagerImp);
|
||||
return object.release ();
|
||||
}
|
||||
87
src/ripple_app/main/ripple_LoadManager.h
Normal file
87
src/ripple_app/main/ripple_LoadManager.h
Normal file
@@ -0,0 +1,87 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LOADMANAGER_H_INCLUDEd
|
||||
#define RIPPLE_LOADMANAGER_H_INCLUDEd
|
||||
|
||||
/** 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 LoadManager
|
||||
{
|
||||
public:
|
||||
/** Create a new manager.
|
||||
|
||||
The manager thread begins running immediately.
|
||||
|
||||
@note The thresholds for warnings and punishments are in
|
||||
the ctor-initializer
|
||||
*/
|
||||
static LoadManager* New ();
|
||||
|
||||
/** Destroy the manager.
|
||||
|
||||
The destructor returns only after the thread has stopped.
|
||||
*/
|
||||
virtual ~LoadManager () { }
|
||||
|
||||
/** 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
|
||||
156
src/ripple_app/main/ripple_LocalCredentials.cpp
Normal file
156
src/ripple_app/main/ripple_LocalCredentials.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
LocalCredentials::LocalCredentials ()
|
||||
: 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 (!getConfig ().QUIET)
|
||||
Log::out() << "NodeIdentity: " << mNodePublicKey.humanNodePublic ();
|
||||
|
||||
getApp().getUNL ().start ();
|
||||
}
|
||||
|
||||
// Retrieve network identity.
|
||||
bool LocalCredentials::nodeIdentityLoad ()
|
||||
{
|
||||
Database* db = getApp().getWalletDB ()->getDB ();
|
||||
DeprecatedScopedLock 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);
|
||||
|
||||
db->endIterRows ();
|
||||
bSuccess = true;
|
||||
}
|
||||
|
||||
if (getConfig ().NODE_PUB.isValid () && getConfig ().NODE_PRIV.isValid ())
|
||||
{
|
||||
mNodePublicKey = getConfig ().NODE_PUB;
|
||||
mNodePrivateKey = getConfig ().NODE_PRIV;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
// Create and store a network identity.
|
||||
bool LocalCredentials::nodeIdentityCreate ()
|
||||
{
|
||||
if (!getConfig ().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
|
||||
std::string strDh512 (RippleSSLContext::getRawDHParams (512));
|
||||
#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 ();
|
||||
|
||||
DeprecatedScopedLock 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 (!getConfig ().QUIET)
|
||||
Log::out() << "NodeIdentity: Created.";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalCredentials::dataDelete (const std::string& strKey)
|
||||
{
|
||||
Database* db = getApp().getRpcDB ()->getDB ();
|
||||
|
||||
DeprecatedScopedLock 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 ();
|
||||
|
||||
DeprecatedScopedLock 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 ();
|
||||
|
||||
DeprecatedScopedLock 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
|
||||
55
src/ripple_app/main/ripple_LocalCredentials.h
Normal file
55
src/ripple_app/main/ripple_LocalCredentials.h
Normal file
@@ -0,0 +1,55 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 : public 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
LedgerIndex mLedger; // ledger we last synched to
|
||||
};
|
||||
|
||||
#endif
|
||||
// vim:ts=4
|
||||
476
src/ripple_app/main/ripple_RippleMain.cpp
Normal file
476
src/ripple_app/main/ripple_RippleMain.cpp
Normal file
@@ -0,0 +1,476 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 (getConfig ().RPC_STARTUP.isArray ())
|
||||
{
|
||||
for (int i = 0; i != getConfig ().RPC_STARTUP.size (); ++i)
|
||||
{
|
||||
const Json::Value& jvCommand = getConfig ().RPC_STARTUP[i];
|
||||
|
||||
if (!getConfig ().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, Config::ADMIN, &loadType);
|
||||
|
||||
if (!getConfig ().QUIET)
|
||||
Log::out() << "Result: " << jvResult;
|
||||
}
|
||||
}
|
||||
|
||||
getApp().run (); // Blocks till we get a stop RPC.
|
||||
}
|
||||
|
||||
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:
|
||||
explicit RippleUnitTests (bool shouldLog)
|
||||
: m_shouldLog (shouldLog)
|
||||
{
|
||||
// VFALCO NOTE It sucks that we have to do this but some
|
||||
// code demands the Application object exists.
|
||||
//
|
||||
// TODO To find out who, just change the #if 1 to #if 0
|
||||
#if 1
|
||||
setupConfigForUnitTests (&getConfig ());
|
||||
|
||||
getApp ();
|
||||
#endif
|
||||
|
||||
setAssertOnFailure (false);
|
||||
}
|
||||
|
||||
void logMessage (String const& message)
|
||||
{
|
||||
if (m_shouldLog)
|
||||
{
|
||||
#if BEAST_MSVC
|
||||
if (beast_isRunningUnderDebugger ())
|
||||
{
|
||||
Logger::outputDebugString (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << message.toStdString () << std::endl;
|
||||
}
|
||||
|
||||
#else
|
||||
std::cout << message.toStdString () << std::endl;
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void setupConfigForUnitTests (Config* config)
|
||||
{
|
||||
config->nodeDatabase = parseDelimitedKeyValueString ("type=memory");
|
||||
config->ephemeralNodeDatabase = StringPairArray ();
|
||||
config->importNodeDatabase = StringPairArray ();
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_shouldLog;
|
||||
};
|
||||
|
||||
static int runUnitTests (String const& match, String const& format)
|
||||
{
|
||||
bool const shouldLog = format != "junit";
|
||||
|
||||
if (format != "junit" && format != "text" && format != "")
|
||||
{
|
||||
String s;
|
||||
s << "Warning, unknown unittest-format='" << format << "'";
|
||||
Log::out () << s.toStdString ();
|
||||
}
|
||||
|
||||
RippleUnitTests tr (shouldLog);
|
||||
|
||||
tr.runSelectedTests (match);
|
||||
|
||||
if (format == "junit")
|
||||
{
|
||||
UnitTestUtilities::JUnitXMLFormatter f (tr);
|
||||
|
||||
String const s = f.createDocumentString ();
|
||||
|
||||
std::cout << s.toStdString ();
|
||||
}
|
||||
else
|
||||
{
|
||||
UnitTests::Results const& r (tr.getResults ());
|
||||
|
||||
String s;
|
||||
|
||||
s << "Summary: " <<
|
||||
String (r.suites.size ()) << " suites, " <<
|
||||
String (r.cases) << " cases, " <<
|
||||
String (r.tests) << " tests, " <<
|
||||
String (r.failures) << " failure" << ((r.failures != 1) ? "s" : "") << ".";
|
||||
|
||||
tr.logMessage (s);
|
||||
}
|
||||
|
||||
return tr.anyTestsFailed () ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int RippleMain::run (int argc, char const* const* argv)
|
||||
{
|
||||
FatalErrorReporter reporter;
|
||||
|
||||
//
|
||||
// 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 RIPPLE_DUMP_LEAKS_ON_EXIT
|
||||
Debug::setHeapReportLeaks (true);
|
||||
#else
|
||||
Debug::setHeapReportLeaks (false);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
// 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", po::value <std::string> ()->implicit_value (""), "Perform unit tests.")
|
||||
("unittest-format", po::value <std::string> ()->implicit_value ("text"), "Format unit test output. Choices are 'text', 'junit'")
|
||||
("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);
|
||||
|
||||
// NOTE: These must be added before the
|
||||
// Application object is created.
|
||||
//
|
||||
NodeStore::addAvailableBackends ();
|
||||
|
||||
// VFALCO NOTE SqliteBackendFactory is here because it has
|
||||
// dependencies like SqliteDatabase and DatabaseCon
|
||||
//
|
||||
NodeStore::addBackendFactory (SqliteBackendFactory::getInstance ());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Use a watchdog process unless we're invoking a stand alone type of mode
|
||||
//
|
||||
if (HaveSustain ()
|
||||
&& !iResult
|
||||
&& !vm.count ("parameters")
|
||||
&& !vm.count ("fg")
|
||||
&& !vm.count ("standalone")
|
||||
&& !vm.count ("unittest"))
|
||||
{
|
||||
std::string logMe = DoSustain (getConfig ().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.
|
||||
// The unit tests will exit the application with an appropriate return code.
|
||||
//
|
||||
if (vm.count ("unittest"))
|
||||
{
|
||||
String format;
|
||||
|
||||
if (vm.count ("unittest-format"))
|
||||
format = vm ["unittest-format"].as <std::string> ();
|
||||
|
||||
return runUnitTests (vm ["unittest"].as <std::string> (), format);
|
||||
}
|
||||
|
||||
if (!iResult)
|
||||
{
|
||||
getConfig ().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"))
|
||||
{
|
||||
getConfig ().RUN_STANDALONE = true;
|
||||
getConfig ().LEDGER_HISTORY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.count ("start")) getConfig ().START_UP = Config::FRESH;
|
||||
|
||||
// Handle a one-time import option
|
||||
//
|
||||
if (vm.count ("import"))
|
||||
{
|
||||
String const optionString (vm ["import"].as <std::string> ());
|
||||
|
||||
getConfig ().importNodeDatabase = parseDelimitedKeyValueString (optionString);
|
||||
}
|
||||
|
||||
if (vm.count ("ledger"))
|
||||
{
|
||||
getConfig ().START_LEDGER = vm["ledger"].as<std::string> ();
|
||||
if (vm.count("replay"))
|
||||
getConfig ().START_UP = Config::REPLAY;
|
||||
else
|
||||
getConfig ().START_UP = Config::LOAD;
|
||||
}
|
||||
else if (vm.count ("load"))
|
||||
{
|
||||
getConfig ().START_UP = Config::LOAD;
|
||||
}
|
||||
else if (vm.count ("net"))
|
||||
{
|
||||
getConfig ().START_UP = Config::NETWORK;
|
||||
|
||||
if (getConfig ().VALIDATION_QUORUM < 2)
|
||||
getConfig ().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"))
|
||||
{
|
||||
getConfig ().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.
|
||||
getConfig ().setRpcPort (vm ["rpc_port"].as <int> ());
|
||||
}
|
||||
}
|
||||
|
||||
if (iResult == 0)
|
||||
{
|
||||
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 = RPCCall::fromCommandLine (vCmd);
|
||||
}
|
||||
}
|
||||
|
||||
if (1 == iResult && !vm.count ("quiet"))
|
||||
printHelp (desc);
|
||||
|
||||
return iResult;
|
||||
}
|
||||
16
src/ripple_app/main/ripple_RippleMain.h
Normal file
16
src/ripple_app/main/ripple_RippleMain.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_RIPPLEMAIN_H_INCLUDED
|
||||
#define RIPPLE_RIPPLEMAIN_H_INCLUDED
|
||||
|
||||
class RippleMain : public Main
|
||||
{
|
||||
public:
|
||||
int run (int argc, char const* const* argv);
|
||||
};
|
||||
|
||||
#endif
|
||||
3061
src/ripple_app/misc/NetworkOPs.cpp
Normal file
3061
src/ripple_app/misc/NetworkOPs.cpp
Normal file
File diff suppressed because it is too large
Load Diff
295
src/ripple_app/misc/NetworkOPs.h
Normal file
295
src/ripple_app/misc/NetworkOPs.h
Normal file
@@ -0,0 +1,295 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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;
|
||||
|
||||
// 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.
|
||||
//
|
||||
/** Provides server functionality for clients.
|
||||
|
||||
Clients include backend applications, local commands, and connected
|
||||
clients. This class acts as a proxy, fulfilling the command with local
|
||||
data if possible, or asking the network and returning the results if
|
||||
needed.
|
||||
|
||||
A backend application or local client can trust a local instance of
|
||||
rippled / NetworkOPs. However, client software connecting to non-local
|
||||
instances of rippled will need to be hardened to protect against hostile
|
||||
or unreliable servers.
|
||||
*/
|
||||
class NetworkOPs : public InfoSub::Source
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
// VFALCO TODO Fix OrderBookDB to not need this unrelated type.
|
||||
//
|
||||
typedef boost::unordered_map <uint64, InfoSub::wptr> SubMapType;
|
||||
|
||||
public:
|
||||
// VFALCO TODO Make LedgerMaster a SharedPtr or a reference.
|
||||
//
|
||||
static NetworkOPs* New (LedgerMaster& ledgerMaster);
|
||||
|
||||
virtual ~NetworkOPs () { }
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Network information
|
||||
//
|
||||
|
||||
// Our best estimate of wall time in seconds from 1/1/2000
|
||||
virtual uint32 getNetworkTimeNC () = 0;
|
||||
// Our best estimate of current ledger close time
|
||||
virtual uint32 getCloseTimeNC () = 0;
|
||||
// Use *only* to timestamp our own validation
|
||||
virtual uint32 getValidationTimeNC () = 0;
|
||||
virtual void closeTimeOffset (int) = 0;
|
||||
virtual boost::posix_time::ptime getNetworkTimePT () = 0;
|
||||
virtual uint32 getLedgerID (uint256 const& hash) = 0;
|
||||
virtual uint32 getCurrentLedgerID () = 0;
|
||||
|
||||
virtual OperatingMode getOperatingMode () = 0;
|
||||
virtual std::string strOperatingMode () = 0;
|
||||
virtual Ledger::ref getClosedLedger () = 0;
|
||||
virtual Ledger::ref getValidatedLedger () = 0;
|
||||
virtual Ledger::ref getPublishedLedger () = 0;
|
||||
virtual Ledger::ref getCurrentLedger () = 0;
|
||||
virtual Ledger::ref getCurrentSnapshot () = 0;
|
||||
virtual Ledger::pointer getLedgerByHash (uint256 const& hash) = 0;
|
||||
virtual Ledger::pointer getLedgerBySeq (const uint32 seq) = 0;
|
||||
virtual void missingNodeInLedger (const uint32 seq) = 0;
|
||||
|
||||
virtual uint256 getClosedLedgerHash () = 0;
|
||||
|
||||
// Do we have this inclusive range of ledgers in our database
|
||||
virtual bool haveLedgerRange (uint32 from, uint32 to) = 0;
|
||||
virtual bool haveLedger (uint32 seq) = 0;
|
||||
virtual uint32 getValidatedSeq () = 0;
|
||||
virtual bool isValidated (uint32 seq) = 0;
|
||||
virtual bool isValidated (uint32 seq, uint256 const& hash) = 0;
|
||||
virtual bool isValidated (Ledger::ref l) = 0;
|
||||
virtual bool getValidatedRange (uint32& minVal, uint32& maxVal) = 0;
|
||||
virtual bool getFullValidatedRange (uint32& minVal, uint32& maxVal) = 0;
|
||||
|
||||
virtual SerializedValidation::ref getLastValidation () = 0;
|
||||
virtual void setLastValidation (SerializedValidation::ref v) = 0;
|
||||
virtual SLE::pointer getSLE (Ledger::pointer lpLedger, uint256 const& uHash) = 0;
|
||||
virtual SLE::pointer getSLEi (Ledger::pointer lpLedger, uint256 const& uHash) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Transaction processing
|
||||
//
|
||||
|
||||
// must complete immediately
|
||||
// VFALCO TODO Make this a TxCallback structure
|
||||
typedef FUNCTION_TYPE<void (Transaction::pointer, TER)> stCallback;
|
||||
virtual void submitTransaction (Job&, SerializedTransaction::pointer,
|
||||
stCallback callback = stCallback ()) = 0;
|
||||
virtual Transaction::pointer submitTransactionSync (Transaction::ref tpTrans,
|
||||
bool bAdmin, bool bFailHard, bool bSubmit) = 0;
|
||||
virtual void runTransactionQueue () = 0;
|
||||
virtual Transaction::pointer processTransaction (Transaction::pointer,
|
||||
bool bAdmin, bool bFailHard, stCallback) = 0;
|
||||
virtual Transaction::pointer processTransaction (Transaction::pointer transaction,
|
||||
bool bAdmin, bool bFailHard) = 0;
|
||||
virtual Transaction::pointer findTransactionByID (uint256 const& transactionID) = 0;
|
||||
virtual int findTransactionsByDestination (std::list<Transaction::pointer>&,
|
||||
const RippleAddress& destinationAccount, uint32 startLedgerSeq,
|
||||
uint32 endLedgerSeq, int maxTransactions) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Account functions
|
||||
//
|
||||
|
||||
virtual AccountState::pointer getAccountState (Ledger::ref lrLedger,
|
||||
const RippleAddress& accountID) = 0;
|
||||
virtual SLE::pointer getGenerator (Ledger::ref lrLedger,
|
||||
uint160 const& uGeneratorID) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Directory functions
|
||||
//
|
||||
|
||||
virtual STVector256 getDirNodeInfo (Ledger::ref lrLedger,
|
||||
uint256 const& uRootIndex, uint64& uNodePrevious, uint64& uNodeNext) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Owner functions
|
||||
//
|
||||
|
||||
virtual Json::Value getOwnerInfo (Ledger::pointer lpLedger,
|
||||
const RippleAddress& naAccount) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Book functions
|
||||
//
|
||||
|
||||
virtual 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) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// ledger proposal/close functions
|
||||
virtual void processTrustedProposal (LedgerProposal::pointer proposal,
|
||||
boost::shared_ptr<protocol::TMProposeSet> set, RippleAddress nodePublic,
|
||||
uint256 checkLedger, bool sigGood) = 0;
|
||||
|
||||
virtual SHAMapAddNode gotTXData (const boost::shared_ptr<Peer>& peer,
|
||||
uint256 const& hash, const std::list<SHAMapNode>& nodeIDs,
|
||||
const std::list< Blob >& nodeData) = 0;
|
||||
|
||||
virtual bool recvValidation (SerializedValidation::ref val,
|
||||
const std::string& source) = 0;
|
||||
|
||||
virtual void takePosition (int seq, SHAMap::ref position) = 0;
|
||||
|
||||
virtual SHAMap::pointer getTXMap (uint256 const& hash) = 0;
|
||||
|
||||
virtual bool hasTXSet (const boost::shared_ptr<Peer>& peer,
|
||||
uint256 const& set, protocol::TxSetStatus status) = 0;
|
||||
|
||||
virtual void mapComplete (uint256 const& hash, SHAMap::ref map) = 0;
|
||||
|
||||
virtual bool stillNeedTXSet (uint256 const& hash) = 0;
|
||||
|
||||
// Fetch packs
|
||||
virtual void makeFetchPack (Job&, boost::weak_ptr<Peer> peer,
|
||||
boost::shared_ptr<protocol::TMGetObjectByHash> request,
|
||||
Ledger::pointer wantLedger, Ledger::pointer haveLedger, uint32 uUptime) = 0;
|
||||
|
||||
virtual bool shouldFetchPack (uint32 seq) = 0;
|
||||
virtual void gotFetchPack (bool progress, uint32 seq) = 0;
|
||||
virtual void addFetchPack (uint256 const& hash, boost::shared_ptr< Blob >& data) = 0;
|
||||
virtual bool getFetchPack (uint256 const& hash, Blob& data) = 0;
|
||||
virtual int getFetchSize () = 0;
|
||||
virtual void sweepFetchPack () = 0;
|
||||
|
||||
// network state machine
|
||||
virtual void endConsensus (bool correctLCL) = 0;
|
||||
virtual void setStandAlone () = 0;
|
||||
virtual void setStateTimer () = 0;
|
||||
|
||||
virtual void newLCL (int proposers, int convergeTime, uint256 const& ledgerHash) = 0;
|
||||
// VFALCO TODO rename to setNeedNetworkLedger
|
||||
virtual void needNetworkLedger () = 0;
|
||||
virtual void clearNeedNetworkLedger () = 0;
|
||||
virtual bool isNeedNetworkLedger () = 0;
|
||||
virtual bool isFull () = 0;
|
||||
virtual void setProposing (bool isProposing, bool isValidating) = 0;
|
||||
virtual bool isProposing () = 0;
|
||||
virtual bool isValidating () = 0;
|
||||
virtual bool isFeatureBlocked () = 0;
|
||||
virtual void setFeatureBlocked () = 0;
|
||||
virtual void consensusViewChange () = 0;
|
||||
virtual int getPreviousProposers () = 0;
|
||||
virtual int getPreviousConvergeTime () = 0;
|
||||
virtual uint32 getLastCloseTime () = 0;
|
||||
virtual void setLastCloseTime (uint32 t) = 0;
|
||||
|
||||
virtual Json::Value getConsensusInfo () = 0;
|
||||
virtual Json::Value getServerInfo (bool human, bool admin) = 0;
|
||||
virtual void clearLedgerFetch () = 0;
|
||||
virtual Json::Value getLedgerFetchInfo () = 0;
|
||||
virtual uint32 acceptLedger () = 0;
|
||||
|
||||
typedef boost::unordered_map <uint160, std::list<LedgerProposal::pointer> > Proposals;
|
||||
virtual Proposals& peekStoredProposals () = 0;
|
||||
|
||||
virtual void storeProposal (LedgerProposal::ref proposal,
|
||||
const RippleAddress& peerPublic) = 0;
|
||||
|
||||
virtual uint256 getConsensusLCL () = 0;
|
||||
|
||||
virtual void reportFeeChange () = 0;
|
||||
|
||||
//Helper function to generate SQL query to get transactions
|
||||
virtual 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) = 0;
|
||||
|
||||
// client information retrieval functions
|
||||
typedef std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > AccountTxs;
|
||||
virtual AccountTxs getAccountTxs (const RippleAddress& account,
|
||||
int32 minLedger, int32 maxLedger, bool descending, uint32 offset,
|
||||
int limit, bool bAdmin) = 0;
|
||||
|
||||
typedef std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > TxsAccount;
|
||||
virtual TxsAccount getTxsAccount (const RippleAddress& account,
|
||||
int32 minLedger, int32 maxLedger, bool forward, Json::Value& token,
|
||||
int limit, bool bAdmin) = 0;
|
||||
|
||||
typedef boost::tuple<std::string, std::string, uint32> txnMetaLedgerType;
|
||||
typedef std::vector<txnMetaLedgerType> MetaTxsList;
|
||||
virtual MetaTxsList getAccountTxsB (const RippleAddress& account,
|
||||
int32 minLedger, int32 maxLedger, bool descending,
|
||||
uint32 offset, int limit, bool bAdmin) = 0;
|
||||
|
||||
virtual MetaTxsList getTxsAccountB (const RippleAddress& account,
|
||||
int32 minLedger, int32 maxLedger, bool forward,
|
||||
Json::Value& token, int limit, bool bAdmin) = 0;
|
||||
|
||||
virtual std::vector<RippleAddress> getLedgerAffectedAccounts (uint32 ledgerSeq) = 0;
|
||||
|
||||
virtual uint32 countAccountTxs (const RippleAddress& account,
|
||||
int32 minLedger, int32 maxLedger) = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Monitoring: publisher side
|
||||
//
|
||||
virtual void pubLedger (Ledger::ref lpAccepted) = 0;
|
||||
virtual void pubProposedTransaction (Ledger::ref lpCurrent,
|
||||
SerializedTransaction::ref stTxn, TER terResult) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
20
src/ripple_app/misc/PowResult.h
Normal file
20
src/ripple_app/misc/PowResult.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_POWRESULT_H_INCLUDED
|
||||
#define RIPPLE_POWRESULT_H_INCLUDED
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
#endif
|
||||
179
src/ripple_app/misc/ProofOfWork.cpp
Normal file
179
src/ripple_app/misc/ProofOfWork.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (ProofOfWork)
|
||||
|
||||
// VFALCO TODO Move these to a header
|
||||
const uint256 ProofOfWork::sMinTarget ("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
|
||||
|
||||
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 = lexicalCast <int> (fields[2]);
|
||||
}
|
||||
|
||||
bool ProofOfWork::isValid () const
|
||||
{
|
||||
if ((mIterations <= kMaxIterations) && (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 > kMaxIterations) || (target < sMinTarget))
|
||||
{
|
||||
WriteLog (lsINFO, ProofOfWork) << "Iterations:" << iterations;
|
||||
WriteLog (lsINFO, ProofOfWork) << "MaxIterat: " << kMaxIterations;
|
||||
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 > kMaxIterations)
|
||||
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);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool ProofOfWork::calcResultInfo (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;
|
||||
}
|
||||
|
||||
65
src/ripple_app/misc/ProofOfWork.h
Normal file
65
src/ripple_app/misc/ProofOfWork.h
Normal file
@@ -0,0 +1,65 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROOFOFWORK_H
|
||||
#define RIPPLE_PROOFOFWORK_H
|
||||
|
||||
class ProofOfWork : LeakChecked <ProofOfWork>
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
kMaxIterations = (1 << 23)
|
||||
};
|
||||
|
||||
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 * kMaxIterations) 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);
|
||||
|
||||
static bool calcResultInfo (PowResult powCode, std::string& strToken, std::string& strHuman);
|
||||
|
||||
private:
|
||||
std::string mToken;
|
||||
uint256 mChallenge;
|
||||
uint256 mTarget;
|
||||
int mIterations;
|
||||
|
||||
static const uint256 sMinTarget;
|
||||
static const int maxIterations;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
411
src/ripple_app/misc/ProofOfWorkFactory.cpp
Normal file
411
src/ripple_app/misc/ProofOfWorkFactory.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
class ProofOfWorkFactoryImp
|
||||
: public ProofOfWorkFactory
|
||||
, public LeakChecked <ProofOfWorkFactoryImp>
|
||||
{
|
||||
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;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ProofOfWorkFactoryImp ()
|
||||
: mLock (this, "PoWFactory", __FILE__, __LINE__)
|
||||
, mValidTime (180)
|
||||
{
|
||||
setDifficulty (1);
|
||||
RandomNumbers::getInstance ().fillBytes (mSecret.begin (), mSecret.size ());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
enum
|
||||
{
|
||||
numPowEntries = kMaxDifficulty + 1
|
||||
};
|
||||
|
||||
struct PowEntry
|
||||
{
|
||||
const char* target;
|
||||
int iterations;
|
||||
};
|
||||
|
||||
typedef std::vector <PowEntry> PowEntries;
|
||||
|
||||
static PowEntries const& getPowEntries ()
|
||||
{
|
||||
struct StaticPowEntries
|
||||
{
|
||||
StaticPowEntries ()
|
||||
{
|
||||
// VFALCO TODO Make this array know its own size.
|
||||
//
|
||||
PowEntry entries [numPowEntries] =
|
||||
{
|
||||
// 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
|
||||
};
|
||||
|
||||
data.reserve (numPowEntries);
|
||||
|
||||
for (int i = 0; i < numPowEntries; ++i)
|
||||
data.push_back (entries [i]);
|
||||
}
|
||||
|
||||
std::vector <PowEntry> data;
|
||||
};
|
||||
|
||||
static StaticPowEntries list;
|
||||
|
||||
return list.data;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
static int getPowEntry (uint256 const& target, int iterations)
|
||||
{
|
||||
PowEntries const& entries (getPowEntries ());
|
||||
|
||||
for (int i = 0; i < numPowEntries; ++i)
|
||||
{
|
||||
if (entries [i].iterations == iterations)
|
||||
{
|
||||
uint256 t;
|
||||
t.SetHex (entries [i].target);
|
||||
|
||||
if (t == target)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ProofOfWork 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 ());
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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 checkProof (const std::string& token, uint256 const& solution)
|
||||
{
|
||||
// VFALCO COmmented this out because Dave said it wasn't used
|
||||
// and also we dont have the lexicalCast from a vector of strings to a time_t
|
||||
|
||||
// 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;
|
||||
#if 0
|
||||
// Broken with lexicalCast<> changes
|
||||
t = lexicalCast <time_t> (fields[3]);
|
||||
#else
|
||||
t = static_cast <time_t> (lexicalCast <uint64> (fields [3]));
|
||||
#endif
|
||||
|
||||
time_t now = time (NULL);
|
||||
|
||||
int iterations = lexicalCast <int> (fields[2]);
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (!mSolvedChallenges.insert (powMap_vt (now, challenge)).second)
|
||||
{
|
||||
WriteLog (lsDEBUG, ProofOfWork) << "PoW " << token << " has been reused";
|
||||
return powREUSED;
|
||||
}
|
||||
}
|
||||
|
||||
return powOK;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void sweep ()
|
||||
{
|
||||
time_t expire = time (NULL) - mValidTime;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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 loadHigh ()
|
||||
{
|
||||
time_t now = time (NULL);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mLastDifficultyChange == now)
|
||||
return;
|
||||
|
||||
if (mPowEntry == 30)
|
||||
return;
|
||||
|
||||
++mPowEntry;
|
||||
mLastDifficultyChange = now;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void loadLow ()
|
||||
{
|
||||
time_t now = time (NULL);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
if (mLastDifficultyChange == now)
|
||||
return;
|
||||
|
||||
if (mPowEntry == 0)
|
||||
return;
|
||||
|
||||
--mPowEntry;
|
||||
mLastDifficultyChange = now;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void setDifficulty (int i)
|
||||
{
|
||||
assert ((i >= 0) && (i <= kMaxDifficulty));
|
||||
time_t now = time (NULL);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mPowEntry = i;
|
||||
PowEntries const& entries (getPowEntries ());
|
||||
mIterations = entries [i].iterations;
|
||||
mTarget.SetHex (entries [i].target);
|
||||
mLastDifficultyChange = now;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
uint64 getDifficulty ()
|
||||
{
|
||||
return ProofOfWork::getDifficulty (mTarget, mIterations);
|
||||
}
|
||||
|
||||
uint256 const& getSecret () const
|
||||
{
|
||||
return mSecret;
|
||||
}
|
||||
|
||||
void setSecret (uint256 const& secret)
|
||||
{
|
||||
mSecret = secret;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
uint256 mSecret;
|
||||
int mIterations;
|
||||
uint256 mTarget;
|
||||
time_t mLastDifficultyChange;
|
||||
int mValidTime;
|
||||
int mPowEntry;
|
||||
|
||||
powMap_t mSolvedChallenges;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ProofOfWorkFactory* ProofOfWorkFactory::New ()
|
||||
{
|
||||
ScopedPointer <ProofOfWorkFactory> object (new ProofOfWorkFactoryImp);
|
||||
return object.release ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ProofOfWorkTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
ProofOfWorkTests () : UnitTest ("ProofOfWork", "ripple", runManual)
|
||||
{
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
using namespace ripple;
|
||||
|
||||
ProofOfWorkFactoryImp gen;
|
||||
ProofOfWork pow = gen.getProof ();
|
||||
|
||||
String s;
|
||||
|
||||
s << "solve difficulty " << String (pow.getDifficulty ());
|
||||
beginTestCase ("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;
|
||||
41
src/ripple_app/misc/ProofOfWorkFactory.h
Normal file
41
src/ripple_app/misc/ProofOfWorkFactory.h
Normal file
@@ -0,0 +1,41 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROOFOFWORKFACTORY_H_INCLUDED
|
||||
#define RIPPLE_PROOFOFWORKFACTORY_H_INCLUDED
|
||||
|
||||
class ProofOfWorkFactory
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
kMaxDifficulty = 30,
|
||||
};
|
||||
|
||||
static ProofOfWorkFactory* New ();
|
||||
|
||||
virtual ~ProofOfWorkFactory () { }
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#endif
|
||||
11
src/ripple_app/misc/ripple_AccountItem.cpp
Normal file
11
src/ripple_app/misc/ripple_AccountItem.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
AccountItem::AccountItem (SerializedLedgerEntry::ref ledger)
|
||||
: mLedgerEntry (ledger)
|
||||
{
|
||||
|
||||
}
|
||||
74
src/ripple_app/misc/ripple_AccountItem.h
Normal file
74
src/ripple_app/misc/ripple_AccountItem.h
Normal 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
|
||||
68
src/ripple_app/misc/ripple_AccountItems.cpp
Normal file
68
src/ripple_app/misc/ripple_AccountItems.cpp
Normal 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
|
||||
45
src/ripple_app/misc/ripple_AccountItems.h
Normal file
45
src/ripple_app/misc/ripple_AccountItems.h
Normal 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
|
||||
69
src/ripple_app/misc/ripple_AccountState.cpp
Normal file
69
src/ripple_app/misc/ripple_AccountState.cpp
Normal 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
|
||||
77
src/ripple_app/misc/ripple_AccountState.h
Normal file
77
src/ripple_app/misc/ripple_AccountState.h
Normal 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
|
||||
78
src/ripple_app/misc/ripple_CanonicalTXSet.cpp
Normal file
78
src/ripple_app/misc/ripple_CanonicalTXSet.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
109
src/ripple_app/misc/ripple_CanonicalTXSet.h
Normal file
109
src/ripple_app/misc/ripple_CanonicalTXSet.h
Normal 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
|
||||
544
src/ripple_app/misc/ripple_Features.cpp
Normal file
544
src/ripple_app/misc/ripple_Features.cpp
Normal file
@@ -0,0 +1,544 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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;
|
||||
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
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)
|
||||
: mLock (this, "Features", __FILE__, __LINE__)
|
||||
, 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 ("';");
|
||||
|
||||
DeprecatedScopedLock 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)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, true);
|
||||
|
||||
if (s->mVetoed)
|
||||
return false;
|
||||
|
||||
s->mVetoed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Features::unVetoFeature (uint256 const& feature)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, false);
|
||||
|
||||
if (!s || !s->mVetoed)
|
||||
return false;
|
||||
|
||||
s->mVetoed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Features::enableFeature (uint256 const& feature)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, true);
|
||||
|
||||
if (s->mEnabled)
|
||||
return false;
|
||||
|
||||
s->mEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Features::disableFeature (uint256 const& feature)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, false);
|
||||
|
||||
if (!s || !s->mEnabled)
|
||||
return false;
|
||||
|
||||
s->mEnabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Features::isFeatureEnabled (uint256 const& feature)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, false);
|
||||
return s && s->mEnabled;
|
||||
}
|
||||
|
||||
bool Features::isFeatureSupported (uint256 const& feature)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
FeatureState* s = getCreateFeature (feature, false);
|
||||
return s && s->mSupported;
|
||||
}
|
||||
|
||||
Features::featureList_t Features::getVetoedFeatures ()
|
||||
{
|
||||
featureList_t ret;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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 ())
|
||||
{
|
||||
DeprecatedScopedLock 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)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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);
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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
|
||||
194
src/ripple_app/misc/ripple_FeeVote.cpp
Normal file
194
src/ripple_app/misc/ripple_FeeVote.cpp
Normal 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
|
||||
229
src/ripple_app/misc/ripple_HashRouter.cpp
Normal file
229
src/ripple_app/misc/ripple_HashRouter.cpp
Normal file
@@ -0,0 +1,229 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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)
|
||||
: mLock (this, "HashRouter", __FILE__, __LINE__)
|
||||
, 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);
|
||||
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType mLock;
|
||||
|
||||
// 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)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
bool created;
|
||||
findCreateEntry (index, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
HashRouter::Entry HashRouter::getEntry (uint256 const& index)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
bool created;
|
||||
return findCreateEntry (index, created);
|
||||
}
|
||||
|
||||
bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
bool created;
|
||||
findCreateEntry (index, created).addPeer (peer);
|
||||
return created;
|
||||
}
|
||||
|
||||
bool HashRouter::addSuppressionPeer (uint256 const& index, uint64 peer, int& flags)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
bool created;
|
||||
Entry& s = findCreateEntry (index, created);
|
||||
s.addPeer (peer);
|
||||
flags = s.getFlags ();
|
||||
return created;
|
||||
}
|
||||
|
||||
int HashRouter::getFlags (uint256 const& index)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
bool created;
|
||||
return findCreateEntry (index, created).getFlags ();
|
||||
}
|
||||
|
||||
bool HashRouter::addSuppressionFlags (uint256 const& index, int flag)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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);
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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);
|
||||
}
|
||||
130
src/ripple_app/misc/ripple_IFeatures.h
Normal file
130
src/ripple_app/misc/ripple_IFeatures.h
Normal 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
|
||||
44
src/ripple_app/misc/ripple_IFeeVote.h
Normal file
44
src/ripple_app/misc/ripple_IFeeVote.h
Normal 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
|
||||
68
src/ripple_app/misc/ripple_IHashRouter.h
Normal file
68
src/ripple_app/misc/ripple_IHashRouter.h
Normal 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
|
||||
33
src/ripple_app/misc/ripple_NicknameState.cpp
Normal file
33
src/ripple_app/misc/ripple_NicknameState.cpp
Normal 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);
|
||||
}
|
||||
49
src/ripple_app/misc/ripple_NicknameState.h
Normal file
49
src/ripple_app/misc/ripple_NicknameState.h
Normal 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
|
||||
33
src/ripple_app/misc/ripple_Offer.cpp
Normal file
33
src/ripple_app/misc/ripple_Offer.cpp
Normal 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
|
||||
55
src/ripple_app/misc/ripple_Offer.h
Normal file
55
src/ripple_app/misc/ripple_Offer.h
Normal 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
|
||||
19
src/ripple_app/misc/ripple_OrderBook.cpp
Normal file
19
src/ripple_app/misc/ripple_OrderBook.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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)
|
||||
{
|
||||
}
|
||||
72
src/ripple_app/misc/ripple_OrderBook.h
Normal file
72
src/ripple_app/misc/ripple_OrderBook.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
{
|
||||
return mBookBase;
|
||||
}
|
||||
|
||||
uint160 const& getCurrencyIn () const
|
||||
{
|
||||
return mCurrencyIn;
|
||||
}
|
||||
|
||||
uint160 const& getCurrencyOut () const
|
||||
{
|
||||
return mCurrencyOut;
|
||||
}
|
||||
|
||||
uint160 const& getIssuerIn () const
|
||||
{
|
||||
return mIssuerIn;
|
||||
}
|
||||
|
||||
uint160 const& getIssuerOut () const
|
||||
{
|
||||
return mIssuerOut;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
uint256 const mBookBase;
|
||||
uint160 const mCurrencyIn;
|
||||
uint160 const mCurrencyOut;
|
||||
uint160 const mIssuerIn;
|
||||
uint160 const mIssuerOut;
|
||||
};
|
||||
|
||||
#endif
|
||||
204
src/ripple_app/misc/ripple_SerializedLedger.cpp
Normal file
204
src/ripple_app/misc/ripple_SerializedLedger.cpp
Normal 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
|
||||
108
src/ripple_app/misc/ripple_SerializedLedger.h
Normal file
108
src/ripple_app/misc/ripple_SerializedLedger.h
Normal 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
|
||||
351
src/ripple_app/misc/ripple_SerializedTransaction.cpp
Normal file
351
src/ripple_app/misc/ripple_SerializedTransaction.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 (getConfig ().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);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class SerializedTransactionTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
SerializedTransactionTests () : UnitTest ("SerializedTransaction", "ripple")
|
||||
{
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
beginTestCase ("tx");
|
||||
|
||||
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);
|
||||
|
||||
unexpected (!j.checkSign (), "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);
|
||||
fail ("Transaction fails serialize/deserialize test");
|
||||
}
|
||||
else
|
||||
{
|
||||
pass ();
|
||||
}
|
||||
|
||||
UPTR_T<STObject> new_obj = STObject::parseJson (j.getJson (0), sfGeneric);
|
||||
|
||||
if (new_obj.get () == NULL) 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);
|
||||
fail ("Built a different transaction");
|
||||
}
|
||||
else
|
||||
{
|
||||
pass ();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static SerializedTransactionTests serializedTransactionTests;
|
||||
141
src/ripple_app/misc/ripple_SerializedTransaction.h
Normal file
141
src/ripple_app/misc/ripple_SerializedTransaction.h
Normal 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
|
||||
451
src/ripple_app/misc/ripple_Validations.cpp
Normal file
451
src/ripple_app/misc/ripple_Validations.cpp
Normal file
@@ -0,0 +1,451 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
class ValidationsImp;
|
||||
|
||||
SETUP_LOG (Validations)
|
||||
|
||||
typedef std::map<uint160, SerializedValidation::pointer>::value_type u160_val_pair;
|
||||
typedef boost::shared_ptr<ValidationSet> VSpointer;
|
||||
|
||||
class ValidationsImp : public Validations
|
||||
{
|
||||
private:
|
||||
typedef RippleMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
typedef LockType::ScopedUnlockType ScopedUnlockType;
|
||||
LockType mLock;
|
||||
|
||||
TaggedCacheType<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:
|
||||
ValidationsImp ()
|
||||
: mLock (this, "Validations", __FILE__, __LINE__)
|
||||
, 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 ();
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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)
|
||||
{
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
VSpointer set = findSet (ledger);
|
||||
|
||||
if (set)
|
||||
return *set;
|
||||
}
|
||||
return ValidationSet ();
|
||||
}
|
||||
|
||||
void getValidationCount (uint256 const& ledger, bool currentOnly, int& trusted, int& untrusted)
|
||||
{
|
||||
trusted = untrusted = 0;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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";
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
BOOST_FOREACH (u160_val_pair & it, mCurrentValidations)
|
||||
{
|
||||
if (it.second)
|
||||
mStaleValidations.push_back (it.second);
|
||||
|
||||
anyNew = true;
|
||||
}
|
||||
mCurrentValidations.clear ();
|
||||
|
||||
if (anyNew)
|
||||
condWrite ();
|
||||
|
||||
while (mWriting)
|
||||
{
|
||||
ScopedUnlockType sul (mLock, __FILE__, __LINE__);
|
||||
boost::this_thread::sleep (boost::posix_time::milliseconds (100));
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, Validations) << "Validations flushed";
|
||||
}
|
||||
|
||||
void condWrite ()
|
||||
{
|
||||
if (mWriting)
|
||||
return;
|
||||
|
||||
mWriting = true;
|
||||
getApp().getJobQueue ().addJob (jtWRITE, "Validations::doWrite",
|
||||
BIND_TYPE (&ValidationsImp::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);");
|
||||
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
assert (mWriting);
|
||||
|
||||
while (!mStaleValidations.empty ())
|
||||
{
|
||||
std::vector<SerializedValidation::pointer> vector;
|
||||
vector.reserve (512);
|
||||
mStaleValidations.swap (vector);
|
||||
|
||||
{
|
||||
ScopedUnlockType sul (mLock, __FILE__, __LINE__);
|
||||
{
|
||||
Database* db = getApp().getLedgerDB ()->getDB ();
|
||||
DeprecatedScopedLock 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;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mWriting = false;
|
||||
}
|
||||
|
||||
void sweep ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
mValidations.sweep ();
|
||||
}
|
||||
};
|
||||
|
||||
Validations* Validations::New ()
|
||||
{
|
||||
return new ValidationsImp;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
48
src/ripple_app/misc/ripple_Validations.h
Normal file
48
src/ripple_app/misc/ripple_Validations.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_VALIDATIONS_H_INCLUDED
|
||||
#define RIPPLE_VALIDATIONS_H_INCLUDED
|
||||
|
||||
// VFALCO TODO rename and move these typedefs into the Validations interface
|
||||
typedef boost::unordered_map<uint160, SerializedValidation::pointer> ValidationSet;
|
||||
typedef std::pair<int, uint160> currentValidationCount; // nodes validating and highest node ID validating
|
||||
|
||||
class Validations : LeakChecked <Validations>
|
||||
{
|
||||
public:
|
||||
static Validations* New ();
|
||||
|
||||
virtual ~Validations () { }
|
||||
|
||||
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
|
||||
238
src/ripple_app/node/ripple_SqliteBackendFactory.cpp
Normal file
238
src/ripple_app/node/ripple_SqliteBackendFactory.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
static const char* s_nodeStoreDBInit [] =
|
||||
{
|
||||
"PRAGMA synchronous=NORMAL;",
|
||||
"PRAGMA journal_mode=WAL;",
|
||||
"PRAGMA journal_size_limit=1582080;",
|
||||
|
||||
#if (ULONG_MAX > UINT_MAX) && !defined (NO_SQLITE_MMAP)
|
||||
"PRAGMA mmap_size=171798691840;",
|
||||
#endif
|
||||
|
||||
"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE CommittedObjects ( \
|
||||
Hash CHARACTER(64) PRIMARY KEY, \
|
||||
ObjType CHAR(1) NOT NULL, \
|
||||
LedgerIndex BIGINT UNSIGNED, \
|
||||
Object BLOB \
|
||||
);",
|
||||
|
||||
"END TRANSACTION;"
|
||||
};
|
||||
|
||||
static int s_nodeStoreDBCount = NUMBER (s_nodeStoreDBInit);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class SqliteBackendFactory::Backend : public NodeStore::Backend
|
||||
{
|
||||
public:
|
||||
Backend (size_t keyBytes, std::string const& path)
|
||||
: m_keyBytes (keyBytes)
|
||||
, m_name (path)
|
||||
, m_db (new DatabaseCon(path, s_nodeStoreDBInit, s_nodeStoreDBCount))
|
||||
{
|
||||
String s;
|
||||
|
||||
// VFALCO TODO Remove this dependency on theConfig
|
||||
//
|
||||
s << "PRAGMA cache_size=-" << String (getConfig ().getSize(siHashNodeDBCache) * 1024);
|
||||
m_db->getDB()->executeSQL (s.toStdString ().c_str ());
|
||||
}
|
||||
|
||||
~Backend()
|
||||
{
|
||||
}
|
||||
|
||||
std::string getName()
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
Status fetch (void const* key, NodeObject::Ptr* pObject)
|
||||
{
|
||||
Status result = ok;
|
||||
|
||||
pObject->reset ();
|
||||
|
||||
{
|
||||
DeprecatedScopedLock sl (m_db->getDBLock());
|
||||
|
||||
uint256 const hash (uint256::fromVoid (key));
|
||||
|
||||
static SqliteStatement pSt (m_db->getDB()->getSqliteDB(),
|
||||
"SELECT ObjType,LedgerIndex,Object FROM CommittedObjects WHERE Hash = ?;");
|
||||
|
||||
pSt.bind (1, hash.GetHex());
|
||||
|
||||
if (pSt.isRow (pSt.step()))
|
||||
{
|
||||
// VFALCO NOTE This is unfortunately needed,
|
||||
// the DatabaseCon creates the blob?
|
||||
Blob data (pSt.getBlob (2));
|
||||
*pObject = NodeObject::createObject (
|
||||
getTypeFromString (pSt.peekString (0)),
|
||||
pSt.getUInt32 (1),
|
||||
data,
|
||||
hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = notFound;
|
||||
}
|
||||
|
||||
pSt.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void store (NodeObject::ref object)
|
||||
{
|
||||
NodeStore::Batch batch;
|
||||
|
||||
batch.push_back (object);
|
||||
|
||||
storeBatch (batch);
|
||||
}
|
||||
|
||||
void storeBatch (NodeStore::Batch const& batch)
|
||||
{
|
||||
// VFALCO TODO Rewrite this to use Beast::db
|
||||
|
||||
DeprecatedScopedLock sl (m_db->getDBLock());
|
||||
|
||||
static SqliteStatement pStB (m_db->getDB()->getSqliteDB(), "BEGIN TRANSACTION;");
|
||||
static SqliteStatement pStE (m_db->getDB()->getSqliteDB(), "END TRANSACTION;");
|
||||
static SqliteStatement pSt (m_db->getDB()->getSqliteDB(),
|
||||
"INSERT OR IGNORE INTO CommittedObjects "
|
||||
"(Hash,ObjType,LedgerIndex,Object) VALUES (?, ?, ?, ?);");
|
||||
|
||||
pStB.step();
|
||||
pStB.reset();
|
||||
|
||||
BOOST_FOREACH (NodeObject::Ptr const& object, batch)
|
||||
{
|
||||
doBind (pSt, object);
|
||||
|
||||
pSt.step();
|
||||
pSt.reset();
|
||||
}
|
||||
|
||||
pStE.step();
|
||||
pStE.reset();
|
||||
}
|
||||
|
||||
void visitAll (VisitCallback& callback)
|
||||
{
|
||||
// No lock needed as per the visitAll() API
|
||||
|
||||
uint256 hash;
|
||||
|
||||
static SqliteStatement pSt(m_db->getDB()->getSqliteDB(),
|
||||
"SELECT ObjType,LedgerIndex,Object,Hash FROM CommittedObjects;");
|
||||
|
||||
while (pSt.isRow (pSt.step()))
|
||||
{
|
||||
hash.SetHexExact(pSt.getString(3));
|
||||
|
||||
// VFALCO NOTE This is unfortunately needed,
|
||||
// the DatabaseCon creates the blob?
|
||||
Blob data (pSt.getBlob (2));
|
||||
NodeObject::Ptr const object (NodeObject::createObject (
|
||||
getTypeFromString (pSt.peekString (0)),
|
||||
pSt.getUInt32 (1),
|
||||
data,
|
||||
hash));
|
||||
|
||||
callback.visitObject (object);
|
||||
}
|
||||
|
||||
pSt.reset ();
|
||||
}
|
||||
|
||||
int getWriteLoad ()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void doBind (SqliteStatement& statement, NodeObject::ref object)
|
||||
{
|
||||
char const* type;
|
||||
switch (object->getType())
|
||||
{
|
||||
case hotLEDGER: type = "L"; break;
|
||||
case hotTRANSACTION: type = "T"; break;
|
||||
case hotACCOUNT_NODE: type = "A"; break;
|
||||
case hotTRANSACTION_NODE: type = "N"; break;
|
||||
default: type = "U";
|
||||
}
|
||||
|
||||
statement.bind(1, object->getHash().GetHex());
|
||||
statement.bind(2, type);
|
||||
statement.bind(3, object->getIndex());
|
||||
statement.bindStatic(4, object->getData());
|
||||
}
|
||||
|
||||
NodeObjectType getTypeFromString (std::string const& s)
|
||||
{
|
||||
NodeObjectType type = hotUNKNOWN;
|
||||
|
||||
if (!s.empty ())
|
||||
{
|
||||
switch (s [0])
|
||||
{
|
||||
case 'L': type = hotLEDGER; break;
|
||||
case 'T': type = hotTRANSACTION; break;
|
||||
case 'A': type = hotACCOUNT_NODE; break;
|
||||
case 'N': type = hotTRANSACTION_NODE; break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t const m_keyBytes;
|
||||
std::string const m_name;
|
||||
ScopedPointer <DatabaseCon> m_db;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
SqliteBackendFactory::SqliteBackendFactory ()
|
||||
{
|
||||
}
|
||||
|
||||
SqliteBackendFactory::~SqliteBackendFactory ()
|
||||
{
|
||||
}
|
||||
|
||||
SqliteBackendFactory& SqliteBackendFactory::getInstance ()
|
||||
{
|
||||
static SqliteBackendFactory instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
String SqliteBackendFactory::getName () const
|
||||
{
|
||||
return "Sqlite";
|
||||
}
|
||||
|
||||
NodeStore::Backend* SqliteBackendFactory::createInstance (
|
||||
size_t keyBytes,
|
||||
StringPairArray const& keyValues,
|
||||
NodeStore::Scheduler& scheduler)
|
||||
{
|
||||
return new Backend (keyBytes, keyValues ["path"].toStdString ());
|
||||
}
|
||||
32
src/ripple_app/node/ripple_SqliteBackendFactory.h
Normal file
32
src/ripple_app/node/ripple_SqliteBackendFactory.h
Normal file
@@ -0,0 +1,32 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SQLITEBACKENDFACTORY_H_INCLUDED
|
||||
#define RIPPLE_SQLITEBACKENDFACTORY_H_INCLUDED
|
||||
|
||||
/** Factory to produce SQLite backends for the NodeStore.
|
||||
|
||||
@see NodeStore
|
||||
*/
|
||||
class SqliteBackendFactory : public NodeStore::BackendFactory
|
||||
{
|
||||
private:
|
||||
class Backend;
|
||||
|
||||
SqliteBackendFactory ();
|
||||
~SqliteBackendFactory ();
|
||||
|
||||
public:
|
||||
static SqliteBackendFactory& getInstance ();
|
||||
|
||||
String getName () const;
|
||||
|
||||
NodeStore::Backend* createInstance (size_t keyBytes,
|
||||
StringPairArray const& keyValues,
|
||||
NodeStore::Scheduler& scheduler);
|
||||
};
|
||||
|
||||
#endif
|
||||
411
src/ripple_app/paths/ripple_PathRequest.cpp
Normal file
411
src/ripple_app/paths/ripple_PathRequest.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Copyright (c) 2011-2013, OpenCoin, Inc.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
SETUP_LOG (PathRequest)
|
||||
|
||||
// VFALCO TODO Move these globals into a PathRequests collection inteface
|
||||
PathRequest::StaticLockType PathRequest::sLock ("PathRequest", __FILE__, __LINE__);
|
||||
std::set <PathRequest::wptr> PathRequest::sRequests;
|
||||
|
||||
PathRequest::PathRequest (const boost::shared_ptr<InfoSub>& subscriber)
|
||||
: mLock (this, "PathRequest", __FILE__, __LINE__)
|
||||
, wpSubscriber (subscriber)
|
||||
, jvStatus (Json::objectValue)
|
||||
, bValid (false)
|
||||
, bNew (true)
|
||||
, iLastLevel (0)
|
||||
, bLastSuccess (false)
|
||||
{
|
||||
}
|
||||
|
||||
bool PathRequest::isValid ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return bValid;
|
||||
}
|
||||
|
||||
bool PathRequest::isNew ()
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return bNew;
|
||||
}
|
||||
|
||||
bool PathRequest::isValid (Ledger::ref lrLedger)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
|
||||
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 ();
|
||||
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
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&)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return jvStatus;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doStatus (const Json::Value&)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
return jvStatus;
|
||||
}
|
||||
|
||||
bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
{
|
||||
ScopedLockType sl (mLock, __FILE__, __LINE__);
|
||||
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;
|
||||
|
||||
int iLevel = iLastLevel;
|
||||
bool loaded = getApp().getFeeTrack().isLoadedLocal();
|
||||
|
||||
if (iLevel == 0)
|
||||
{ // first pass
|
||||
if (loaded)
|
||||
iLevel = getConfig().PATH_SEARCH_FAST;
|
||||
else if (!fast)
|
||||
iLevel = getConfig().PATH_SEARCH_OLD;
|
||||
else if (getConfig().PATH_SEARCH < getConfig().PATH_SEARCH_MAX)
|
||||
iLevel = getConfig().PATH_SEARCH + 1; // start with an extra boost
|
||||
else
|
||||
iLevel = getConfig().PATH_SEARCH;
|
||||
}
|
||||
else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast)
|
||||
{ // leaving fast pathfinding
|
||||
iLevel = getConfig().PATH_SEARCH;
|
||||
if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
|
||||
--iLevel;
|
||||
else if (!loaded && (iLevel < getConfig().PATH_SEARCH))
|
||||
++iLevel;
|
||||
}
|
||||
else if (bLastSuccess)
|
||||
{ // decrement, if possible
|
||||
if ((iLevel > getConfig().PATH_SEARCH) || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)))
|
||||
--iLevel;
|
||||
}
|
||||
else
|
||||
{ // adjust as needed
|
||||
if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX))
|
||||
++iLevel;
|
||||
if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))
|
||||
--iLevel;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
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 = mContext[currIssuer];
|
||||
Pathfinder pf (cache, raSrcAccount, raDstAccount,
|
||||
currIssuer.first, currIssuer.second, saDstAmount, valid);
|
||||
CondLog (!valid, lsINFO, PathRequest) << "PF request not valid";
|
||||
|
||||
if (valid && pf.findPaths (iLevel, 4, 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);
|
||||
found = true;
|
||||
jvArray.append (jvEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsINFO, PathRequest) << "rippleCalc returns " << transHuman (terResult);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsINFO, PathRequest) << "No paths found";
|
||||
}
|
||||
}
|
||||
|
||||
iLastLevel = iLevel;
|
||||
bLastSuccess = found;
|
||||
|
||||
jvStatus["alternatives"] = jvArray;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PathRequest::updateAll (Ledger::ref ledger, bool newOnly)
|
||||
{
|
||||
std::set<wptr> requests;
|
||||
|
||||
{
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
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)
|
||||
{
|
||||
if (newOnly && !pRequest->isNew ())
|
||||
remove = false;
|
||||
else
|
||||
{
|
||||
InfoSub::pointer ipSub = pRequest->wpSubscriber.lock ();
|
||||
|
||||
if (ipSub)
|
||||
{
|
||||
Json::Value update;
|
||||
{
|
||||
ScopedLockType sl (pRequest->mLock, __FILE__, __LINE__);
|
||||
pRequest->doUpdate (cache, false);
|
||||
update = pRequest->jvStatus;
|
||||
}
|
||||
update["type"] = "path_find";
|
||||
ipSub->send (update, false);
|
||||
remove = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
StaticScopedLockType sl (sLock, __FILE__, __LINE__);
|
||||
sRequests.erase (wRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
82
src/ripple_app/paths/ripple_PathRequest.h
Normal file
82
src/ripple_app/paths/ripple_PathRequest.h
Normal file
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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:
|
||||
void setValid ();
|
||||
int parseJson (const Json::Value&, bool complete);
|
||||
|
||||
typedef RippleRecursiveMutex LockType;
|
||||
typedef LockType::ScopedLockType ScopedLockType;
|
||||
LockType 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;
|
||||
std::map<currIssuer_t, STPathSet> mContext;
|
||||
|
||||
bool bValid;
|
||||
bool bNew;
|
||||
|
||||
int iLastLevel;
|
||||
bool bLastSuccess;
|
||||
|
||||
// Track all requests
|
||||
static std::set<wptr> sRequests;
|
||||
|
||||
typedef RippleRecursiveMutex StaticLockType;
|
||||
typedef LockType::ScopedLockType StaticScopedLockType;
|
||||
static StaticLockType sLock;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// vim:ts=4
|
||||
794
src/ripple_app/paths/ripple_PathState.cpp
Normal file
794
src/ripple_app/paths/ripple_PathState.cpp
Normal 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 (lsTRACE, 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 (lsTRACE, 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;
|
||||
}
|
||||
|
||||
164
src/ripple_app/paths/ripple_PathState.h
Normal file
164
src/ripple_app/paths/ripple_PathState.h
Normal 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
|
||||
854
src/ripple_app/paths/ripple_Pathfinder.cpp
Normal file
854
src/ripple_app/paths/ripple_Pathfinder.cpp
Normal file
@@ -0,0 +1,854 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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> ();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// FIXME: This is not right
|
||||
getApp().getOrderBookDB ().setup (mLedger);
|
||||
|
||||
m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath");
|
||||
|
||||
bool bIssuer = mSrcCurrencyID.isNonZero() && mSrcIssuerID.isNonZero() && (mSrcIssuerID != mSrcAccountID);
|
||||
mSource = STPathElement( // Where does an empty path start?
|
||||
bIssuer ? mSrcIssuerID : mSrcAccountID, // On the source account or issuer account
|
||||
mSrcCurrencyID, // In the source currency
|
||||
mSrcCurrencyID.isZero() ? uint160() : (bIssuer ? mSrcIssuerID : mSrcAccountID));
|
||||
|
||||
}
|
||||
|
||||
bool Pathfinder::findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& pathsOut)
|
||||
{ // pathsOut contains only non-default paths without source or destiation
|
||||
// On input, pathsOut contains any paths you want to ensure are included if still good
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool bSrcXrp = mSrcCurrencyID.isZero();
|
||||
bool bDstXrp = mDstAmount.getCurrency().isZero();
|
||||
|
||||
SLE::pointer sleSrc = mLedger->getSLEi(Ledger::getAccountRootIndex(mSrcAccountID));
|
||||
if (!sleSrc)
|
||||
return false;
|
||||
|
||||
SLE::pointer sleDest = mLedger->getSLEi(Ledger::getAccountRootIndex(mDstAccountID));
|
||||
if (!sleDest && (!bDstXrp || (mDstAmount < mLedger->getReserve(0))))
|
||||
return false;
|
||||
|
||||
PaymentType paymentType;
|
||||
if (bSrcXrp && bDstXrp)
|
||||
{ // XRP -> XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "XRP to XRP payment";
|
||||
paymentType = pt_XRP_to_XRP;
|
||||
|
||||
}
|
||||
else if (bSrcXrp)
|
||||
{ // XRP -> non-XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "XRP to non-XRP payment";
|
||||
paymentType = pt_XRP_to_nonXRP;
|
||||
|
||||
}
|
||||
else if (bDstXrp)
|
||||
{ // non-XRP -> XRP
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to XRP payment";
|
||||
paymentType = pt_nonXRP_to_XRP;
|
||||
|
||||
}
|
||||
else if (mSrcCurrencyID == mDstAmount.getCurrency())
|
||||
{ // non-XRP -> non-XRP - Same currency
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - same currency";
|
||||
paymentType = pt_nonXRP_to_same;
|
||||
|
||||
}
|
||||
else
|
||||
{ // non-XRP to non-XRP - Different currency
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - cross currency";
|
||||
paymentType = pt_nonXRP_to_nonXRP;
|
||||
|
||||
}
|
||||
|
||||
BOOST_FOREACH(CostedPath_t const& costedPath, mPathTable[paymentType])
|
||||
{
|
||||
if (costedPath.first <= iLevel)
|
||||
{
|
||||
getPaths(costedPath.second);
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " complete paths found";
|
||||
|
||||
BOOST_FOREACH(const STPath& path, pathsOut)
|
||||
{ // make sure no paths were lost
|
||||
bool found = false;
|
||||
BOOST_FOREACH(const STPath& ePath, mCompletePaths)
|
||||
{
|
||||
if (ePath == path)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
mCompletePaths.addPath(path);
|
||||
}
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " paths to filter";
|
||||
|
||||
if (mCompletePaths.size() > iMaxPaths)
|
||||
pathsOut = filterPaths(iMaxPaths);
|
||||
else
|
||||
pathsOut = mCompletePaths;
|
||||
|
||||
return true; // Even if we find no paths, default paths may work, and we don't check them currently
|
||||
}
|
||||
|
||||
STPathSet Pathfinder::filterPaths(int iMaxPaths)
|
||||
{
|
||||
if (mCompletePaths.size() <= iMaxPaths)
|
||||
return mCompletePaths;
|
||||
|
||||
STAmount remaining = mDstAmount;
|
||||
|
||||
// must subtract liquidity in default path from remaining amount
|
||||
try
|
||||
{
|
||||
STAmount saMaxAmountAct, saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
LedgerEntrySet lesSandbox (mLedger, tapNONE);
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
std::vector<path_LQ_t> vMap;
|
||||
|
||||
// Build map of quality to entry.
|
||||
for (int i = mCompletePaths.size (); i--;)
|
||||
{
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
std::vector<PathState::pointer> vpsExpanded;
|
||||
STPathSet spsPaths;
|
||||
STPath& spCurrent = mCompletePaths[i];
|
||||
|
||||
spsPaths.addPath (spCurrent); // Just checking the current path.
|
||||
|
||||
TER terResult;
|
||||
|
||||
try
|
||||
{
|
||||
LedgerEntrySet lesSandbox (mLedger, tapNONE);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet spsDst;
|
||||
|
||||
if (vMap.size())
|
||||
{
|
||||
std::sort (vMap.begin (), vMap.end (), bQualityCmp); // Lower is better and should be first.
|
||||
|
||||
|
||||
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 (mCompletePaths[lqt.get<3> ()]);
|
||||
}
|
||||
else
|
||||
WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << mCompletePaths[lqt.get<3> ()].getJson (0);
|
||||
}
|
||||
|
||||
if (remaining.isPositive ())
|
||||
{
|
||||
WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
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"));
|
||||
}
|
||||
|
||||
return spsDst;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (currency != mSrcCurrencyID)
|
||||
return false;
|
||||
|
||||
if (currency.isZero())
|
||||
return true;
|
||||
|
||||
return (issuer == mSrcIssuerID) || (issuer == mSrcAccountID);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void Pathfinder::addLink(
|
||||
const STPathSet& currentPaths, // The paths to build from
|
||||
STPathSet& incompletePaths, // The set of partial paths we add to
|
||||
int addFlags)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "addLink< on " << currentPaths.size() << " source(s), flags=" << addFlags;
|
||||
BOOST_FOREACH(const STPath& path, currentPaths)
|
||||
{
|
||||
addLink(path, incompletePaths, addFlags);
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete)
|
||||
{
|
||||
std::map< PathType_t, STPathSet >::iterator it = mPaths.find(type);
|
||||
|
||||
// We already have these paths
|
||||
if (it != mPaths.end())
|
||||
return it->second;
|
||||
|
||||
// The type is empty
|
||||
if (type.empty())
|
||||
return mPaths[type];
|
||||
|
||||
NodeType toAdd = type.back();
|
||||
PathType_t pathType(type);
|
||||
pathType.pop_back();
|
||||
|
||||
STPathSet pathsIn = getPaths(pathType, false);
|
||||
STPathSet& pathsOut = mPaths[type];
|
||||
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< "getPaths< adding onto '"
|
||||
<< pathTypeToString(pathType) << "' to get '"
|
||||
<< pathTypeToString(type) << "'";
|
||||
|
||||
int cp = mCompletePaths.size();
|
||||
|
||||
switch (toAdd)
|
||||
{
|
||||
|
||||
case nt_SOURCE:
|
||||
|
||||
{ // source is an empty path
|
||||
assert(pathsOut.isEmpty());
|
||||
pathsOut.addPath(STPath());
|
||||
}
|
||||
break;
|
||||
|
||||
case nt_ACCOUNTS:
|
||||
addLink(pathsIn, pathsOut, afADD_ACCOUNTS);
|
||||
break;
|
||||
|
||||
case nt_BOOKS:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS);
|
||||
break;
|
||||
|
||||
case nt_XRP_BOOK:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_XRP);
|
||||
break;
|
||||
|
||||
case nt_DEST_BOOK:
|
||||
addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_LAST);
|
||||
break;
|
||||
|
||||
case nt_DESTINATION:
|
||||
// FIXME: What if a different issuer was specified on the destination amount
|
||||
addLink(pathsIn, pathsOut, afADD_ACCOUNTS | afAC_LAST);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
CondLog (mCompletePaths.size() != cp, lsDEBUG, Pathfinder)
|
||||
<< (mCompletePaths.size() - cp)
|
||||
<< " complete paths added";
|
||||
WriteLog (lsDEBUG, Pathfinder) << "getPaths> " << pathsOut.size() << " partial paths found";
|
||||
return pathsOut;
|
||||
}
|
||||
|
||||
void Pathfinder::addLink(
|
||||
const STPath& currentPath, // The path to build from
|
||||
STPathSet& incompletePaths, // The set of partial paths we add to
|
||||
int addFlags)
|
||||
{
|
||||
STPathElement const& pathEnd = currentPath.isEmpty() ? mSource : currentPath.mPath.back ();
|
||||
uint160 const& uEndCurrency = pathEnd.mCurrencyID;
|
||||
uint160 const& uEndIssuer = pathEnd.mIssuerID;
|
||||
uint160 const& uEndAccount = pathEnd.mAccountID;
|
||||
bool const bOnXRP = uEndCurrency.isZero();
|
||||
|
||||
WriteLog (lsTRACE, Pathfinder) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP;
|
||||
WriteLog (lsTRACE, Pathfinder) << currentPath.getJson(0);
|
||||
|
||||
if (addFlags & afADD_ACCOUNTS)
|
||||
{ // add accounts
|
||||
if (bOnXRP)
|
||||
{
|
||||
if (mDstAmount.isNative() && !currentPath.isEmpty())
|
||||
{ // non-default path to XRP destination
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ax: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(currentPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // search for accounts to add
|
||||
SLE::pointer sleEnd = mLedger->getSLEi(Ledger::getAccountRootIndex(uEndAccount));
|
||||
if (sleEnd)
|
||||
{
|
||||
bool const bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth);
|
||||
bool const bIsEndCurrency = (uEndCurrency == mDstAmount.getCurrency());
|
||||
|
||||
AccountItems& rippleLines(mRLCache->getRippleLines(uEndAccount));
|
||||
|
||||
std::vector< std::pair<int, uint160> > candidates;
|
||||
candidates.reserve(rippleLines.getItems().size());
|
||||
|
||||
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
|
||||
{
|
||||
RippleState const& rspEntry = * reinterpret_cast<RippleState const *>(item.get());
|
||||
uint160 const& acctID = rspEntry.getAccountIDPeer();
|
||||
|
||||
if ((uEndCurrency == rspEntry.getLimit().getCurrency()) &&
|
||||
!currentPath.hasSeen(acctID, uEndCurrency, acctID))
|
||||
{ // path is for correct currency and has not been seen
|
||||
if (!rspEntry.getBalance().isPositive()
|
||||
&& (!rspEntry.getLimitPeer()
|
||||
|| -rspEntry.getBalance() >= rspEntry.getLimitPeer()
|
||||
|| (bRequireAuth && !rspEntry.getAuth())))
|
||||
{
|
||||
// path has no credit
|
||||
}
|
||||
else if (acctID == mDstAccountID)
|
||||
{ // destination is always worth trying
|
||||
if (uEndCurrency == mDstAmount.getCurrency())
|
||||
{ // this is a complete path
|
||||
if (!currentPath.isEmpty())
|
||||
{
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ae: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(currentPath);
|
||||
}
|
||||
}
|
||||
else if ((addFlags & afAC_LAST) == 0)
|
||||
{ // this is a high-priority candidate
|
||||
candidates.push_back(std::make_pair(100000, acctID));
|
||||
}
|
||||
}
|
||||
else if (acctID == mSrcAccountID)
|
||||
{
|
||||
// going back to the source is bad
|
||||
}
|
||||
else if ((addFlags & afAC_LAST) == 0)
|
||||
{ // save this candidate
|
||||
int out = getPathsOut(uEndCurrency, acctID, bIsEndCurrency, mDstAccountID);
|
||||
if (out)
|
||||
candidates.push_back(std::make_pair(out, acctID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) && (uEndAccount != mSrcAccountID)) // allow more paths from source
|
||||
count = 10;
|
||||
else if (count > 50)
|
||||
count = 50;
|
||||
|
||||
std::vector< std::pair<int, uint160> >::const_iterator it = candidates.begin();
|
||||
while (count-- != 0)
|
||||
{ // Add accounts to incompletePaths
|
||||
incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeAccount, it->second, uEndCurrency, it->second));
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog(lsWARNING, Pathfinder) << "Path ends on non-existent issuer";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (addFlags & afADD_BOOKS)
|
||||
{ // add order books
|
||||
if (addFlags & afOB_XRP)
|
||||
{ // to XRP only
|
||||
if (!bOnXRP && getApp().getOrderBookDB().isBookToXRP(uEndIssuer, uEndCurrency))
|
||||
{
|
||||
incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool bDestOnly = (addFlags & afOB_LAST) != 0;
|
||||
std::vector<OrderBook::pointer> books;
|
||||
getApp().getOrderBookDB().getBooksByTakerPays(uEndIssuer, uEndCurrency, books);
|
||||
WriteLog (lsTRACE, Pathfinder) << books.size() << " books found from this currency/issuer";
|
||||
BOOST_FOREACH(OrderBook::ref book, books)
|
||||
{
|
||||
if (!currentPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()) &&
|
||||
!matchesOrigin(book->getCurrencyOut(), book->getIssuerOut()) &&
|
||||
(!bDestOnly || (book->getCurrencyOut() == mDstAmount.getCurrency())))
|
||||
{
|
||||
STPath newPath(currentPath);
|
||||
|
||||
if (book->getCurrencyOut().isZero())
|
||||
{ // to XRP
|
||||
|
||||
// add the order book itself
|
||||
newPath.addElement(STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP));
|
||||
|
||||
if (mDstAmount.getCurrency().isZero())
|
||||
{ // destination is XRP, add account and path is complete
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found bx: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(newPath);
|
||||
}
|
||||
else
|
||||
incompletePaths.addPath(newPath);
|
||||
}
|
||||
else if (!currentPath.hasSeen(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{ // Don't want the book if we've already seen the issuer
|
||||
// add the order book itself
|
||||
newPath.addElement(STPathElement(STPathElement::typeCurrency | STPathElement::typeIssuer,
|
||||
ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()));
|
||||
|
||||
if ((book->getIssuerOut() == mDstAccountID) && book->getCurrencyOut() == mDstAmount.getCurrency())
|
||||
{ // with the destination account, this path is complete
|
||||
WriteLog (lsTRACE, Pathfinder) << "complete path found ba: " << currentPath.getJson(0);
|
||||
mCompletePaths.addUniquePath(newPath);
|
||||
}
|
||||
else
|
||||
{ // add issuer's account, path still incomplete
|
||||
incompletePaths.assembleAdd(newPath,
|
||||
STPathElement(STPathElement::typeAccount,
|
||||
book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<Pathfinder::PaymentType, Pathfinder::CostedPathList_t> Pathfinder::mPathTable;
|
||||
|
||||
Pathfinder::PathType_t Pathfinder::makePath(char const *string)
|
||||
{
|
||||
PathType_t ret;
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (*string++)
|
||||
{
|
||||
case 's': // source
|
||||
ret.push_back(nt_SOURCE);
|
||||
break;
|
||||
|
||||
case 'a': // accounts
|
||||
ret.push_back(nt_ACCOUNTS);
|
||||
break;
|
||||
|
||||
case 'b': // books
|
||||
ret.push_back(nt_BOOKS);
|
||||
break;
|
||||
|
||||
case 'x': // xrp book
|
||||
ret.push_back(nt_XRP_BOOK);
|
||||
break;
|
||||
|
||||
case 'f': // book to final currency
|
||||
ret.push_back(nt_DEST_BOOK);
|
||||
break;
|
||||
|
||||
case 'd': // destination (with account, if required and not already present)
|
||||
ret.push_back(nt_DESTINATION);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Pathfinder::pathTypeToString(PathType_t const& type)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
BOOST_FOREACH(NodeType const& node, type)
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case nt_SOURCE:
|
||||
ret.append("s");
|
||||
break;
|
||||
case nt_ACCOUNTS:
|
||||
ret.append("a");
|
||||
break;
|
||||
case nt_BOOKS:
|
||||
ret.append("b");
|
||||
break;
|
||||
case nt_XRP_BOOK:
|
||||
ret.append("x");
|
||||
break;
|
||||
case nt_DEST_BOOK:
|
||||
ret.append("f");
|
||||
break;
|
||||
case nt_DESTINATION:
|
||||
ret.append("d");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Costs:
|
||||
// 0 = minimum to make some payments possible
|
||||
// 1 = include trivial paths to make common cases work
|
||||
// 4 = normal fast search level
|
||||
// 7 = normal slow search level
|
||||
// 10 = most agressive
|
||||
|
||||
void Pathfinder::initPathTable()
|
||||
{ // CAUTION: Do not include rules that build default paths
|
||||
{ // XRP to XRP
|
||||
CostedPathList_t& list = mPathTable[pt_XRP_to_XRP];
|
||||
|
||||
list.push_back(CostedPath_t(8, makePath("sbxd"))); // source -> book -> book_to_XRP -> destination
|
||||
list.push_back(CostedPath_t(9, makePath("sbaxd"))); // source -> book -> gateway -> to_XRP ->destination
|
||||
}
|
||||
|
||||
{ // XRP to non-XRP
|
||||
CostedPathList_t& list = mPathTable[pt_XRP_to_nonXRP];
|
||||
|
||||
list.push_back(CostedPath_t(0, makePath("sfd"))); // source -> book -> gateway
|
||||
list.push_back(CostedPath_t(3, makePath("sfad"))); // source -> book -> account -> destination
|
||||
list.push_back(CostedPath_t(5, makePath("sfaad"))); // source -> book -> account -> account -> destination
|
||||
list.push_back(CostedPath_t(6, makePath("sbfd"))); // source -> book -> book -> destination
|
||||
list.push_back(CostedPath_t(8, makePath("sbafd"))); // source -> book -> account -> book -> destination
|
||||
list.push_back(CostedPath_t(9, makePath("sbfad"))); // source -> book -> book -> account -> destination
|
||||
list.push_back(CostedPath_t(10, makePath("sbafad")));
|
||||
}
|
||||
|
||||
{ // non-XRP to XRP
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_XRP];
|
||||
|
||||
list.push_back(CostedPath_t(0, makePath("sxd"))); // gateway buys XRP
|
||||
list.push_back(CostedPath_t(1, makePath("saxd"))); // source -> gateway -> book(XRP) -> dest
|
||||
list.push_back(CostedPath_t(6, makePath("saaxd")));
|
||||
list.push_back(CostedPath_t(7, makePath("sbxd")));
|
||||
list.push_back(CostedPath_t(8, makePath("sabxd")));
|
||||
list.push_back(CostedPath_t(9, makePath("sabaxd")));
|
||||
}
|
||||
|
||||
{ // non-XRP to non-XRP (same currency)
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_same];
|
||||
|
||||
list.push_back(CostedPath_t(1, makePath("sad"))); // source -> gateway -> destination
|
||||
list.push_back(CostedPath_t(1, makePath("sfd"))); // source -> book -> destination
|
||||
list.push_back(CostedPath_t(4, makePath("safd"))); // source -> gateway -> book -> destination
|
||||
list.push_back(CostedPath_t(4, makePath("sfad")));
|
||||
list.push_back(CostedPath_t(5, makePath("saad")));
|
||||
list.push_back(CostedPath_t(5, makePath("sxfd")));
|
||||
list.push_back(CostedPath_t(6, makePath("sxfad")));
|
||||
list.push_back(CostedPath_t(6, makePath("safad")));
|
||||
list.push_back(CostedPath_t(6, makePath("saxfd"))); // source -> gateway -> book to XRP -> book -> destination
|
||||
list.push_back(CostedPath_t(6, makePath("saxfad")));
|
||||
list.push_back(CostedPath_t(8, makePath("saaad")));
|
||||
}
|
||||
|
||||
{ // non-XRP to non-XRP (different currency)
|
||||
CostedPathList_t& list = mPathTable[pt_nonXRP_to_nonXRP];
|
||||
|
||||
list.push_back(CostedPath_t(1, makePath("sfad")));
|
||||
list.push_back(CostedPath_t(1, makePath("safd")));
|
||||
list.push_back(CostedPath_t(3, makePath("safad")));
|
||||
list.push_back(CostedPath_t(4, makePath("sxfd")));
|
||||
list.push_back(CostedPath_t(5, makePath("saxfd")));
|
||||
list.push_back(CostedPath_t(6, makePath("saxfad")));
|
||||
list.push_back(CostedPath_t(7, makePath("saafd")));
|
||||
list.push_back(CostedPath_t(8, makePath("saafad")));
|
||||
list.push_back(CostedPath_t(9, makePath("safaad")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
129
src/ripple_app/paths/ripple_Pathfinder.h
Normal file
129
src/ripple_app/paths/ripple_Pathfinder.h
Normal file
@@ -0,0 +1,129 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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);
|
||||
|
||||
static void initPathTable();
|
||||
bool findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& spsDst);
|
||||
|
||||
private:
|
||||
|
||||
enum PaymentType
|
||||
{
|
||||
pt_XRP_to_XRP,
|
||||
pt_XRP_to_nonXRP,
|
||||
pt_nonXRP_to_XRP,
|
||||
pt_nonXRP_to_same,
|
||||
pt_nonXRP_to_nonXRP
|
||||
};
|
||||
|
||||
enum NodeType
|
||||
{
|
||||
nt_SOURCE, // The source account with an issuer account, if required
|
||||
nt_ACCOUNTS, // Accounts that connect from this source/currency
|
||||
nt_BOOKS, // Order books that connect to this currency
|
||||
nt_XRP_BOOK, // The order book from this currency to XRP
|
||||
nt_DEST_BOOK, // The order book to the destination currency/issuer
|
||||
nt_DESTINATION // The destination account only
|
||||
};
|
||||
|
||||
typedef std::vector<NodeType> PathType_t;
|
||||
typedef std::pair<int, PathType_t> CostedPath_t;
|
||||
typedef std::vector<CostedPath_t> CostedPathList_t;
|
||||
|
||||
// returns true if any building paths are now complete?
|
||||
bool checkComplete (STPathSet& retPathSet);
|
||||
|
||||
static std::string pathTypeToString(PathType_t const&);
|
||||
|
||||
bool matchesOrigin (const uint160& currency, const uint160& issuer);
|
||||
|
||||
int getPathsOut (const uint160& currency, const uint160& accountID,
|
||||
bool isDestCurrency, const uint160& dest);
|
||||
|
||||
void addLink(const STPath& currentPath, STPathSet& incompletePaths, int addFlags);
|
||||
void addLink(const STPathSet& currentPaths, STPathSet& incompletePaths, int addFlags);
|
||||
STPathSet& getPaths(const PathType_t& type, bool addComplete = true);
|
||||
STPathSet filterPaths(int iMaxPaths);
|
||||
|
||||
// Our main table of paths
|
||||
|
||||
static std::map<PaymentType, CostedPathList_t> mPathTable;
|
||||
static PathType_t makePath(char const*);
|
||||
|
||||
uint160 mSrcAccountID;
|
||||
uint160 mDstAccountID;
|
||||
STAmount mDstAmount;
|
||||
uint160 mSrcCurrencyID;
|
||||
uint160 mSrcIssuerID;
|
||||
STAmount mSrcAmount;
|
||||
|
||||
Ledger::pointer mLedger;
|
||||
LoadEvent::pointer m_loadEvent;
|
||||
RippleLineCache::pointer mRLCache;
|
||||
|
||||
STPathElement mSource;
|
||||
STPathSet mCompletePaths;
|
||||
std::map< PathType_t, STPathSet > mPaths;
|
||||
|
||||
boost::unordered_map<uint160, AccountItems::pointer> mRLMap;
|
||||
boost::unordered_map<std::pair<uint160, uint160>, int> mPOMap;
|
||||
|
||||
static const uint32 afADD_ACCOUNTS = 0x001; // Add ripple paths
|
||||
static const uint32 afADD_BOOKS = 0x002; // Add order books
|
||||
static const uint32 afOB_XRP = 0x010; // Add order book to XRP only
|
||||
static const uint32 afOB_LAST = 0x040; // Must link to destination currency
|
||||
static const uint32 afAC_LAST = 0x080; // Destination account only
|
||||
};
|
||||
|
||||
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
|
||||
2670
src/ripple_app/paths/ripple_RippleCalc.cpp
Normal file
2670
src/ripple_app/paths/ripple_RippleCalc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
88
src/ripple_app/paths/ripple_RippleCalc.h
Normal file
88
src/ripple_app/paths/ripple_RippleCalc.h
Normal 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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user