mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Merge branch 'api2' of github.com:jedmccaleb/NewCoin into api2
Conflicts: src/cpp/ripple/TransactionAction.cpp src/cpp/ripple/TransactionEngine.cpp src/cpp/ripple/TransactionEngine.h
This commit is contained in:
119
src/cpp/ripple/AccountSetTransactor.cpp
Normal file
119
src/cpp/ripple/AccountSetTransactor.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "AccountSetTransactor.h"
|
||||
|
||||
TER AccountSetTransactor::doApply()
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet>";
|
||||
|
||||
//
|
||||
// EmailHash
|
||||
//
|
||||
|
||||
if (mTxn.isFieldPresent(sfEmailHash))
|
||||
{
|
||||
uint128 uHash = mTxn.getFieldH128(sfEmailHash);
|
||||
|
||||
if (!uHash)
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: unset email hash";
|
||||
|
||||
mTxnAccount->makeFieldAbsent(sfEmailHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: set email hash";
|
||||
|
||||
mTxnAccount->setFieldH128(sfEmailHash, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// WalletLocator
|
||||
//
|
||||
|
||||
if (mTxn.isFieldPresent(sfWalletLocator))
|
||||
{
|
||||
uint256 uHash = mTxn.getFieldH256(sfWalletLocator);
|
||||
|
||||
if (!uHash)
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: unset wallet locator";
|
||||
|
||||
mTxnAccount->makeFieldAbsent(sfEmailHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: set wallet locator";
|
||||
|
||||
mTxnAccount->setFieldH256(sfWalletLocator, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MessageKey
|
||||
//
|
||||
|
||||
if (!mTxn.isFieldPresent(sfMessageKey))
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: set message key";
|
||||
|
||||
mTxnAccount->setFieldVL(sfMessageKey, mTxn.getFieldVL(sfMessageKey));
|
||||
}
|
||||
|
||||
//
|
||||
// Domain
|
||||
//
|
||||
|
||||
if (mTxn.isFieldPresent(sfDomain))
|
||||
{
|
||||
std::vector<unsigned char> vucDomain = mTxn.getFieldVL(sfDomain);
|
||||
|
||||
if (vucDomain.empty())
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: unset domain";
|
||||
|
||||
mTxnAccount->makeFieldAbsent(sfDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: set domain";
|
||||
|
||||
mTxnAccount->setFieldVL(sfDomain, vucDomain);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// TransferRate
|
||||
//
|
||||
|
||||
if (mTxn.isFieldPresent(sfTransferRate))
|
||||
{
|
||||
uint32 uRate = mTxn.getFieldU32(sfTransferRate);
|
||||
|
||||
if (!uRate || uRate == QUALITY_ONE)
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: unset transfer rate";
|
||||
|
||||
mTxnAccount->makeFieldAbsent(sfTransferRate);
|
||||
}
|
||||
else if (uRate > QUALITY_ONE)
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: set transfer rate";
|
||||
|
||||
mTxnAccount->setFieldU32(sfTransferRate, uRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "doAccountSet: bad transfer rate";
|
||||
|
||||
return temBAD_TRANSFER_RATE;
|
||||
}
|
||||
}
|
||||
|
||||
Log(lsINFO) << "doAccountSet<";
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
9
src/cpp/ripple/AccountSetTransactor.h
Normal file
9
src/cpp/ripple/AccountSetTransactor.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
class AccountSetTransactor : public Transactor
|
||||
{
|
||||
public:
|
||||
AccountSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
@@ -17,13 +17,15 @@
|
||||
enum JobType
|
||||
{ // must be in priority order, low to high
|
||||
jtINVALID,
|
||||
jtVALIDATION_ut,
|
||||
jtTRANSACTION,
|
||||
jtPROPOSAL_ut,
|
||||
jtVALIDATION_t,
|
||||
jtTRANSACTION_l,
|
||||
jtPROPOSAL_t,
|
||||
jtADMIN,
|
||||
jtVALIDATION_ut, // A validation from an untrusted source
|
||||
jtCLIENTOP_ut, // A client operation from a non-local/untrusted source
|
||||
jtTRANSACTION, // A transaction received from the network
|
||||
jtPROPOSAL_ut, // A proposal from an untrusted source
|
||||
jtCLIENTOP_t, // A client operation from a trusted source
|
||||
jtVALIDATION_t, // A validation from a trusted source
|
||||
jtTRANSACTION_l, // A local transaction
|
||||
jtPROPOSAL_t, // A proposal from a trusted source
|
||||
jtADMIN, // An administrative operation
|
||||
jtDEATH, // job of death, used internally
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ DEFINE_INSTANCE(Ledger);
|
||||
class Ledger : public boost::enable_shared_from_this<Ledger>, public IS_INSTANCE(Ledger)
|
||||
{ // The basic Ledger structure, can be opened, closed, or synching
|
||||
friend class TransactionEngine;
|
||||
friend class Transactor;
|
||||
public:
|
||||
typedef boost::shared_ptr<Ledger> pointer;
|
||||
typedef const boost::shared_ptr<Ledger>& ref;
|
||||
|
||||
@@ -267,7 +267,7 @@ bool LCTransaction::updateVote(int percentTime, bool proposing)
|
||||
|
||||
LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previousLedger, uint32 closeTime)
|
||||
: mState(lcsPRE_CLOSE), mCloseTime(closeTime), mPrevLedgerHash(prevLCLHash), mPreviousLedger(previousLedger),
|
||||
mValPublic(theConfig.VALIDATION_PUB), mValPrivate(theConfig.VALIDATION_PRIV),
|
||||
mValPublic(theConfig.VALIDATION_PUB), mValPrivate(theConfig.VALIDATION_PRIV), mConsensusFail(false),
|
||||
mCurrentMSeconds(0), mClosePercent(0), mHaveCloseTimeConsensus(false),
|
||||
mConsensusStartTime(boost::posix_time::microsec_clock::universal_time())
|
||||
{
|
||||
@@ -305,6 +305,36 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previou
|
||||
}
|
||||
}
|
||||
|
||||
void LedgerConsensus::checkOurValidation()
|
||||
{ // This only covers some cases - Fix for the case where we can't ever acquire the consensus ledger
|
||||
if (!mHaveCorrectLCL || !mValPublic.isValid() || !mValPrivate.isValid())
|
||||
return;
|
||||
|
||||
SerializedValidation::pointer lastVal = theApp->getOPs().getLastValidation();
|
||||
if (lastVal)
|
||||
{
|
||||
if (lastVal->getFieldU32(sfLedgerSequence) == mPreviousLedger->getLedgerSeq())
|
||||
return;
|
||||
if (lastVal->getLedgerHash() == mPrevLedgerHash)
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 signingHash;
|
||||
SerializedValidation::pointer v = boost::make_shared<SerializedValidation>
|
||||
(mPreviousLedger->getHash(), theApp->getOPs().getValidationTimeNC(), mValPublic, false);
|
||||
v->setTrusted();
|
||||
v->sign(signingHash, mValPrivate);
|
||||
theApp->isNew(signingHash);
|
||||
theApp->getValidations().addValidation(v);
|
||||
std::vector<unsigned char> validation = v->getSigned();
|
||||
ripple::TMValidation val;
|
||||
val.set_validation(&validation[0], validation.size());
|
||||
theApp->getConnectionPool().relayMessage(NULL,
|
||||
boost::make_shared<PackedMessage>(val, ripple::mtVALIDATION));
|
||||
theApp->getOPs().setLastValidation(v);
|
||||
cLog(lsWARNING) << "Sending partial validation";
|
||||
}
|
||||
|
||||
void LedgerConsensus::checkLCL()
|
||||
{
|
||||
uint256 netLgr = mPrevLedgerHash;
|
||||
@@ -575,12 +605,13 @@ void LedgerConsensus::statePreClose()
|
||||
|
||||
void LedgerConsensus::closeLedger()
|
||||
{
|
||||
mState = lcsESTABLISH;
|
||||
mConsensusStartTime = boost::posix_time::microsec_clock::universal_time();
|
||||
mCloseTime = theApp->getOPs().getCloseTimeNC();
|
||||
theApp->getOPs().setLastCloseTime(mCloseTime);
|
||||
statusChange(ripple::neCLOSING_LEDGER, *mPreviousLedger);
|
||||
takeInitialPosition(*theApp->getMasterLedger().closeLedger(true));
|
||||
checkOurValidation();
|
||||
mState = lcsESTABLISH;
|
||||
mConsensusStartTime = boost::posix_time::microsec_clock::universal_time();
|
||||
mCloseTime = theApp->getOPs().getCloseTimeNC();
|
||||
theApp->getOPs().setLastCloseTime(mCloseTime);
|
||||
statusChange(ripple::neCLOSING_LEDGER, *mPreviousLedger);
|
||||
takeInitialPosition(*theApp->getMasterLedger().closeLedger(true));
|
||||
}
|
||||
|
||||
void LedgerConsensus::stateEstablish()
|
||||
@@ -769,7 +800,7 @@ bool LedgerConsensus::haveConsensus(bool forReal)
|
||||
cLog(lsDEBUG) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree;
|
||||
|
||||
return ContinuousLedgerTiming::haveConsensus(mPreviousProposers, agree + disagree, agree, currentValidations,
|
||||
mPreviousMSeconds, mCurrentMSeconds, forReal);
|
||||
mPreviousMSeconds, mCurrentMSeconds, forReal, mConsensusFail);
|
||||
}
|
||||
|
||||
SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool doAcquire)
|
||||
@@ -1193,15 +1224,17 @@ void LedgerConsensus::accept(SHAMap::ref set)
|
||||
}
|
||||
|
||||
statusChange(ripple::neACCEPTED_LEDGER, *newLCL);
|
||||
if (mValidating)
|
||||
if (mValidating && !mConsensusFail)
|
||||
{
|
||||
uint256 signingHash;
|
||||
SerializedValidation::pointer v = boost::make_shared<SerializedValidation>
|
||||
(newLCLHash, theApp->getOPs().getValidationTimeNC(), mValPublic, mValPrivate,
|
||||
mProposing, boost::ref(signingHash));
|
||||
(newLCLHash, theApp->getOPs().getValidationTimeNC(), mValPublic, mProposing);
|
||||
v->setFieldU32(sfLedgerSequence, newLCL->getLedgerSeq());
|
||||
v->sign(signingHash, mValPrivate);
|
||||
v->setTrusted();
|
||||
theApp->isNew(signingHash); // suppress it if we receive it
|
||||
theApp->getValidations().addValidation(v);
|
||||
theApp->getOPs().setLastValidation(v);
|
||||
std::vector<unsigned char> validation = v->getSigned();
|
||||
ripple::TMValidation val;
|
||||
val.set_validation(&validation[0], validation.size());
|
||||
@@ -1238,7 +1271,7 @@ void LedgerConsensus::accept(SHAMap::ref set)
|
||||
cLog(lsINFO) << "Applying transactions from current ledger";
|
||||
applyTransactions(theApp->getMasterLedger().getCurrentLedger()->peekTransactionMap(), newOL, newLCL,
|
||||
failedTransactions, true);
|
||||
theApp->getMasterLedger().pushLedger(newLCL, newOL, true);
|
||||
theApp->getMasterLedger().pushLedger(newLCL, newOL, !mConsensusFail);
|
||||
mNewLedgerHash = newLCL->getHash();
|
||||
mState = lcsACCEPTED;
|
||||
sl.unlock();
|
||||
|
||||
@@ -91,7 +91,7 @@ protected:
|
||||
LedgerAcquire::pointer mAcquiringLedger;
|
||||
LedgerProposal::pointer mOurPosition;
|
||||
RippleAddress mValPublic, mValPrivate;
|
||||
bool mProposing, mValidating, mHaveCorrectLCL;
|
||||
bool mProposing, mValidating, mHaveCorrectLCL, mConsensusFail;
|
||||
|
||||
int mCurrentMSeconds, mClosePercent, mCloseResolution;
|
||||
bool mHaveCloseTimeConsensus;
|
||||
@@ -148,6 +148,7 @@ protected:
|
||||
void playbackProposals();
|
||||
int getThreshold();
|
||||
void closeLedger();
|
||||
void checkOurValidation();
|
||||
|
||||
void beginAccept(bool synchronous);
|
||||
void endConsensus();
|
||||
|
||||
@@ -395,13 +395,14 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, TER result)
|
||||
|
||||
SLE::pointer origNode = mLedger->getSLE(it.first);
|
||||
SLE::pointer curNode = it.second.mEntry;
|
||||
uint16 nodeType = curNode ? curNode->getFieldU16(sfLedgerEntry) : origNode->getFieldU16(sfLedgerEntry);
|
||||
uint16 nodeType = curNode ? curNode->getFieldU16(sfLedgerEntryType) : origNode->getFieldU16(sfLedgerEntryType);
|
||||
|
||||
mSet.setAffectedNode(it.first, *type, nodeType);
|
||||
|
||||
if (type == &sfDeletedNode)
|
||||
{
|
||||
assert(origNode);
|
||||
assert(curNode);
|
||||
threadOwners(origNode, mLedger, newMod);
|
||||
|
||||
STObject finals(sfFinalFields);
|
||||
|
||||
@@ -63,7 +63,8 @@ bool ContinuousLedgerTiming::haveConsensus(
|
||||
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 forReal, // deciding whether to stop consensus process
|
||||
bool& failed) // we can't reach a consensus
|
||||
{
|
||||
cLog(lsTRACE) << boost::str(boost::format("CLC::haveConsensus: prop=%d/%d agree=%d validated=%d time=%d/%d%s") %
|
||||
currentProposers % previousProposers % currentAgree % currentFinished % currentAgreeTime % previousAgreeTime %
|
||||
@@ -85,13 +86,15 @@ bool ContinuousLedgerTiming::haveConsensus(
|
||||
if (((currentAgree * 100 + 100) / (currentProposers + 1)) > 80)
|
||||
{
|
||||
tLog(forReal, lsINFO) << "normal consensus";
|
||||
failed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If 50% of the nodes on your UNL have moved on, you should declare consensus
|
||||
if (((currentFinished * 100) / (currentProposers + 1)) > 50)
|
||||
// If 80% of the nodes on your UNL have moved on, you should declare consensus
|
||||
if (((currentFinished * 100) / (currentProposers + 1)) > 80)
|
||||
{
|
||||
tLog(forReal, lsWARNING) << "We see no consensus, but 50% of nodes have moved on";
|
||||
tLog(forReal, lsWARNING) << "We see no consensus, but 80% of nodes have moved on";
|
||||
failed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
int previousProposers, int currentProposers,
|
||||
int currentAgree, int currentClosed,
|
||||
int previousAgreeTime, int currentAgreeTime,
|
||||
bool forReal);
|
||||
bool forReal, bool& failed);
|
||||
|
||||
static int getNextLedgerTimeResolution(int previousResolution, bool previousAgree, int ledgerSeq);
|
||||
};
|
||||
|
||||
@@ -1006,8 +1006,8 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, const SerializedT
|
||||
ispListener->send(jvObj);
|
||||
}
|
||||
}
|
||||
|
||||
pubAccountTransaction(lpCurrent,stTxn,terResult,false,TransactionMetaSet::pointer());
|
||||
TransactionMetaSet::pointer ret;
|
||||
pubAccountTransaction(lpCurrent,stTxn,terResult,false,ret);
|
||||
}
|
||||
|
||||
void NetworkOPs::pubLedger(Ledger::ref lpAccepted)
|
||||
@@ -1168,11 +1168,10 @@ std::map<RippleAddress,bool> NetworkOPs::getAffectedAccounts(const SerializedTra
|
||||
const STAccount* sa = dynamic_cast<const STAccount*>(&it);
|
||||
if (sa)
|
||||
{
|
||||
bool found = false;
|
||||
RippleAddress na = sa->getValueNCA();
|
||||
accounts[na]=true;
|
||||
}else
|
||||
{
|
||||
{
|
||||
if( it.getFName() == sfLimitAmount )
|
||||
{
|
||||
const STAmount* amount = dynamic_cast<const STAmount*>(&it);
|
||||
|
||||
@@ -85,6 +85,8 @@ protected:
|
||||
uint256 mLastCloseHash;
|
||||
uint32 mLastCloseTime;
|
||||
uint32 mLastValidationTime;
|
||||
SerializedValidation::pointer mLastValidation;
|
||||
|
||||
|
||||
// XXX Split into more locks.
|
||||
boost::interprocess::interprocess_upgradable_mutex mMonitorLock;
|
||||
@@ -131,8 +133,10 @@ public:
|
||||
Ledger::pointer getLedgerByHash(const uint256& hash) { return mLedgerMaster->getLedgerByHash(hash); }
|
||||
Ledger::pointer getLedgerBySeq(const uint32 seq) { return mLedgerMaster->getLedgerBySeq(seq); }
|
||||
|
||||
uint256 getClosedLedgerHash()
|
||||
{ return mLedgerMaster->getClosedLedger()->getHash(); }
|
||||
uint256 getClosedLedgerHash() { return mLedgerMaster->getClosedLedger()->getHash(); }
|
||||
|
||||
SerializedValidation::ref getLastValidation() { return mLastValidation; }
|
||||
void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; }
|
||||
|
||||
SLE::pointer getSLE(Ledger::pointer lpLedger, const uint256& uHash) { return lpLedger->getSLE(uHash); }
|
||||
|
||||
|
||||
41
src/cpp/ripple/OfferCancelTransactor.cpp
Normal file
41
src/cpp/ripple/OfferCancelTransactor.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "OfferCancelTransactor.h"
|
||||
#include "Log.h"
|
||||
|
||||
TER OfferCancelTransactor::doApply()
|
||||
{
|
||||
TER terResult;
|
||||
const uint32 uOfferSequence = mTxn.getFieldU32(sfOfferSequence);
|
||||
const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence);
|
||||
|
||||
Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence;
|
||||
|
||||
if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence)
|
||||
{
|
||||
Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence;
|
||||
|
||||
terResult = temBAD_SEQUENCE;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence);
|
||||
SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex);
|
||||
|
||||
if (sleOffer)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence;
|
||||
|
||||
terResult = mEngine->getNodes().offerDelete(sleOffer, uOfferIndex, mTxnAccountID);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCancel: offer not found: "
|
||||
<< RippleAddress::createHumanAccountID(mTxnAccountID)
|
||||
<< " : " << uOfferSequence
|
||||
<< " : " << uOfferIndex.ToString();
|
||||
|
||||
terResult = tesSUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return terResult;
|
||||
}
|
||||
9
src/cpp/ripple/OfferCancelTransactor.h
Normal file
9
src/cpp/ripple/OfferCancelTransactor.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
class OfferCancelTransactor : public Transactor
|
||||
{
|
||||
public:
|
||||
OfferCancelTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
440
src/cpp/ripple/OfferCreateTransactor.cpp
Normal file
440
src/cpp/ripple/OfferCreateTransactor.cpp
Normal file
@@ -0,0 +1,440 @@
|
||||
#include "OfferCreateTransactor.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
// Take as much as possible. Adjusts account balances. Charges fees on top to taker.
|
||||
// --> uBookBase: The order book to take against.
|
||||
// --> saTakerPays: What the taker offers (w/ issuer)
|
||||
// --> saTakerGets: What the taker wanted (w/ issuer)
|
||||
// <-- saTakerPaid: What taker paid not including fees. To reduce an offer.
|
||||
// <-- saTakerGot: What taker got not including fees. To reduce an offer.
|
||||
// <-- terResult: tesSUCCESS or terNO_ACCOUNT
|
||||
// XXX: Fees should be paid by the source of the currency.
|
||||
TER OfferCreateTransactor::takeOffers(
|
||||
bool bPassive,
|
||||
const uint256& uBookBase,
|
||||
const uint160& uTakerAccountID,
|
||||
const SLE::pointer& sleTakerAccount,
|
||||
const STAmount& saTakerPays,
|
||||
const STAmount& saTakerGets,
|
||||
STAmount& saTakerPaid,
|
||||
STAmount& saTakerGot)
|
||||
{
|
||||
assert(saTakerPays && saTakerGets);
|
||||
|
||||
Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString();
|
||||
|
||||
uint256 uTipIndex = uBookBase;
|
||||
const uint256 uBookEnd = Ledger::getQualityNext(uBookBase);
|
||||
const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays);
|
||||
const uint160 uTakerPaysAccountID = saTakerPays.getIssuer();
|
||||
const uint160 uTakerGetsAccountID = saTakerGets.getIssuer();
|
||||
TER terResult = temUNCERTAIN;
|
||||
|
||||
boost::unordered_set<uint256> usOfferUnfundedFound; // Offers found unfunded.
|
||||
boost::unordered_set<uint256> usOfferUnfundedBecame; // Offers that became unfunded.
|
||||
boost::unordered_set<uint160> usAccountTouched; // Accounts touched.
|
||||
|
||||
saTakerPaid = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer());
|
||||
saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer());
|
||||
|
||||
while (temUNCERTAIN == terResult)
|
||||
{
|
||||
SLE::pointer sleOfferDir;
|
||||
uint64 uTipQuality;
|
||||
|
||||
// Figure out next offer to take, if needed.
|
||||
if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid)
|
||||
{
|
||||
// Taker, still, needs to get and pay.
|
||||
|
||||
sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->getNextLedgerIndex(uTipIndex, uBookEnd));
|
||||
if (sleOfferDir)
|
||||
{
|
||||
Log(lsINFO) << "takeOffers: possible counter offer found";
|
||||
|
||||
uTipIndex = sleOfferDir->getIndex();
|
||||
uTipQuality = Ledger::getQuality(uTipIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "takeOffers: counter offer book is empty: "
|
||||
<< uTipIndex.ToString()
|
||||
<< " ... "
|
||||
<< uBookEnd.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!sleOfferDir // No offer directory to take.
|
||||
|| uTakeQuality < uTipQuality // No offers of sufficient quality available.
|
||||
|| (bPassive && uTakeQuality == uTipQuality))
|
||||
{
|
||||
// Done.
|
||||
Log(lsINFO) << "takeOffers: done";
|
||||
|
||||
terResult = tesSUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Have an offer directory to consider.
|
||||
Log(lsINFO) << "takeOffers: considering dir: " << sleOfferDir->getJson(0);
|
||||
|
||||
SLE::pointer sleBookNode;
|
||||
unsigned int uBookEntry;
|
||||
uint256 uOfferIndex;
|
||||
|
||||
mEngine->getNodes().dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex);
|
||||
|
||||
SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex);
|
||||
|
||||
Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0);
|
||||
|
||||
const uint160 uOfferOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID();
|
||||
STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets);
|
||||
STAmount saOfferGets = sleOffer->getFieldAmount(sfTakerPays);
|
||||
|
||||
if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(sfExpiration) <= mEngine->getLedger()->getParentCloseTimeNC())
|
||||
{
|
||||
// Offer is expired. Expired offers are considered unfunded. Delete it.
|
||||
Log(lsINFO) << "takeOffers: encountered expired offer";
|
||||
|
||||
usOfferUnfundedFound.insert(uOfferIndex);
|
||||
}
|
||||
else if (uOfferOwnerID == uTakerAccountID)
|
||||
{
|
||||
// Would take own offer. Consider old offer expired. Delete it.
|
||||
Log(lsINFO) << "takeOffers: encountered taker's own old offer";
|
||||
|
||||
usOfferUnfundedFound.insert(uOfferIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get offer funds available.
|
||||
|
||||
Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText();
|
||||
|
||||
STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays);
|
||||
STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays);
|
||||
SLE::pointer sleOfferAccount; // Owner of offer.
|
||||
|
||||
if (!saOfferFunds.isPositive())
|
||||
{
|
||||
// Offer is unfunded, possibly due to previous balance action.
|
||||
Log(lsINFO) << "takeOffers: offer unfunded: delete";
|
||||
|
||||
boost::unordered_set<uint160>::iterator account = usAccountTouched.find(uOfferOwnerID);
|
||||
if (account != usAccountTouched.end())
|
||||
{
|
||||
// Previously touched account.
|
||||
usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Never touched source account.
|
||||
usOfferUnfundedFound.insert(uOfferIndex); // Delete found unfunded offer when possible.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
STAmount saPay = saTakerPays - saTakerPaid;
|
||||
if (saTakerFunds < saPay)
|
||||
saPay = saTakerFunds;
|
||||
STAmount saSubTakerPaid;
|
||||
STAmount saSubTakerGot;
|
||||
STAmount saTakerIssuerFee;
|
||||
STAmount saOfferIssuerFee;
|
||||
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText();
|
||||
|
||||
bool bOfferDelete = STAmount::applyOffer(
|
||||
mEngine->getNodes().rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID),
|
||||
mEngine->getNodes().rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
|
||||
saOfferFunds,
|
||||
saPay, // Driver XXX need to account for fees.
|
||||
saOfferPays,
|
||||
saOfferGets,
|
||||
saTakerPays,
|
||||
saTakerGets,
|
||||
saSubTakerPaid,
|
||||
saSubTakerGot,
|
||||
saTakerIssuerFee,
|
||||
saOfferIssuerFee);
|
||||
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText();
|
||||
Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText();
|
||||
|
||||
// Adjust offer
|
||||
|
||||
// Offer owner will pay less. Subtract what taker just got.
|
||||
sleOffer->setFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot);
|
||||
|
||||
// Offer owner will get less. Subtract what owner just paid.
|
||||
sleOffer->setFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid);
|
||||
|
||||
mEngine->entryModify(sleOffer);
|
||||
|
||||
if (bOfferDelete)
|
||||
{
|
||||
// Offer now fully claimed or now unfunded.
|
||||
Log(lsINFO) << "takeOffers: offer claimed: delete";
|
||||
|
||||
usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success.
|
||||
|
||||
// Offer owner's account is no longer pristine.
|
||||
usAccountTouched.insert(uOfferOwnerID);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lsINFO) << "takeOffers: offer partial claim.";
|
||||
}
|
||||
|
||||
// Offer owner pays taker.
|
||||
// saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier?
|
||||
assert(!!saSubTakerGot.getIssuer());
|
||||
|
||||
mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot);
|
||||
mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee);
|
||||
|
||||
saTakerGot += saSubTakerGot;
|
||||
|
||||
// Taker pays offer owner.
|
||||
// saSubTakerPaid.setIssuer(uTakerPaysAccountID);
|
||||
assert(!!saSubTakerPaid.getIssuer());
|
||||
|
||||
mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid);
|
||||
mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee);
|
||||
|
||||
saTakerPaid += saSubTakerPaid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On storing meta data, delete offers that were found unfunded to prevent encountering them in future.
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound)
|
||||
{
|
||||
terResult = mEngine->getNodes().offerDelete(uOfferIndex);
|
||||
if (tesSUCCESS != terResult)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
// On success, delete offers that became unfunded.
|
||||
BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame)
|
||||
{
|
||||
terResult = mEngine->getNodes().offerDelete(uOfferIndex);
|
||||
if (tesSUCCESS != terResult)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return terResult;
|
||||
}
|
||||
|
||||
TER OfferCreateTransactor::doApply()
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate> " << mTxn.getJson(0);
|
||||
const uint32 txFlags = mTxn.getFlags();
|
||||
const bool bPassive = isSetBit(txFlags, tfPassive);
|
||||
STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays);
|
||||
STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets);
|
||||
|
||||
Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s")
|
||||
% saTakerPays.getFullText()
|
||||
% saTakerGets.getFullText());
|
||||
|
||||
const uint160 uPaysIssuerID = saTakerPays.getIssuer();
|
||||
const uint160 uGetsIssuerID = saTakerGets.getIssuer();
|
||||
const uint32 uExpiration = mTxn.getFieldU32(sfExpiration);
|
||||
const bool bHaveExpiration = mTxn.isFieldPresent(sfExpiration);
|
||||
const uint32 uSequence = mTxn.getSequence();
|
||||
|
||||
const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence);
|
||||
SLE::pointer sleOffer = mEngine->entryCreate(ltOFFER, uLedgerIndex);
|
||||
|
||||
Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence;
|
||||
|
||||
const uint160 uPaysCurrency = saTakerPays.getCurrency();
|
||||
const uint160 uGetsCurrency = saTakerGets.getCurrency();
|
||||
const uint64 uRate = STAmount::getRate(saTakerGets, saTakerPays);
|
||||
|
||||
TER terResult = tesSUCCESS;
|
||||
uint256 uDirectory; // Delete hints.
|
||||
uint64 uOwnerNode;
|
||||
uint64 uBookNode;
|
||||
|
||||
if (bHaveExpiration && !uExpiration)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration";
|
||||
|
||||
terResult = temBAD_EXPIRATION;
|
||||
}
|
||||
else if (bHaveExpiration && mEngine->getLedger()->getParentCloseTimeNC() >= uExpiration)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired";
|
||||
|
||||
// XXX CHARGE FEE ONLY.
|
||||
terResult = tesSUCCESS;
|
||||
}
|
||||
else if (saTakerPays.isNative() && saTakerGets.isNative())
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Malformed offer: XRP for XRP";
|
||||
|
||||
terResult = temBAD_OFFER;
|
||||
}
|
||||
else if (!saTakerPays.isPositive() || !saTakerGets.isPositive())
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount";
|
||||
|
||||
terResult = temBAD_OFFER;
|
||||
}
|
||||
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer";
|
||||
|
||||
terResult = temREDUNDANT;
|
||||
}
|
||||
else if (saTakerPays.isNative() != !uPaysIssuerID || saTakerGets.isNative() != !uGetsIssuerID)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer";
|
||||
|
||||
terResult = temBAD_ISSUER;
|
||||
}
|
||||
else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive())
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded.";
|
||||
|
||||
terResult = terUNFUNDED;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult && !saTakerPays.isNative())
|
||||
{
|
||||
SLE::pointer sleTakerPays = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID));
|
||||
|
||||
if (!sleTakerPays)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID(uPaysIssuerID);
|
||||
|
||||
terResult = terNO_ACCOUNT;
|
||||
}
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
STAmount saOfferPaid;
|
||||
STAmount saOfferGot;
|
||||
const uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID);
|
||||
|
||||
Log(lsINFO) << boost::str(boost::format("doOfferCreate: take against book: %s for %s -> %s")
|
||||
% uTakeBookBase.ToString()
|
||||
% saTakerGets.getFullText()
|
||||
% saTakerPays.getFullText());
|
||||
|
||||
// Take using the parameters of the offer.
|
||||
#if 1
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText();
|
||||
terResult = takeOffers(
|
||||
bPassive,
|
||||
uTakeBookBase,
|
||||
mTxnAccountID,
|
||||
mTxnAccount,
|
||||
saTakerGets,
|
||||
saTakerPays,
|
||||
saOfferPaid, // How much was spent.
|
||||
saOfferGot // How much was got.
|
||||
);
|
||||
#else
|
||||
terResult = tesSUCCESS;
|
||||
#endif
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult;
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText();
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText();
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText();
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText();
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got.
|
||||
saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid.
|
||||
}
|
||||
}
|
||||
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText();
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText();
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID);
|
||||
Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText();
|
||||
|
||||
// Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID);
|
||||
// Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID);
|
||||
|
||||
if (tesSUCCESS == terResult
|
||||
&& saTakerPays // Still wanting something.
|
||||
&& saTakerGets // Still offering something.
|
||||
&& mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded.
|
||||
{
|
||||
// We need to place the remainder of the offer into its order book.
|
||||
Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s")
|
||||
% saTakerPays.getFullText()
|
||||
% saTakerGets.getFullText());
|
||||
|
||||
// Add offer to owner's directory.
|
||||
terResult = mEngine->getNodes().dirAdd(uOwnerNode, Ledger::getOwnerDirIndex(mTxnAccountID), uLedgerIndex);
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID);
|
||||
|
||||
Log(lsINFO) << boost::str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s")
|
||||
% uBookBase.ToString()
|
||||
% saTakerPays.getHumanCurrency()
|
||||
% RippleAddress::createHumanAccountID(saTakerPays.getIssuer())
|
||||
% saTakerGets.getHumanCurrency()
|
||||
% RippleAddress::createHumanAccountID(saTakerGets.getIssuer()));
|
||||
|
||||
uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate.
|
||||
|
||||
// Add offer to order book.
|
||||
terResult = mEngine->getNodes().dirAdd(uBookNode, uDirectory, uLedgerIndex);
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
Log(lsWARNING) << "doOfferCreate: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID);
|
||||
Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID);
|
||||
Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID);
|
||||
Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative();
|
||||
Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative();
|
||||
Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency();
|
||||
Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency();
|
||||
|
||||
sleOffer->setFieldAccount(sfAccount, mTxnAccountID);
|
||||
sleOffer->setFieldU32(sfSequence, uSequence);
|
||||
sleOffer->setFieldH256(sfBookDirectory, uDirectory);
|
||||
sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
|
||||
sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
|
||||
sleOffer->setFieldU64(sfOwnerNode, uOwnerNode);
|
||||
sleOffer->setFieldU64(sfBookNode, uBookNode);
|
||||
|
||||
if (uExpiration)
|
||||
sleOffer->setFieldU32(sfExpiration, uExpiration);
|
||||
|
||||
if (bPassive)
|
||||
sleOffer->setFlag(lsfPassive);
|
||||
}
|
||||
}
|
||||
|
||||
Log(lsINFO) << "doOfferCreate: final sleOffer=" << sleOffer->getJson(0);
|
||||
|
||||
return terResult;
|
||||
}
|
||||
22
src/cpp/ripple/OfferCreateTransactor.h
Normal file
22
src/cpp/ripple/OfferCreateTransactor.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
|
||||
class OfferCreateTransactor : public Transactor
|
||||
{
|
||||
TER takeOffers(
|
||||
bool bPassive,
|
||||
const uint256& uBookBase,
|
||||
const uint160& uTakerAccountID,
|
||||
const SLE::pointer& sleTakerAccount,
|
||||
const STAmount& saTakerPays,
|
||||
const STAmount& saTakerGets,
|
||||
STAmount& saTakerPaid,
|
||||
STAmount& saTakerGot);
|
||||
|
||||
public:
|
||||
OfferCreateTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
|
||||
|
||||
168
src/cpp/ripple/PaymentTransactor.cpp
Normal file
168
src/cpp/ripple/PaymentTransactor.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "PaymentTransactor.h"
|
||||
#include "Config.h"
|
||||
#include "RippleCalc.h"
|
||||
|
||||
#define RIPPLE_PATHS_MAX 3
|
||||
|
||||
// TODO: only have the higher fee if the account doesn't in fact exist
|
||||
void PaymentTransactor::calculateFee()
|
||||
{
|
||||
if (mTxn.getFlags() & tfCreateAccount)
|
||||
{
|
||||
mFeeDue = theConfig.FEE_ACCOUNT_CREATE;
|
||||
}else Transactor::calculateFee();
|
||||
}
|
||||
|
||||
TER PaymentTransactor::doApply()
|
||||
{
|
||||
// Ripple if source or destination is non-native or if there are paths.
|
||||
const uint32 uTxFlags = mTxn.getFlags();
|
||||
const bool bCreate = isSetBit(uTxFlags, tfCreateAccount);
|
||||
const bool bPartialPayment = isSetBit(uTxFlags, tfPartialPayment);
|
||||
const bool bLimitQuality = isSetBit(uTxFlags, tfLimitQuality);
|
||||
const bool bNoRippleDirect = isSetBit(uTxFlags, tfNoRippleDirect);
|
||||
const bool bPaths = mTxn.isFieldPresent(sfPaths);
|
||||
const bool bMax = mTxn.isFieldPresent(sfSendMax);
|
||||
const uint160 uDstAccountID = mTxn.getFieldAccount160(sfDestination);
|
||||
const STAmount saDstAmount = mTxn.getFieldAmount(sfAmount);
|
||||
const STAmount saMaxAmount = bMax
|
||||
? mTxn.getFieldAmount(sfSendMax)
|
||||
: saDstAmount.isNative()
|
||||
? saDstAmount
|
||||
: STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative());
|
||||
const uint160 uSrcCurrency = saMaxAmount.getCurrency();
|
||||
const uint160 uDstCurrency = saDstAmount.getCurrency();
|
||||
|
||||
Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s")
|
||||
% saMaxAmount.getFullText()
|
||||
% saDstAmount.getFullText());
|
||||
|
||||
if (!uDstAccountID)
|
||||
{
|
||||
Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specified.";
|
||||
|
||||
return temDST_NEEDED;
|
||||
}
|
||||
else if (bMax && !saMaxAmount.isPositive())
|
||||
{
|
||||
Log(lsINFO) << "doPayment: Invalid transaction: bad max amount: " << saMaxAmount.getFullText();
|
||||
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
else if (!saDstAmount.isPositive())
|
||||
{
|
||||
Log(lsINFO) << "doPayment: Invalid transaction: bad dst amount: " << saDstAmount.getFullText();
|
||||
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths)
|
||||
{
|
||||
Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redundant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s")
|
||||
% mTxnAccountID.ToString()
|
||||
% uDstAccountID.ToString()
|
||||
% uSrcCurrency.ToString()
|
||||
% uDstCurrency.ToString());
|
||||
|
||||
return temREDUNDANT;
|
||||
}
|
||||
else if (bMax
|
||||
&& ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency())
|
||||
|| (saDstAmount.isNative() && saMaxAmount.isNative())))
|
||||
{
|
||||
Log(lsINFO) << "doPayment: Invalid transaction: bad SendMax.";
|
||||
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID));
|
||||
if (!sleDst)
|
||||
{
|
||||
// Destination account does not exist.
|
||||
if (bCreate && !saDstAmount.isNative())
|
||||
{
|
||||
// This restriction could be relaxed.
|
||||
Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XRP.";
|
||||
|
||||
return temCREATEXRP;
|
||||
}
|
||||
else if (!bCreate)
|
||||
{
|
||||
Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist.";
|
||||
|
||||
return terNO_DST;
|
||||
}
|
||||
|
||||
// Create the account.
|
||||
sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID));
|
||||
|
||||
sleDst->setFieldAccount(sfAccount, uDstAccountID);
|
||||
sleDst->setFieldU32(sfSequence, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mEngine->entryModify(sleDst);
|
||||
}
|
||||
|
||||
TER terResult;
|
||||
// XXX Should bMax be sufficient to imply ripple?
|
||||
const bool bRipple = bPaths || bMax || !saDstAmount.isNative();
|
||||
|
||||
if (bRipple)
|
||||
{
|
||||
// Ripple payment
|
||||
|
||||
STPathSet spsPaths = mTxn.getFieldPathSet(sfPaths);
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
|
||||
terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX
|
||||
? telBAD_PATH_COUNT
|
||||
: RippleCalc::rippleCalc(
|
||||
mEngine->getNodes(),
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
uDstAccountID,
|
||||
mTxnAccountID,
|
||||
spsPaths,
|
||||
bPartialPayment,
|
||||
bLimitQuality,
|
||||
bNoRippleDirect);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direct XRP payment.
|
||||
|
||||
STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance);
|
||||
|
||||
if (saSrcXRPBalance < saDstAmount)
|
||||
{
|
||||
// Transaction might succeed, if applied in a different order.
|
||||
Log(lsINFO) << "doPayment: Delay transaction: Insufficient funds.";
|
||||
|
||||
terResult = terUNFUNDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount);
|
||||
sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount);
|
||||
|
||||
terResult = tesSUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
std::string strToken;
|
||||
std::string strHuman;
|
||||
|
||||
if (transResultInfo(terResult, strToken, strHuman))
|
||||
{
|
||||
Log(lsINFO) << boost::str(boost::format("doPayment: %s: %s") % strToken % strHuman);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return terResult;
|
||||
}
|
||||
11
src/cpp/ripple/PaymentTransactor.h
Normal file
11
src/cpp/ripple/PaymentTransactor.h
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
#include "Transactor.h"
|
||||
|
||||
class PaymentTransactor : public Transactor
|
||||
{
|
||||
void calculateFee();
|
||||
public:
|
||||
PaymentTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
@@ -1312,13 +1312,15 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param
|
||||
cLog(lsTRACE) << "RPC:" << command;
|
||||
cLog(lsTRACE) << "RPC params:" << params;
|
||||
|
||||
mRole = role;
|
||||
|
||||
static struct {
|
||||
const char* pCommand;
|
||||
doFuncPtr dfpFunc;
|
||||
int iMinParams;
|
||||
int iMaxParams;
|
||||
bool mAdminRequired;
|
||||
bool mEvented;
|
||||
bool bAdminRequired;
|
||||
bool bEvented;
|
||||
unsigned int iOptions;
|
||||
} commandsA[] = {
|
||||
// Request-response methods
|
||||
@@ -1335,6 +1337,7 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param
|
||||
{ "ledger_closed", &RPCHandler::doLedgerClosed, 0, 0, false, false, optClosed },
|
||||
{ "ledger_current", &RPCHandler::doLedgerCurrent, 0, 0, false, false, optCurrent },
|
||||
{ "ledger_entry", &RPCHandler::doLedgerEntry, -1, -1, false, false, optCurrent },
|
||||
{ "ledger_header", &RPCHandler::doLedgerHeader, -1, -1, false, false, optCurrent },
|
||||
{ "log_level", &RPCHandler::doLogLevel, 0, 2, true },
|
||||
{ "logrotate", &RPCHandler::doLogRotate, 0, 0, true },
|
||||
{ "nickname_info", &RPCHandler::doNicknameInfo, 1, 1, false, false, optCurrent },
|
||||
@@ -1380,11 +1383,11 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param
|
||||
{
|
||||
return rpcError(rpcUNKNOWN_COMMAND);
|
||||
}
|
||||
else if (commandsA[i].mAdminRequired && role != ADMIN)
|
||||
else if (commandsA[i].bAdminRequired && mRole != ADMIN)
|
||||
{
|
||||
return rpcError(rpcNO_PERMISSION);
|
||||
}
|
||||
else if (commandsA[i].mEvented && mInfoSub == NULL)
|
||||
else if (commandsA[i].bEvented && mInfoSub == NULL)
|
||||
{
|
||||
return rpcError(rpcNO_EVENTS);
|
||||
}
|
||||
@@ -1705,15 +1708,13 @@ Json::Value RPCHandler::doTransactionEntry(const Json::Value& jvRequest)
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest)
|
||||
Json::Value RPCHandler::lookupLedger(const Json::Value& jvRequest, Ledger::pointer& lpLedger)
|
||||
{
|
||||
Json::Value jvResult;
|
||||
|
||||
uint256 uLedger = jvRequest.isMember("ledger_hash") ? uint256(jvRequest["ledger_hash"].asString()) : 0;
|
||||
uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0;
|
||||
|
||||
Ledger::pointer lpLedger;
|
||||
|
||||
if (!!uLedger)
|
||||
{
|
||||
// Ledger directly specified.
|
||||
@@ -1756,6 +1757,17 @@ Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest)
|
||||
jvResult["ledger_current_index"] = uLedgerIndex;
|
||||
}
|
||||
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest)
|
||||
{
|
||||
Ledger::pointer lpLedger;
|
||||
Json::Value jvResult = lookupLedger(jvRequest, lpLedger);
|
||||
|
||||
if (!lpLedger)
|
||||
return jvResult;
|
||||
|
||||
uint256 uNodeIndex;
|
||||
bool bNodeBinary = false;
|
||||
|
||||
@@ -1951,6 +1963,25 @@ Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest)
|
||||
return jvResult;
|
||||
}
|
||||
|
||||
Json::Value RPCHandler::doLedgerHeader(const Json::Value& jvRequest)
|
||||
{
|
||||
Ledger::pointer lpLedger;
|
||||
Json::Value jvResult = lookupLedger(jvRequest, lpLedger);
|
||||
|
||||
if (!lpLedger)
|
||||
return jvResult;
|
||||
|
||||
Serializer s;
|
||||
|
||||
lpLedger->addRaw(s);
|
||||
|
||||
jvResult["ledger_data"] = strHex(s.peekData());
|
||||
|
||||
if (mRole == ADMIN)
|
||||
lpLedger->addJson(jvResult, 0);
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
boost::unordered_set<RippleAddress> RPCHandler::parseAccountIds(const Json::Value& jvArray)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ class RPCHandler
|
||||
{
|
||||
NetworkOPs* mNetOps;
|
||||
InfoSub* mInfoSub;
|
||||
int mRole;
|
||||
|
||||
typedef Json::Value (RPCHandler::*doFuncPtr)(const Json::Value ¶ms);
|
||||
enum {
|
||||
@@ -22,6 +23,8 @@ class RPCHandler
|
||||
int getParamCount(const Json::Value& params);
|
||||
bool extractString(std::string& param, const Json::Value& params, int index);
|
||||
|
||||
Json::Value lookupLedger(const Json::Value& jvRequest, Ledger::pointer& lpLedger);
|
||||
|
||||
Json::Value getMasterGenerator(const uint256& uLedger, const RippleAddress& naRegularSeed, RippleAddress& naMasterGenerator);
|
||||
Json::Value authorize(const uint256& uLedger, const RippleAddress& naRegularSeed, const RippleAddress& naSrcAccountID,
|
||||
RippleAddress& naAccountPublic, RippleAddress& naAccountPrivate,
|
||||
@@ -86,6 +89,7 @@ class RPCHandler
|
||||
Json::Value doLedgerClosed(const Json::Value& params);
|
||||
Json::Value doLedgerCurrent(const Json::Value& params);
|
||||
Json::Value doLedgerEntry(const Json::Value& params);
|
||||
Json::Value doLedgerHeader(const Json::Value& params);
|
||||
Json::Value doTransactionEntry(const Json::Value& params);
|
||||
|
||||
Json::Value doSubscribe(const Json::Value& params);
|
||||
|
||||
51
src/cpp/ripple/RegularKeySetTransactor.cpp
Normal file
51
src/cpp/ripple/RegularKeySetTransactor.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "RegularKeySetTransactor.h"
|
||||
#include "Log.h"
|
||||
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
// TODO:
|
||||
TER RegularKeySetTransactor::checkSig()
|
||||
{
|
||||
// Transaction's signing public key must be for the source account.
|
||||
// To prove the master private key made this transaction.
|
||||
if (mSigningPubKey.getAccountID() != mTxnAccountID)
|
||||
{
|
||||
// Signing Pub Key must be for Source Account ID.
|
||||
cLog(lsWARNING) << "sourceAccountID: " << mSigningPubKey.humanAccountID();
|
||||
cLog(lsWARNING) << "txn accountID: " << mTxn.getSourceAccount().humanAccountID();
|
||||
|
||||
return temBAD_SET_ID;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// TODO: this should be default fee if flag isn't set
|
||||
void RegularKeySetTransactor::calculateFee()
|
||||
{
|
||||
mFeeDue = 0;
|
||||
}
|
||||
|
||||
|
||||
// TODO: change to take a fee if there is one there
|
||||
TER RegularKeySetTransactor::doApply()
|
||||
{
|
||||
std::cerr << "doRegularKeySet>" << std::endl;
|
||||
|
||||
if (mTxnAccount->getFlags() & lsfPasswordSpent)
|
||||
{
|
||||
std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl;
|
||||
|
||||
return terFUNDS_SPENT;
|
||||
}
|
||||
|
||||
mTxnAccount->setFlag(lsfPasswordSpent);
|
||||
|
||||
uint160 uAuthKeyID=mTxn.getFieldAccount160(sfAuthorizedKey);
|
||||
mTxnAccount->setFieldAccount(sfAuthorizedKey, uAuthKeyID);
|
||||
|
||||
|
||||
std::cerr << "doRegularKeySet<" << std::endl;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
11
src/cpp/ripple/RegularKeySetTransactor.h
Normal file
11
src/cpp/ripple/RegularKeySetTransactor.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
class RegularKeySetTransactor : public Transactor
|
||||
{
|
||||
void calculateFee();
|
||||
public:
|
||||
RegularKeySetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
TER checkFee();
|
||||
TER checkSig();
|
||||
TER doApply();
|
||||
};
|
||||
@@ -115,7 +115,7 @@ uint160 RippleAddress::getNodeID() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getNodeID");
|
||||
|
||||
case VER_NODE_PUBLIC:
|
||||
// Note, we are encoding the left.
|
||||
@@ -129,7 +129,7 @@ const std::vector<unsigned char>& RippleAddress::getNodePublic() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getNodePublic");
|
||||
|
||||
case VER_NODE_PUBLIC:
|
||||
return vchData;
|
||||
@@ -143,7 +143,7 @@ std::string RippleAddress::humanNodePublic() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanNodePublic");
|
||||
|
||||
case VER_NODE_PUBLIC:
|
||||
return ToString();
|
||||
@@ -209,7 +209,7 @@ const std::vector<unsigned char>& RippleAddress::getNodePrivateData() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getNodePrivateData");
|
||||
|
||||
case VER_NODE_PRIVATE:
|
||||
return vchData;
|
||||
@@ -223,7 +223,7 @@ uint256 RippleAddress::getNodePrivate() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source = getNodePrivate");
|
||||
|
||||
case VER_NODE_PRIVATE:
|
||||
return uint256(vchData);
|
||||
@@ -237,7 +237,7 @@ std::string RippleAddress::humanNodePrivate() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanNodePrivate");
|
||||
|
||||
case VER_NODE_PRIVATE:
|
||||
return ToString();
|
||||
@@ -279,7 +279,7 @@ uint160 RippleAddress::getAccountID() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getAccountID");
|
||||
|
||||
case VER_ACCOUNT_ID:
|
||||
return uint160(vchData);
|
||||
@@ -297,7 +297,7 @@ std::string RippleAddress::humanAccountID() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanAccountID");
|
||||
|
||||
case VER_ACCOUNT_ID:
|
||||
return ToString();
|
||||
@@ -353,7 +353,7 @@ const std::vector<unsigned char>& RippleAddress::getAccountPublic() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getAccountPublic");
|
||||
|
||||
case VER_ACCOUNT_ID:
|
||||
throw std::runtime_error("public not available from account id");
|
||||
@@ -371,7 +371,7 @@ std::string RippleAddress::humanAccountPublic() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanAccountPublic");
|
||||
|
||||
case VER_ACCOUNT_ID:
|
||||
throw std::runtime_error("public not available from account id");
|
||||
@@ -446,7 +446,7 @@ uint256 RippleAddress::getAccountPrivate() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getAccountPrivate");
|
||||
|
||||
case VER_ACCOUNT_PRIVATE:
|
||||
return uint256(vchData);
|
||||
@@ -460,7 +460,7 @@ std::string RippleAddress::humanAccountPrivate() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanAccountPrivate");
|
||||
|
||||
case VER_ACCOUNT_PRIVATE:
|
||||
return ToString();
|
||||
@@ -606,7 +606,7 @@ BIGNUM* RippleAddress::getGeneratorBN() const
|
||||
{ // returns the public generator
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getGeneratorBN");
|
||||
|
||||
case VER_FAMILY_GENERATOR:
|
||||
// Do nothing.
|
||||
@@ -625,7 +625,7 @@ const std::vector<unsigned char>& RippleAddress::getGenerator() const
|
||||
{ // returns the public generator
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getGenerator");
|
||||
|
||||
case VER_FAMILY_GENERATOR:
|
||||
// Do nothing.
|
||||
@@ -640,7 +640,7 @@ std::string RippleAddress::humanGenerator() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanGenerator");
|
||||
|
||||
case VER_FAMILY_GENERATOR:
|
||||
return ToString();
|
||||
@@ -678,7 +678,7 @@ uint128 RippleAddress::getSeed() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - getSeed");
|
||||
|
||||
case VER_FAMILY_SEED:
|
||||
return uint128(vchData);
|
||||
@@ -692,7 +692,7 @@ std::string RippleAddress::humanSeed1751() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanSeed1751");
|
||||
|
||||
case VER_FAMILY_SEED:
|
||||
{
|
||||
@@ -719,7 +719,7 @@ std::string RippleAddress::humanSeed() const
|
||||
{
|
||||
switch (nVersion) {
|
||||
case VER_NONE:
|
||||
throw std::runtime_error("unset source");
|
||||
throw std::runtime_error("unset source - humanSeed");
|
||||
|
||||
case VER_FAMILY_SEED:
|
||||
return ToString();
|
||||
|
||||
@@ -39,26 +39,32 @@ SerializedValidation::SerializedValidation(SerializerIterator& sit, bool checkSi
|
||||
}
|
||||
|
||||
SerializedValidation::SerializedValidation(const uint256& ledgerHash, uint32 signTime,
|
||||
const RippleAddress& naPub, const RippleAddress& naPriv, bool isFull, uint256& signingHash)
|
||||
const RippleAddress& raPub, bool isFull)
|
||||
: STObject(sValidationFormat, sfValidation), mTrusted(false)
|
||||
{
|
||||
{ // Does not sign
|
||||
setFieldH256(sfLedgerHash, ledgerHash);
|
||||
setFieldU32(sfSigningTime, signTime);
|
||||
|
||||
setFieldVL(sfSigningPubKey, naPub.getNodePublic());
|
||||
mNodeID = naPub.getNodeID();
|
||||
setFieldVL(sfSigningPubKey, raPub.getNodePublic());
|
||||
mNodeID = raPub.getNodeID();
|
||||
assert(mNodeID.isNonZero());
|
||||
|
||||
if (!isFull)
|
||||
setFlag(sFullFlag);
|
||||
}
|
||||
|
||||
void SerializedValidation::sign(const RippleAddress& raPriv)
|
||||
{
|
||||
uint256 signingHash;
|
||||
sign(signingHash, raPriv);
|
||||
}
|
||||
|
||||
void SerializedValidation::sign(uint256& signingHash, const RippleAddress& raPriv)
|
||||
{
|
||||
signingHash = getSigningHash();
|
||||
std::vector<unsigned char> signature;
|
||||
naPriv.signNodePrivate(signingHash, signature);
|
||||
raPriv.signNodePrivate(signingHash, signature);
|
||||
setFieldVL(sfSignature, signature);
|
||||
// XXX Check if this can fail.
|
||||
// if (!RippleAddress::createNodePrivate(naSeed).signNodePrivate(getSigningHash(), mSignature.peekValue()))
|
||||
// throw std::runtime_error("Unable to sign validation");
|
||||
}
|
||||
|
||||
uint256 SerializedValidation::getSigningHash() const
|
||||
@@ -90,8 +96,8 @@ bool SerializedValidation::isValid(const uint256& signingHash) const
|
||||
{
|
||||
try
|
||||
{
|
||||
RippleAddress naPublicKey = RippleAddress::createNodePublic(getFieldVL(sfSigningPubKey));
|
||||
return naPublicKey.isValid() && naPublicKey.verifyNodePublic(signingHash, getFieldVL(sfSignature));
|
||||
RippleAddress raPublicKey = RippleAddress::createNodePublic(getFieldVL(sfSigningPubKey));
|
||||
return raPublicKey.isValid() && raPublicKey.verifyNodePublic(signingHash, getFieldVL(sfSignature));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -24,13 +24,14 @@ public:
|
||||
|
||||
// These throw if the object is not valid
|
||||
SerializedValidation(SerializerIterator& sit, bool checkSignature = true);
|
||||
SerializedValidation(const uint256& ledgerHash, uint32 signTime, const RippleAddress& naPub,
|
||||
const RippleAddress& naPriv, bool isFull, uint256& signingHash);
|
||||
|
||||
// Does not sign the validation
|
||||
SerializedValidation(const uint256& ledgerHash, uint32 signTime, const RippleAddress& raPub, bool isFull);
|
||||
|
||||
uint256 getLedgerHash() const;
|
||||
uint32 getSignTime() const;
|
||||
uint32 getFlags() const;
|
||||
RippleAddress getSignerPublic() const;
|
||||
RippleAddress getSignerPublic() const;
|
||||
uint160 getNodeID() const { return mNodeID; }
|
||||
bool isValid() const;
|
||||
bool isFull() const;
|
||||
@@ -41,6 +42,8 @@ public:
|
||||
void setTrusted() { mTrusted = true; }
|
||||
std::vector<unsigned char> getSigned() const;
|
||||
std::vector<unsigned char> getSignature() const;
|
||||
void sign(uint256& signingHash, const RippleAddress& raPrivate);
|
||||
void sign(const RippleAddress& raPrivate);
|
||||
|
||||
// The validation this replaced
|
||||
const uint256& getPreviousHash() { return mPreviousHash; }
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
#define __TRANSACTION__
|
||||
|
||||
//
|
||||
// Notes: this code contains legacy constructored sharedXYZ and setXYZ. The intent is for these functions to go away. Transactions
|
||||
// should now be constructed in JSON with. Use STObject::parseJson to obtain a binary version.
|
||||
// Transactions should be constructed in JSON with. Use STObject::parseJson to obtain a binary version.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,10 @@
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/smart_ptr/shared_ptr.hpp>
|
||||
|
||||
#include "TransactionEngine.h"
|
||||
#include "Transactor.h"
|
||||
|
||||
#include "../json/writer.h"
|
||||
|
||||
@@ -88,394 +90,74 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa
|
||||
}
|
||||
#endif
|
||||
|
||||
TER terResult = tesSUCCESS;
|
||||
uint256 txID = txn.getTransactionID();
|
||||
if (!txID)
|
||||
Transactor::pointer transactor=Transactor::makeTransactor(txn,params,this);
|
||||
if(transactor)
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: invalid transaction id";
|
||||
|
||||
terResult = temINVALID;
|
||||
}
|
||||
|
||||
//
|
||||
// Verify transaction is signed properly.
|
||||
//
|
||||
|
||||
// Extract signing key
|
||||
// Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed
|
||||
// without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is
|
||||
// associated with the account.
|
||||
// XXX This could be a lot cleaner to prevent unnecessary copying.
|
||||
RippleAddress naSigningPubKey;
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
naSigningPubKey = RippleAddress::createAccountPublic(txn.getSigningPubKey());
|
||||
|
||||
// Consistency: really signed.
|
||||
if ((tesSUCCESS == terResult) && !isSetBit(params, tapNO_CHECK_SIGN) && !txn.checkSign(naSigningPubKey))
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature";
|
||||
|
||||
terResult = temINVALID;
|
||||
}
|
||||
|
||||
STAmount saCost = theConfig.FEE_DEFAULT;
|
||||
|
||||
// Customize behavior based on transaction type.
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
switch (txn.getTxnType())
|
||||
uint256 txID = txn.getTransactionID();
|
||||
if (!txID)
|
||||
{
|
||||
case ttCLAIM:
|
||||
case ttREGULAR_KEY_SET:
|
||||
saCost = 0;
|
||||
break;
|
||||
cLog(lsWARNING) << "applyTransaction: invalid transaction id";
|
||||
|
||||
case ttPAYMENT:
|
||||
if (txn.getFlags() & tfCreateAccount)
|
||||
{
|
||||
saCost = theConfig.FEE_ACCOUNT_CREATE;
|
||||
}
|
||||
break;
|
||||
|
||||
case ttNICKNAME_SET:
|
||||
{
|
||||
SLE::pointer sleNickname = entryCache(ltNICKNAME, txn.getFieldH256(sfNickname));
|
||||
|
||||
if (!sleNickname)
|
||||
saCost = theConfig.FEE_NICKNAME_CREATE;
|
||||
}
|
||||
break;
|
||||
|
||||
case ttACCOUNT_SET:
|
||||
case ttTRUST_SET:
|
||||
case ttOFFER_CREATE:
|
||||
case ttOFFER_CANCEL:
|
||||
case ttPASSWORD_FUND:
|
||||
case ttWALLET_ADD:
|
||||
nothing();
|
||||
break;
|
||||
|
||||
case ttINVALID:
|
||||
cLog(lsWARNING) << "applyTransaction: Invalid transaction: ttINVALID transaction type";
|
||||
terResult = temINVALID;
|
||||
break;
|
||||
|
||||
default:
|
||||
cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type";
|
||||
terResult = temUNKNOWN;
|
||||
break;
|
||||
return temINVALID;
|
||||
}
|
||||
}
|
||||
|
||||
STAmount saPaid = txn.getTransactionFee();
|
||||
TER terResult= transactor->apply();
|
||||
std::string strToken;
|
||||
std::string strHuman;
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
if (saCost)
|
||||
transResultInfo(terResult, strToken, strHuman);
|
||||
|
||||
cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman;
|
||||
|
||||
if (isTepPartial(terResult) && isSetBit(params, tapRETRY))
|
||||
{
|
||||
// Only check fee is sufficient when the ledger is open.
|
||||
if (isSetBit(params, tapOPEN_LEDGER) && saPaid < saCost)
|
||||
// Partial result and allowed to retry, reclassify as a retry.
|
||||
terResult = terRETRY;
|
||||
}
|
||||
|
||||
if ((tesSUCCESS == terResult) || isTepPartial(terResult))
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially).
|
||||
Serializer m;
|
||||
mNodes.calcRawMeta(m, terResult);
|
||||
|
||||
txnWrite();
|
||||
|
||||
Serializer s;
|
||||
txn.add(s);
|
||||
|
||||
if (isSetBit(params, tapOPEN_LEDGER))
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: insufficient fee";
|
||||
|
||||
terResult = telINSUF_FEE_P;
|
||||
if (!mLedger->addTransaction(txID, s))
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (saPaid)
|
||||
{
|
||||
// Transaction is malformed.
|
||||
cLog(lsWARNING) << "applyTransaction: fee not allowed";
|
||||
|
||||
terResult = temINSUF_FEE_P;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get source account ID.
|
||||
mTxnAccountID = txn.getSourceAccount().getAccountID();
|
||||
if (tesSUCCESS == terResult && !mTxnAccountID)
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: bad source id";
|
||||
|
||||
terResult = temINVALID;
|
||||
}
|
||||
|
||||
if (tesSUCCESS != terResult)
|
||||
return terResult;
|
||||
|
||||
boost::recursive_mutex::scoped_lock sl(mLedger->mLock);
|
||||
|
||||
mTxnAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID));
|
||||
|
||||
// Find source account
|
||||
// If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probablistic.
|
||||
|
||||
STAmount saSrcBalance;
|
||||
uint32 t_seq = txn.getSequence();
|
||||
bool bHaveAuthKey = false;
|
||||
|
||||
if (!mTxnAccount)
|
||||
{
|
||||
cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") %
|
||||
txn.getSourceAccount().humanAccountID());
|
||||
|
||||
terResult = terNO_ACCOUNT;
|
||||
}
|
||||
else
|
||||
{
|
||||
saSrcBalance = mTxnAccount->getFieldAmount(sfBalance);
|
||||
bHaveAuthKey = mTxnAccount->isFieldPresent(sfRegularKey);
|
||||
}
|
||||
|
||||
// Check if account claimed.
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
switch (txn.getTxnType())
|
||||
{
|
||||
case ttCLAIM:
|
||||
if (bHaveAuthKey)
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: Account already claimed.";
|
||||
|
||||
terResult = tefCLAIMED;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
nothing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Consistency: Check signature
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
switch (txn.getTxnType())
|
||||
{
|
||||
case ttCLAIM:
|
||||
// Transaction's signing public key must be for the source account.
|
||||
// To prove the master private key made this transaction.
|
||||
if (naSigningPubKey.getAccountID() != mTxnAccountID)
|
||||
{
|
||||
// Signing Pub Key must be for Source Account ID.
|
||||
cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID();
|
||||
cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID();
|
||||
|
||||
terResult = tefBAD_CLAIM_ID;
|
||||
}
|
||||
break;
|
||||
|
||||
case ttREGULAR_KEY_SET:
|
||||
// Transaction's signing public key must be for the source account.
|
||||
// To prove the master private key made this transaction.
|
||||
if (naSigningPubKey.getAccountID() != mTxnAccountID)
|
||||
{
|
||||
// Signing Pub Key must be for Source Account ID.
|
||||
cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID();
|
||||
cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID();
|
||||
|
||||
terResult = temBAD_SET_ID;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Verify the transaction's signing public key is the key authorized for signing.
|
||||
if (bHaveAuthKey && naSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfRegularKey).getAccountID())
|
||||
{
|
||||
// Authorized to continue.
|
||||
nothing();
|
||||
}
|
||||
else if (naSigningPubKey.getAccountID() == mTxnAccountID)
|
||||
{
|
||||
// Authorized to continue.
|
||||
nothing();
|
||||
}
|
||||
else if (bHaveAuthKey)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account.";
|
||||
|
||||
terResult = tefBAD_AUTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account.";
|
||||
|
||||
terResult = temBAD_AUTH_MASTER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back, if the transaction succeeds.
|
||||
if (tesSUCCESS != terResult || !saCost)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (saSrcBalance < saPaid)
|
||||
{
|
||||
cLog(lsINFO)
|
||||
<< boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s")
|
||||
% saSrcBalance.getText()
|
||||
% saPaid.getText());
|
||||
|
||||
terResult = terINSUF_FEE_B;
|
||||
}
|
||||
else
|
||||
{
|
||||
mTxnAccount->setFieldAmount(sfBalance, saSrcBalance - saPaid);
|
||||
}
|
||||
|
||||
// Validate sequence
|
||||
if (tesSUCCESS != terResult)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (saCost)
|
||||
{
|
||||
uint32 a_seq = mTxnAccount->getFieldU32(sfSequence);
|
||||
|
||||
cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq;
|
||||
|
||||
if (t_seq != a_seq)
|
||||
{
|
||||
if (a_seq < t_seq)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: future sequence number";
|
||||
|
||||
terResult = terPRE_SEQ;
|
||||
}
|
||||
else if (mLedger->hasTransaction(txID))
|
||||
terResult = tefALREADY;
|
||||
else
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: past sequence number";
|
||||
if (!mLedger->addTransaction(txID, s, m))
|
||||
assert(false);
|
||||
|
||||
terResult = tefPAST_SEQ;
|
||||
STAmount saPaid = txn.getTransactionFee();
|
||||
// Charge whatever fee they specified.
|
||||
mLedger->destroyCoins(saPaid.getNValue());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
mTxnAccount.reset();
|
||||
mNodes.clear();
|
||||
|
||||
if (!isSetBit(params, tapOPEN_LEDGER)
|
||||
&& (isTemMalformed(terResult) || isTefFailure(terResult)))
|
||||
{
|
||||
mTxnAccount->setFieldU32(sfSequence, t_seq + 1);
|
||||
// XXX Malformed or failed transaction in closed ledger must bow out.
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return terResult;
|
||||
}else
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: Zero cost transaction";
|
||||
|
||||
if (t_seq)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: bad sequence for pre-paid transaction";
|
||||
|
||||
terResult = tefPAST_SEQ;
|
||||
}
|
||||
cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type";
|
||||
return temUNKNOWN;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
entryModify(mTxnAccount);
|
||||
|
||||
switch (txn.getTxnType())
|
||||
{
|
||||
case ttACCOUNT_SET:
|
||||
terResult = doAccountSet(txn);
|
||||
break;
|
||||
|
||||
|
||||
case ttTRUST_SET:
|
||||
terResult = doTrustSet(txn);
|
||||
break;
|
||||
|
||||
case ttINVALID:
|
||||
cLog(lsINFO) << "applyTransaction: invalid type";
|
||||
terResult = temINVALID;
|
||||
break;
|
||||
|
||||
case ttOFFER_CREATE:
|
||||
terResult = doOfferCreate(txn);
|
||||
break;
|
||||
|
||||
case ttOFFER_CANCEL:
|
||||
terResult = doOfferCancel(txn);
|
||||
break;
|
||||
|
||||
case ttREGULAR_KEY_SET:
|
||||
terResult = doRegularKeySet(txn);
|
||||
break;
|
||||
|
||||
case ttPAYMENT:
|
||||
terResult = doPayment(txn, params);
|
||||
break;
|
||||
|
||||
case ttWALLET_ADD:
|
||||
terResult = doWalletAdd(txn);
|
||||
break;
|
||||
|
||||
case ttCONTRACT:
|
||||
terResult = doContractAdd(txn);
|
||||
break;
|
||||
case ttCONTRACT_REMOVE:
|
||||
terResult = doContractRemove(txn);
|
||||
break;
|
||||
|
||||
default:
|
||||
terResult = temUNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string strToken;
|
||||
std::string strHuman;
|
||||
|
||||
transResultInfo(terResult, strToken, strHuman);
|
||||
|
||||
cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman;
|
||||
|
||||
if (isTepPartial(terResult) && isSetBit(params, tapRETRY))
|
||||
{
|
||||
// Partial result and allowed to retry, reclassify as a retry.
|
||||
terResult = terRETRY;
|
||||
}
|
||||
|
||||
if ((tesSUCCESS == terResult) || isTepPartial(terResult))
|
||||
{
|
||||
// Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially).
|
||||
Serializer m;
|
||||
mNodes.calcRawMeta(m, terResult);
|
||||
|
||||
txnWrite();
|
||||
|
||||
Serializer s;
|
||||
txn.add(s);
|
||||
|
||||
if (isSetBit(params, tapOPEN_LEDGER))
|
||||
{
|
||||
if (!mLedger->addTransaction(txID, s))
|
||||
assert(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mLedger->addTransaction(txID, s, m))
|
||||
assert(false);
|
||||
|
||||
// Charge whatever fee they specified.
|
||||
mLedger->destroyCoins(saPaid.getNValue());
|
||||
}
|
||||
}
|
||||
|
||||
mTxnAccount.reset();
|
||||
mNodes.clear();
|
||||
|
||||
if (!isSetBit(params, tapOPEN_LEDGER)
|
||||
&& (isTemMalformed(terResult) || isTefFailure(terResult)))
|
||||
{
|
||||
// XXX Malformed or failed transaction in closed ledger must bow out.
|
||||
}
|
||||
|
||||
return terResult;
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "TransactionErr.h"
|
||||
#include "InstanceCounter.h"
|
||||
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
DEFINE_INSTANCE(TransactionEngine);
|
||||
|
||||
// A TransactionEngine applies serialized transactions to a ledger
|
||||
@@ -38,6 +42,7 @@ private:
|
||||
LedgerEntrySet mNodes;
|
||||
|
||||
TER setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator);
|
||||
TER checkSig(const SerializedTransaction& txn);
|
||||
|
||||
TER takeOffers(
|
||||
bool bPassive,
|
||||
@@ -55,30 +60,26 @@ protected:
|
||||
uint160 mTxnAccountID;
|
||||
SLE::pointer mTxnAccount;
|
||||
|
||||
|
||||
|
||||
void txnWrite();
|
||||
|
||||
|
||||
public:
|
||||
typedef boost::shared_ptr<TransactionEngine> pointer;
|
||||
|
||||
TransactionEngine() { ; }
|
||||
TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); }
|
||||
|
||||
LedgerEntrySet& getNodes() { return mNodes; }
|
||||
Ledger::pointer getLedger() { return mLedger; }
|
||||
void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; }
|
||||
|
||||
SLE::pointer entryCreate(LedgerEntryType type, const uint256& index) { return mNodes.entryCreate(type, index); }
|
||||
SLE::pointer entryCache(LedgerEntryType type, const uint256& index) { return mNodes.entryCache(type, index); }
|
||||
void entryDelete(SLE::ref sleEntry) { mNodes.entryDelete(sleEntry); }
|
||||
void entryModify(SLE::ref sleEntry) { mNodes.entryModify(sleEntry); }
|
||||
|
||||
void txnWrite();
|
||||
|
||||
TER doAccountSet(const SerializedTransaction& txn);
|
||||
TER doTrustSet(const SerializedTransaction& txn);
|
||||
TER doOfferCreate(const SerializedTransaction& txn);
|
||||
TER doOfferCancel(const SerializedTransaction& txn);
|
||||
TER doRegularKeySet(const SerializedTransaction& txn);
|
||||
TER doPayment(const SerializedTransaction& txn, const TransactionEngineParams params);
|
||||
TER doWalletAdd(const SerializedTransaction& txn);
|
||||
TER doContractAdd(const SerializedTransaction& txn);
|
||||
TER doContractRemove(const SerializedTransaction& txn);
|
||||
|
||||
public:
|
||||
TransactionEngine() { ; }
|
||||
TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); }
|
||||
|
||||
Ledger::pointer getLedger() { return mLedger; }
|
||||
void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; }
|
||||
|
||||
TER applyTransaction(const SerializedTransaction&, TransactionEngineParams);
|
||||
};
|
||||
|
||||
|
||||
220
src/cpp/ripple/Transactor.cpp
Normal file
220
src/cpp/ripple/Transactor.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "Transactor.h"
|
||||
#include "Log.h"
|
||||
#include "Config.h"
|
||||
#include "PaymentTransactor.h"
|
||||
#include "RegularKeySetTransactor.h"
|
||||
#include "AccountSetTransactor.h"
|
||||
#include "WalletAddTransactor.h"
|
||||
#include "OfferCancelTransactor.h"
|
||||
#include "OfferCreateTransactor.h"
|
||||
#include "TrustSetTransactor.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
Transactor::pointer Transactor::makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine)
|
||||
{
|
||||
switch(txn.getTxnType())
|
||||
{
|
||||
case ttPAYMENT:
|
||||
return( Transactor::pointer(new PaymentTransactor(txn,params,engine)) );
|
||||
case ttACCOUNT_SET:
|
||||
return( Transactor::pointer(new AccountSetTransactor(txn,params,engine)) );
|
||||
case ttREGULAR_KEY_SET:
|
||||
return( Transactor::pointer(new RegularKeySetTransactor(txn,params,engine)) );
|
||||
case ttTRUST_SET:
|
||||
return( Transactor::pointer(new TrustSetTransactor(txn,params,engine)) );
|
||||
case ttOFFER_CREATE:
|
||||
return( Transactor::pointer(new OfferCreateTransactor(txn,params,engine)) );
|
||||
case ttOFFER_CANCEL:
|
||||
return( Transactor::pointer(new OfferCancelTransactor(txn,params,engine)) );
|
||||
case ttWALLET_ADD:
|
||||
return( Transactor::pointer(new WalletAddTransactor(txn,params,engine)) );
|
||||
default:
|
||||
return(Transactor::pointer());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Transactor::Transactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : mTxn(txn), mParams(params), mEngine(engine)
|
||||
{
|
||||
mHasAuthKey=false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Transactor::calculateFee()
|
||||
{
|
||||
mFeeDue = theConfig.FEE_DEFAULT;
|
||||
}
|
||||
|
||||
TER Transactor::payFee()
|
||||
{
|
||||
STAmount saPaid = mTxn.getTransactionFee();
|
||||
|
||||
// Only check fee is sufficient when the ledger is open.
|
||||
if (isSetBit(mParams, tapOPEN_LEDGER) && saPaid < mFeeDue)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: insufficient fee";
|
||||
|
||||
return telINSUF_FEE_P;
|
||||
}
|
||||
|
||||
if( !saPaid ) return tesSUCCESS;
|
||||
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back, if the transaction succeeds.
|
||||
if (mSourceBalance < saPaid)
|
||||
{
|
||||
cLog(lsINFO)
|
||||
<< boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s")
|
||||
% mSourceBalance.getText()
|
||||
% saPaid.getText());
|
||||
|
||||
return terINSUF_FEE_B;
|
||||
}
|
||||
|
||||
mSourceBalance -= saPaid;
|
||||
mTxnAccount->setFieldAmount(sfBalance, mSourceBalance);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
}
|
||||
|
||||
|
||||
TER Transactor::checkSig()
|
||||
{
|
||||
// Consistency: Check signature
|
||||
// Verify the transaction's signing public key is the key authorized for signing.
|
||||
if (mHasAuthKey && mSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfAuthorizedKey).getAccountID())
|
||||
{
|
||||
// Authorized to continue.
|
||||
nothing();
|
||||
}
|
||||
else if (mSigningPubKey.getAccountID() == mTxnAccountID)
|
||||
{
|
||||
// Authorized to continue.
|
||||
nothing();
|
||||
}
|
||||
else if (mHasAuthKey)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account.";
|
||||
|
||||
return tefBAD_AUTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account.";
|
||||
|
||||
return temBAD_AUTH_MASTER;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::checkSeq()
|
||||
{
|
||||
uint32 t_seq = mTxn.getSequence();
|
||||
uint32 a_seq = mTxnAccount->getFieldU32(sfSequence);
|
||||
|
||||
cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq;
|
||||
|
||||
if (t_seq != a_seq)
|
||||
{
|
||||
if (a_seq < t_seq)
|
||||
{
|
||||
cLog(lsINFO) << "applyTransaction: future sequence number";
|
||||
|
||||
return terPRE_SEQ;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint256 txID = mTxn.getTransactionID();
|
||||
if (mEngine->getLedger()->hasTransaction(txID))
|
||||
return tefALREADY;
|
||||
}
|
||||
|
||||
cLog(lsWARNING) << "applyTransaction: past sequence number";
|
||||
|
||||
return tefPAST_SEQ;
|
||||
|
||||
}else
|
||||
{
|
||||
mTxnAccount->setFieldU32(sfSequence, t_seq + 1);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// check stuff before you bother to lock the ledger
|
||||
TER Transactor::preCheck()
|
||||
{
|
||||
|
||||
mTxnAccountID = mTxn.getSourceAccount().getAccountID();
|
||||
if (!mTxnAccountID)
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: bad source id";
|
||||
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
// Extract signing key
|
||||
// Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed
|
||||
// without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is
|
||||
// associated with the account.
|
||||
// XXX This could be a lot cleaner to prevent unnecessary copying.
|
||||
mSigningPubKey = RippleAddress::createAccountPublic(mTxn.getSigningPubKey());
|
||||
|
||||
// Consistency: really signed.
|
||||
if ( !isSetBit(mParams, tapNO_CHECK_SIGN) && !mTxn.checkSign(mSigningPubKey))
|
||||
{
|
||||
cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature";
|
||||
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::apply()
|
||||
{
|
||||
TER terResult = tesSUCCESS;
|
||||
terResult=preCheck();
|
||||
if(terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
calculateFee();
|
||||
|
||||
boost::recursive_mutex::scoped_lock sl(mEngine->getLedger()->mLock);
|
||||
|
||||
mTxnAccount = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID));
|
||||
|
||||
// Find source account
|
||||
// If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probabilistic.
|
||||
|
||||
if (!mTxnAccount)
|
||||
{
|
||||
cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") %
|
||||
mTxn.getSourceAccount().humanAccountID());
|
||||
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSourceBalance = mTxnAccount->getFieldAmount(sfBalance);
|
||||
mHasAuthKey = mTxnAccount->isFieldPresent(sfAuthorizedKey);
|
||||
}
|
||||
|
||||
terResult=payFee();
|
||||
if(terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
terResult=checkSig();
|
||||
if(terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
terResult=checkSeq();
|
||||
if(terResult != tesSUCCESS) return(terResult);
|
||||
|
||||
mEngine->entryModify(mTxnAccount);
|
||||
|
||||
return doApply();
|
||||
|
||||
}
|
||||
41
src/cpp/ripple/Transactor.h
Normal file
41
src/cpp/ripple/Transactor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef __TRANSACTOR__
|
||||
#define __TRANSACTOR__
|
||||
|
||||
#include "SerializedTransaction.h"
|
||||
#include "TransactionErr.h"
|
||||
#include "TransactionEngine.h"
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class Transactor
|
||||
{
|
||||
protected:
|
||||
const SerializedTransaction& mTxn;
|
||||
TransactionEngine* mEngine;
|
||||
TransactionEngineParams mParams;
|
||||
|
||||
uint160 mTxnAccountID;
|
||||
STAmount mFeeDue;
|
||||
STAmount mSourceBalance;
|
||||
SLE::pointer mTxnAccount;
|
||||
bool mHasAuthKey;
|
||||
RippleAddress mSigningPubKey;
|
||||
|
||||
|
||||
TER preCheck();
|
||||
TER checkSeq();
|
||||
TER payFee();
|
||||
virtual void calculateFee();
|
||||
virtual TER checkSig();
|
||||
virtual TER doApply()=0;
|
||||
|
||||
Transactor(const SerializedTransaction& txn, TransactionEngineParams params, TransactionEngine* engine);
|
||||
|
||||
public:
|
||||
typedef boost::shared_ptr<Transactor> pointer;
|
||||
|
||||
static Transactor::pointer makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine);
|
||||
|
||||
TER apply();
|
||||
};
|
||||
|
||||
#endif
|
||||
147
src/cpp/ripple/TrustSetTransactor.cpp
Normal file
147
src/cpp/ripple/TrustSetTransactor.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "TrustSetTransactor.h"
|
||||
|
||||
TER TrustSetTransactor::doApply()
|
||||
{
|
||||
TER terResult = tesSUCCESS;
|
||||
Log(lsINFO) << "doTrustSet>";
|
||||
|
||||
const STAmount saLimitAmount = mTxn.getFieldAmount(sfLimitAmount);
|
||||
const bool bQualityIn = mTxn.isFieldPresent(sfQualityIn);
|
||||
const uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32(sfQualityIn) : 0;
|
||||
const bool bQualityOut = mTxn.isFieldPresent(sfQualityOut);
|
||||
const uint32 uQualityOut = bQualityIn ? mTxn.getFieldU32(sfQualityOut) : 0;
|
||||
const uint160 uCurrencyID = saLimitAmount.getCurrency();
|
||||
uint160 uDstAccountID = saLimitAmount.getIssuer();
|
||||
const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest.
|
||||
bool bDelIndex = false;
|
||||
|
||||
// Check if destination makes sense.
|
||||
|
||||
if (saLimitAmount.isNegative())
|
||||
{
|
||||
Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit.";
|
||||
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
else if (!uDstAccountID)
|
||||
{
|
||||
Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified.";
|
||||
|
||||
return temDST_NEEDED;
|
||||
}
|
||||
else if (mTxnAccountID == uDstAccountID)
|
||||
{
|
||||
Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self.";
|
||||
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
|
||||
SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID));
|
||||
if (!sleDst)
|
||||
{
|
||||
Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist.";
|
||||
|
||||
return terNO_DST;
|
||||
}
|
||||
|
||||
STAmount saLimitAllow = saLimitAmount;
|
||||
saLimitAllow.setIssuer(mTxnAccountID);
|
||||
|
||||
SLE::pointer sleRippleState = mEngine->entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID));
|
||||
if (sleRippleState)
|
||||
{
|
||||
// A line exists in one or more directions.
|
||||
#if 0
|
||||
if (!saLimitAmount)
|
||||
{
|
||||
// Zeroing line.
|
||||
uint160 uLowID = sleRippleState->getFieldAmount(sfLowLimit).getIssuer();
|
||||
uint160 uHighID = sleRippleState->getFieldAmount(sfHighLimit).getIssuer();
|
||||
bool bLow = uLowID == uSrcAccountID;
|
||||
bool bHigh = uLowID == uDstAccountID;
|
||||
bool bBalanceZero = !sleRippleState->getFieldAmount(sfBalance);
|
||||
STAmount saDstLimit = sleRippleState->getFieldAmount(bSendLow ? sfLowLimit : sfHighLimit);
|
||||
bool bDstLimitZero = !saDstLimit;
|
||||
|
||||
assert(bLow || bHigh);
|
||||
|
||||
if (bBalanceZero && bDstLimitZero)
|
||||
{
|
||||
// Zero balance and eliminating last limit.
|
||||
|
||||
bDelIndex = true;
|
||||
terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!bDelIndex)
|
||||
{
|
||||
sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow);
|
||||
|
||||
if (!bQualityIn)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (uQualityIn)
|
||||
{
|
||||
sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn);
|
||||
}
|
||||
|
||||
if (!bQualityOut)
|
||||
{
|
||||
nothing();
|
||||
}
|
||||
else if (uQualityOut)
|
||||
{
|
||||
sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut);
|
||||
}
|
||||
|
||||
mEngine->entryModify(sleRippleState);
|
||||
}
|
||||
|
||||
Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex;
|
||||
}
|
||||
// Line does not exist.
|
||||
else if (!saLimitAmount)
|
||||
{
|
||||
Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent ripple line to 0.";
|
||||
|
||||
return terNO_LINE_NO_ZERO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new ripple line.
|
||||
sleRippleState = mEngine->entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID));
|
||||
|
||||
Log(lsINFO) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString();
|
||||
|
||||
sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency.
|
||||
sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow);
|
||||
sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID));
|
||||
|
||||
if (uQualityIn)
|
||||
sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
|
||||
if (uQualityOut)
|
||||
sleRippleState->setFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
|
||||
|
||||
uint64 uSrcRef; // Ignored, dirs never delete.
|
||||
|
||||
terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex());
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex());
|
||||
}
|
||||
|
||||
Log(lsINFO) << "doTrustSet<";
|
||||
|
||||
return terResult;
|
||||
}
|
||||
9
src/cpp/ripple/TrustSetTransactor.h
Normal file
9
src/cpp/ripple/TrustSetTransactor.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
class TrustSetTransactor : public Transactor
|
||||
{
|
||||
public:
|
||||
TrustSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
@@ -97,12 +97,12 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren
|
||||
uint32 now = theApp->getOPs().getNetworkTimeNC();
|
||||
if (set)
|
||||
{
|
||||
for (ValidationSet::iterator vit = set->begin(), end = set->end(); vit != end; ++vit)
|
||||
BOOST_FOREACH(u160_val_pair& it, *set)
|
||||
{
|
||||
bool isTrusted = vit->second->isTrusted();
|
||||
bool isTrusted = it.second->isTrusted();
|
||||
if (isTrusted && currentOnly)
|
||||
{
|
||||
uint32 closeTime = vit->second->getSignTime();
|
||||
uint32 closeTime = it.second->getSignTime();
|
||||
if ((now < (closeTime - LEDGER_EARLY_INTERVAL)) || (now > (closeTime + LEDGER_VAL_INTERVAL)))
|
||||
isTrusted = false;
|
||||
else
|
||||
@@ -119,6 +119,28 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren
|
||||
cLog(lsTRACE) << "VC: " << ledger << "t:" << trusted << " u:" << untrusted;
|
||||
}
|
||||
|
||||
void ValidationCollection::getValidationTypes(const uint256& ledger, int& full, int& partial)
|
||||
{
|
||||
full = partial = 0;
|
||||
boost::mutex::scoped_lock sl(mValidationLock);
|
||||
VSpointer set = findSet(ledger);
|
||||
if (set)
|
||||
{
|
||||
BOOST_FOREACH(u160_val_pair& it, *set)
|
||||
{
|
||||
if (it.second->isTrusted())
|
||||
{
|
||||
if (it.second->isFull())
|
||||
++full;
|
||||
else
|
||||
++partial;
|
||||
}
|
||||
}
|
||||
}
|
||||
cLog(lsTRACE) << "VC: " << ledger << "f:" << full << " p:" << partial;
|
||||
}
|
||||
|
||||
|
||||
int ValidationCollection::getTrustedValidationCount(const uint256& ledger)
|
||||
{
|
||||
int trusted = 0;
|
||||
@@ -126,9 +148,9 @@ int ValidationCollection::getTrustedValidationCount(const uint256& ledger)
|
||||
VSpointer set = findSet(ledger);
|
||||
if (set)
|
||||
{
|
||||
for (ValidationSet::iterator vit = set->begin(), end = set->end(); vit != end; ++vit)
|
||||
BOOST_FOREACH(u160_val_pair& it, *set)
|
||||
{
|
||||
if (vit->second->isTrusted())
|
||||
if (it.second->isTrusted())
|
||||
++trusted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
bool addValidation(const SerializedValidation::pointer&);
|
||||
ValidationSet getValidations(const uint256& ledger);
|
||||
void getValidationCount(const uint256& ledger, bool currentOnly, int& trusted, int& untrusted);
|
||||
void getValidationTypes(const uint256& ledger, int& full, int& partial);
|
||||
|
||||
int getTrustedValidationCount(const uint256& ledger);
|
||||
|
||||
|
||||
58
src/cpp/ripple/WalletAddTransactor.cpp
Normal file
58
src/cpp/ripple/WalletAddTransactor.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "WalletAddTransactor.h"
|
||||
|
||||
TER WalletAddTransactor::doApply()
|
||||
{
|
||||
std::cerr << "WalletAdd>" << std::endl;
|
||||
|
||||
const std::vector<unsigned char> vucPubKey = mTxn.getFieldVL(sfPublicKey);
|
||||
const std::vector<unsigned char> vucSignature = mTxn.getFieldVL(sfSignature);
|
||||
const uint160 uAuthKeyID = mTxn.getFieldAccount160(sfAuthorizedKey);
|
||||
const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey);
|
||||
const uint160 uDstAccountID = naMasterPubKey.getAccountID();
|
||||
|
||||
// FIXME: This should be moved to the transaction's signature check logic and cached
|
||||
if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature))
|
||||
{
|
||||
std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl;
|
||||
|
||||
return tefBAD_ADD_AUTH;
|
||||
}
|
||||
|
||||
SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID));
|
||||
|
||||
if (sleDst)
|
||||
{
|
||||
std::cerr << "WalletAdd: account already created" << std::endl;
|
||||
|
||||
return tefCREATED;
|
||||
}
|
||||
|
||||
STAmount saAmount = mTxn.getFieldAmount(sfAmount);
|
||||
STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance);
|
||||
|
||||
if (saSrcBalance < saAmount)
|
||||
{
|
||||
std::cerr
|
||||
<< boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s")
|
||||
% saSrcBalance.getText()
|
||||
% saAmount.getText())
|
||||
<< std::endl;
|
||||
|
||||
return terUNFUNDED;
|
||||
}
|
||||
|
||||
// Deduct initial balance from source account.
|
||||
mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount);
|
||||
|
||||
// Create the account.
|
||||
sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID));
|
||||
|
||||
sleDst->setFieldAccount(sfAccount, uDstAccountID);
|
||||
sleDst->setFieldU32(sfSequence, 1);
|
||||
sleDst->setFieldAmount(sfBalance, saAmount);
|
||||
sleDst->setFieldAccount(sfAuthorizedKey, uAuthKeyID);
|
||||
|
||||
std::cerr << "WalletAdd<" << std::endl;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
10
src/cpp/ripple/WalletAddTransactor.h
Normal file
10
src/cpp/ripple/WalletAddTransactor.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "Transactor.h"
|
||||
|
||||
|
||||
class WalletAddTransactor : public Transactor
|
||||
{
|
||||
public:
|
||||
WalletAddTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {}
|
||||
|
||||
TER doApply();
|
||||
};
|
||||
Reference in New Issue
Block a user