diff --git a/ripple-example.txt b/ripple-example.txt index 1e3904e45a..2b70163210 100644 --- a/ripple-example.txt +++ b/ripple-example.txt @@ -91,6 +91,13 @@ # [currencies]: # This section allows a site to declare currencies it currently issues. # +# [ledger_history]: +# To serve clients, servers need historical ledger data. This sets the number of +# past ledgers to acquire on server startup and the minimum to maintain while +# running. Servers that don't need to serve clients can set this to "none" or "off". +# Servers that want complete history can set this to "full" or "on". +# The default is 256 ledgers. + [validation_public_key] n9MZTnHe5D5Q2cgE8oV2usFwRqhUvEA8MwP5Mu1XVD6TxmssPRev diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index 7a4bfadef1..16fcada6ad 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -71,8 +71,24 @@ static void InitDB(DatabaseCon** dbCon, const char *fileName, const char *dbInit *dbCon = new DatabaseCon(fileName, dbInit, dbCount); } +volatile bool doShutdown = false; + +#ifdef SIGINT +void sigIntHandler(int) +{ + doShutdown = true; +} +#endif + void Application::run() { +#ifdef SIGINT + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigIntHandler; + sigaction(SIGINT, &sa, NULL); +#endif + assert(mTxnDB == NULL); if (!theConfig.DEBUG_LOGFILE.empty()) { // Let DEBUG messages go to the file but only WARNING or higher to regular output (unless verbose) @@ -118,13 +134,6 @@ void Application::run() else startNewLedger(); - if (theConfig.FULL_HISTORY && (theConfig.START_UP != Config::LOAD)) - { - Ledger::pointer ledger = Ledger::getLastFullLedger(); - if (ledger) - mLedgerMaster.setLedgerRangePresent(0, ledger->getLedgerSeq()); - } - // // Begin validation and ip maintenance. // - Wallet maintains local information: including identity and network connection persistence information. diff --git a/src/cpp/ripple/Config.cpp b/src/cpp/ripple/Config.cpp index 18c60c235b..0304de7b47 100644 --- a/src/cpp/ripple/Config.cpp +++ b/src/cpp/ripple/Config.cpp @@ -3,6 +3,7 @@ #include "utils.h" #include +#include #include #include #include @@ -14,7 +15,7 @@ #define SECTION_FEE_NICKNAME_CREATE "fee_nickname_create" #define SECTION_FEE_OFFER "fee_offer" #define SECTION_FEE_OPERATION "fee_operation" -#define SECTION_FULL_HISTORY "full_history" +#define SECTION_LEDGER_HISTORY "ledger_history" #define SECTION_IPS "ips" #define SECTION_NETWORK_QUORUM "network_quorum" #define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water" @@ -157,7 +158,7 @@ void Config::setup(const std::string& strConf) FEE_DEFAULT = DEFAULT_FEE_DEFAULT; FEE_CONTRACT_OPERATION = DEFAULT_FEE_OPERATION; - FULL_HISTORY = false; + LEDGER_HISTORY = 256; ACCOUNT_PROBE_MAX = 10; @@ -292,8 +293,16 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_FEE_OPERATION, strTemp)) FEE_CONTRACT_OPERATION = boost::lexical_cast(strTemp); - if (sectionSingleB(secConfig, SECTION_FULL_HISTORY, strTemp)) - FULL_HISTORY = boost::lexical_cast(strTemp); + if (sectionSingleB(secConfig, SECTION_LEDGER_HISTORY, strTemp)) + { + boost::to_lower(strTemp); + if ((strTemp == "no") || (strTemp == "none") || (strTemp == "off") || (strTemp == "false")) + LEDGER_HISTORY = 0; + else if ((strTemp == "yes") || (strTemp == "full") || (strTemp == "on") || (strTemp == "-1")) + LEDGER_HISTORY = 1000000000u; + else + LEDGER_HISTORY = boost::lexical_cast(strTemp); + } if (sectionSingleB(secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp)) ACCOUNT_PROBE_MAX = boost::lexical_cast(strTemp); diff --git a/src/cpp/ripple/Config.h b/src/cpp/ripple/Config.h index 53d91f6921..9704396f72 100644 --- a/src/cpp/ripple/Config.h +++ b/src/cpp/ripple/Config.h @@ -108,7 +108,7 @@ public: int FEE_CONTRACT_OPERATION; // fee for each contract operation // Node storage configuration - bool FULL_HISTORY; + uint32 LEDGER_HISTORY; // Client behavior int ACCOUNT_PROBE_MAX; // How far to scan for accounts. diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index d430272a37..f59a186fe1 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -12,7 +12,7 @@ SETUP_LOG(); DECLARE_INSTANCE(HashedObject); HashedObjectStore::HashedObjectStore(int cacheSize, int cacheAge) : - mCache("HashedObjectStore", cacheSize, cacheAge), mWritePending(false), mWriteGeneration(0) + mCache("HashedObjectStore", cacheSize, cacheAge), mWriteGeneration(0), mWritePending(false) { mWriteSet.reserve(128); } diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 2e9482e0fe..c91662aa03 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -299,6 +299,29 @@ SerializedTransaction::pointer Ledger::getSTransaction(SHAMapItem::ref item, SHA return SerializedTransaction::pointer(); } +SerializedTransaction::pointer Ledger::getSMTransaction(SHAMapItem::ref item, SHAMapTreeNode::TNType type, + TransactionMetaSet::pointer& txMeta) +{ + SerializerIterator sit(item->peekSerializer()); + + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + { + txMeta.reset(); + return boost::make_shared(boost::ref(sit)); + } + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { + Serializer sTxn(sit.getVL()); + SerializerIterator tSit(sTxn); + + txMeta = boost::make_shared(item->getTag(), mLedgerSeq, sit.getVL()); + return boost::make_shared(boost::ref(tSit)); + } + + txMeta.reset(); + return SerializedTransaction::pointer(); +} + bool Ledger::getTransaction(const uint256& txID, Transaction::pointer& txn, TransactionMetaSet::pointer& meta) { SHAMapTreeNode::TNType type; @@ -346,7 +369,8 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) static boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %d;"); static boost::format AcctTransExists("SELECT LedgerSeq FROM AccountTransactions WHERE TransId = '%s';"); static boost::format transExists("SELECT Status FROM Transactions WHERE TransID = '%s';"); - static boost::format updateTx("UPDATE Transactions SET LedgerSeq = %d, Status = '%c' WHERE TransID = '%s';"); + static boost::format + updateTx("UPDATE Transactions SET LedgerSeq = %d, Status = '%c', TxnMeta = %s WHERE TransID = '%s';"); static boost::format addLedger("INSERT INTO Ledgers " "(LedgerHash,LedgerSeq,PrevHash,TotalCoins,ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags," "AccountSetHash,TransSetHash) VALUES ('%s','%u','%s','%s','%u','%u','%d','%u','%s','%s');"); @@ -374,14 +398,20 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) for (SHAMapItem::pointer item = txSet.peekFirstItem(type); !!item; item = txSet.peekNextItem(item->getTag(), type)) { - SerializedTransaction::pointer txn = getSTransaction(item, type); - assert(txn); + assert(type == SHAMapTreeNode::tnTRANSACTION_MD); + SerializerIterator sit(item->peekSerializer()); + Serializer rawTxn(sit.getVL()); + std::string escMeta(sqlEscape(sit.getVL())); + + SerializerIterator txnIt(rawTxn); + SerializedTransaction txn(txnIt); + assert(txn.getTransactionID() == item->getTag()); // Make sure transaction is in AccountTransactions. if (!SQL_EXISTS(db, boost::str(AcctTransExists % item->getTag().GetHex()))) { // Transaction not in AccountTransactions - std::vector accts = txn->getAffectedAccounts(); + std::vector accts = txn.getAffectedAccounts(); std::string sql = "INSERT INTO AccountTransactions (TransID, Account, LedgerSeq) VALUES "; bool first = true; @@ -394,7 +424,7 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) sql += "('"; first = false; } - sql += txn->getTransactionID().GetHex(); + sql += txn.getTransactionID().GetHex(); sql += "','"; sql += it->humanAccountID(); sql += "',"; @@ -406,19 +436,20 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) db->executeSQL(sql); // may already be in there } - if (SQL_EXISTS(db, boost::str(transExists % txn->getTransactionID().GetHex()))) + if (SQL_EXISTS(db, boost::str(transExists % txn.getTransactionID().GetHex()))) { - // In Transactions, update LedgerSeq and Status. + // In Transactions, update LedgerSeq, metadata and Status. db->executeSQL(boost::str(updateTx % getLedgerSeq() % TXN_SQL_VALIDATED - % txn->getTransactionID().GetHex())); + % escMeta + % txn.getTransactionID().GetHex())); } else { // Not in Transactions, insert the whole thing.. db->executeSQL( - txn->getSQLInsertHeader() + txn->getSQL(getLedgerSeq(), TXN_SQL_VALIDATED) + ";"); + txn.getMetaSQLInsertHeader() + txn.getMetaSQL(getLedgerSeq(), escMeta) + ";"); } } db->executeSQL("COMMIT TRANSACTION;"); diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index 7f9556c3ed..d9ddc14cb4 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -161,7 +161,10 @@ public: bool hasTransaction(const uint256& TransID) const { return mTransactionMap->hasItem(TransID); } Transaction::pointer getTransaction(const uint256& transID) const; bool getTransaction(const uint256& transID, Transaction::pointer& txn, TransactionMetaSet::pointer& txMeta); + static SerializedTransaction::pointer getSTransaction(SHAMapItem::ref, SHAMapTreeNode::TNType); + SerializedTransaction::pointer getSMTransaction(SHAMapItem::ref, SHAMapTreeNode::TNType, + TransactionMetaSet::pointer& txMeta); // high-level functions AccountState::pointer getAccountState(const RippleAddress& acctID); diff --git a/src/cpp/ripple/LedgerConsensus.cpp b/src/cpp/ripple/LedgerConsensus.cpp index e3e867d90a..7401461e86 100644 --- a/src/cpp/ripple/LedgerConsensus.cpp +++ b/src/cpp/ripple/LedgerConsensus.cpp @@ -644,8 +644,16 @@ void LedgerConsensus::stateAccepted() endConsensus(); } +extern volatile bool doShutdown; + void LedgerConsensus::timerEntry() { + if (doShutdown) + { + cLog(lsFATAL) << "Shutdown requested"; + theApp->stop(); + } + if ((mState != lcsFINISHED) && (mState != lcsACCEPTED)) checkLCL(); diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 6c3a2ebc97..0a130f90a3 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -117,6 +117,12 @@ TER LedgerMaster::doTransaction(const SerializedTransaction& txn, TransactionEng return result; } +bool LedgerMaster::haveLedgerRange(uint32 from, uint32 to) +{ + uint32 prevMissing = mCompleteLedgers.prevMissing(to + 1); + return (prevMissing == RangeSet::RangeSetAbsent) || (prevMissing < from); +} + void LedgerMaster::acquireMissingLedger(const uint256& ledgerHash, uint32 ledgerSeq) { mMissingLedger = theApp->getMasterLedgerAcquire().findCreate(ledgerHash); @@ -152,8 +158,19 @@ void LedgerMaster::missingAcquireComplete(LedgerAcquire::pointer acq) } } -void LedgerMaster::setFullLedger(Ledger::ref ledger) +static bool shouldAcquire(uint32 currentLedger, uint32 ledgerHistory, uint32 candidateLedger) { + bool ret; + if (candidateLedger >= currentLedger) + ret = true; + else ret = (currentLedger - candidateLedger) <= ledgerHistory; + cLog(lsDEBUG) << "Missing ledger " << candidateLedger << (ret ? "will" : "will NOT") << " be acquired"; + return ret; +} + +void LedgerMaster::setFullLedger(Ledger::ref ledger) +{ // A new ledger has been accepted as part of the trusted chain + boost::recursive_mutex::scoped_lock ml(mLock); mCompleteLedgers.setValue(ledger->getLedgerSeq()); @@ -161,7 +178,12 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) if ((ledger->getLedgerSeq() != 0) && mCompleteLedgers.hasValue(ledger->getLedgerSeq() - 1)) { // we think we have the previous ledger, double check Ledger::pointer prevLedger = getLedgerBySeq(ledger->getLedgerSeq() - 1); - if (prevLedger && (prevLedger->getHash() != ledger->getParentHash())) + if (!prevLedger) + { + cLog(lsWARNING) << "Ledger " << ledger->getLedgerSeq() - 1 << " missing"; + mCompleteLedgers.clearValue(ledger->getLedgerSeq() - 1); + } + else if (prevLedger->getHash() != ledger->getParentHash()) { cLog(lsWARNING) << "Ledger " << ledger->getLedgerSeq() << " invalidates prior ledger"; mCompleteLedgers.clearValue(prevLedger->getLedgerSeq()); @@ -171,7 +193,7 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) if (mMissingLedger && mMissingLedger->isComplete()) mMissingLedger.reset(); - if (mMissingLedger || !theConfig.FULL_HISTORY) + if (mMissingLedger || !theConfig.LEDGER_HISTORY) return; if (Ledger::getPendingSaves() > 3) @@ -183,13 +205,17 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) // see if there's a ledger gap we need to fill if (!mCompleteLedgers.hasValue(ledger->getLedgerSeq() - 1)) { + if (!shouldAcquire(mCurrentLedger->getLedgerSeq(), theConfig.LEDGER_HISTORY, ledger->getLedgerSeq() - 1)) + return; cLog(lsINFO) << "We need the ledger before the ledger we just accepted"; acquireMissingLedger(ledger->getParentHash(), ledger->getLedgerSeq() - 1); } else { uint32 prevMissing = mCompleteLedgers.prevMissing(ledger->getLedgerSeq()); - if (prevMissing != RangeSet::RangeSetAbsent) + if (prevMissing == RangeSet::RangeSetAbsent) + return; + if (shouldAcquire(mCurrentLedger->getLedgerSeq(), theConfig.LEDGER_HISTORY, prevMissing)) { cLog(lsINFO) << "Ledger " << prevMissing << " is missing"; assert(!mCompleteLedgers.hasValue(prevMissing)); diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index 152952b50b..801e2f8d74 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -93,6 +93,8 @@ public: void addHeldTransaction(const Transaction::pointer& trans); + bool haveLedgerRange(uint32 from, uint32 to); + void sweep(void) { mLedgerHistory.sweep(); } }; diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index e67d8eb927..ae2d11087d 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -81,6 +81,11 @@ uint32 NetworkOPs::getCurrentLedgerID() return mLedgerMaster->getCurrentLedger()->getLedgerSeq(); } +bool NetworkOPs::haveLedgerRange(uint32 from, uint32 to) +{ + return mLedgerMaster->haveLedgerRange(from, to); +} + void NetworkOPs::submitTransaction(Job&, SerializedTransaction::pointer iTrans, stCallback callback) { // this is an asynchronous interface Serializer s; @@ -876,14 +881,15 @@ void NetworkOPs::setMode(OperatingMode om) mMode = om; } -std::vector< std::pair > - NetworkOPs::getAffectedAccounts(const RippleAddress& account, uint32 minLedger, uint32 maxLedger) + +std::vector< std::pair > + NetworkOPs::getAccountTxs(const RippleAddress& account, uint32 minLedger, uint32 maxLedger) { - std::vector< std::pair > affectedAccounts; + std::vector< std::pair > ret; std::string sql = - str(boost::format("SELECT LedgerSeq,TransID FROM AccountTransactions INDEXED BY AcctTxIndex " - " WHERE Account = '%s' AND LedgerSeq <= '%d' AND LedgerSeq >= '%d' ORDER BY LedgerSeq LIMIT 1000;") + str(boost::format("SELECT LedgerSeq,Status,RawTxn,TxnMeta FROM Transactions where TransID in (SELECT TransID from AccountTransactions " + " WHERE Account = '%s' AND LedgerSeq <= '%d' AND LedgerSeq >= '%d' LIMIT 1000) ORDER BY LedgerSeq;") % account.humanAccountID() % maxLedger % minLedger); { @@ -892,11 +898,24 @@ std::vector< std::pair > SQL_FOREACH(db, sql) { - affectedAccounts.push_back(std::make_pair(db->getInt("LedgerSeq"), uint256(db->getStrBinary("TransID")))); + Transaction::pointer txn=Transaction::transactionFromSQL(db,false); + + Serializer rawMeta; + int metaSize = 2048; + rawMeta.resize(metaSize); + metaSize = db->getBinary("RawTxn", &*rawMeta.begin(), rawMeta.getLength()); + if (metaSize > rawMeta.getLength()) + { + rawMeta.resize(metaSize); + db->getBinary("RawTxn", &*rawMeta.begin(), rawMeta.getLength()); + }else rawMeta.resize(metaSize); + + TransactionMetaSet::pointer meta= boost::make_shared(txn->getID(), txn->getLedger(), rawMeta.getData()); + ret.push_back(std::make_pair(txn,meta)); } } - return affectedAccounts; + return ret; } std::vector @@ -1076,6 +1095,7 @@ Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TER terRes void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta) { Json::Value jvObj = transJson(stTxn, terResult, true, lpCurrent, "transaction"); + if(meta) jvObj["meta"]=meta->getJson(0); { boost::recursive_mutex::scoped_lock sl(mMonitorLock); diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 3878689dd8..8e5730df55 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -134,6 +134,9 @@ public: uint256 getClosedLedgerHash() { return mLedgerMaster->getClosedLedger()->getHash(); } + // Do we have this inclusive range of ledgers in our database + bool haveLedgerRange(uint32 from, uint32 to); + SerializedValidation::ref getLastValidation() { return mLastValidation; } void setLastValidation(SerializedValidation::ref v) { mLastValidation = v; } @@ -230,8 +233,8 @@ public: uint256 getConsensusLCL(); // client information retrieval functions - std::vector< std::pair > - getAffectedAccounts(const RippleAddress& account, uint32 minLedger, uint32 maxLedger); + std::vector< std::pair > + getAccountTxs(const RippleAddress& account, uint32 minLedger, uint32 maxLedger); std::vector getLedgerAffectedAccounts(uint32 ledgerSeq); std::vector getLedgerTransactions(uint32 ledgerSeq); diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 653231d479..f1966c626f 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -1303,25 +1303,18 @@ Json::Value RPCHandler::doAccountTransactions(const Json::Value& params) try { #endif - std::vector< std::pair > txns = mNetOps->getAffectedAccounts(account, minLedger, maxLedger); + std::vector< std::pair > txns = mNetOps->getAccountTxs(account, minLedger, maxLedger); Json::Value ret(Json::objectValue); ret["account"] = account.humanAccountID(); Json::Value ledgers(Json::arrayValue); // uint32 currentLedger = 0; - for (std::vector< std::pair >::iterator it = txns.begin(), end = txns.end(); it != end; ++it) + for (std::vector< std::pair >::iterator it = txns.begin(), end = txns.end(); it != end; ++it) { - Transaction::pointer txn = theApp->getMasterTransaction().fetch(it->second, true); - if (!txn) - { - ret["transactions"].append(it->second.GetHex()); - } - else - { - txn->setLedger(it->first); - ret["transactions"].append(txn->getJson(1)); - } - + Json::Value obj(Json::objectValue); + if(it->first) obj["tx"]=it->first->getJson(1); + if(it->second) obj["meta"]=it->second->getJson(0); + ret["transactions"].append(obj); } return ret; #ifndef DEBUG diff --git a/src/cpp/ripple/SerializedTransaction.cpp b/src/cpp/ripple/SerializedTransaction.cpp index 835339cac7..a6ded3c21b 100644 --- a/src/cpp/ripple/SerializedTransaction.cpp +++ b/src/cpp/ripple/SerializedTransaction.cpp @@ -183,11 +183,21 @@ std::string SerializedTransaction::getSQLValueHeader() return "(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn)"; } +std::string SerializedTransaction::getMetaSQLValueHeader() +{ + return "(TransID, TransType, FromAcct, FromSeq, LedgerSeq, Status, RawTxn, TxnMeta)"; +} + std::string SerializedTransaction::getSQLInsertHeader() { return "INSERT INTO Transactions " + getSQLValueHeader() + " VALUES "; } +std::string SerializedTransaction::getMetaSQLInsertHeader() +{ + return "INSERT INTO Transactions " + getMetaSQLValueHeader() + " VALUES "; +} + std::string SerializedTransaction::getSQL(uint32 inLedger, char status) const { Serializer s; @@ -195,16 +205,36 @@ std::string SerializedTransaction::getSQL(uint32 inLedger, char status) const return getSQL(s, inLedger, status); } +std::string SerializedTransaction::getMetaSQL(uint32 inLedger, const std::string& escapedMetaData) const +{ + Serializer s; + add(s); + return getMetaSQL(s, inLedger, TXN_SQL_VALIDATED, escapedMetaData); +} + std::string SerializedTransaction::getSQL(Serializer rawTxn, uint32 inLedger, char status) const { + static boost::format bfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s)"); std::string rTxn; theApp->getTxnDB()->getDB()->escape( reinterpret_cast(rawTxn.getDataPtr()), rawTxn.getLength(), rTxn); - return str(boost::format("('%s', '%s', '%s', '%d', '%d', '%c', %s)") + return str(bfTrans % getTransactionID().GetHex() % getTransactionType() % getSourceAccount().humanAccountID() % getSequence() % inLedger % status % rTxn); } +std::string SerializedTransaction::getMetaSQL(Serializer rawTxn, uint32 inLedger, char status, + const std::string& escapedMetaData) const +{ + static boost::format bfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)"); + std::string rTxn; + theApp->getTxnDB()->getDB()->escape( + reinterpret_cast(rawTxn.getDataPtr()), rawTxn.getLength(), rTxn); + return str(bfTrans + % getTransactionID().GetHex() % getTransactionType() % getSourceAccount().humanAccountID() + % getSequence() % inLedger % status % rTxn % escapedMetaData); +} + BOOST_AUTO_TEST_SUITE(SerializedTransactionTS) diff --git a/src/cpp/ripple/SerializedTransaction.h b/src/cpp/ripple/SerializedTransaction.h index 50d974db09..c026d0220d 100644 --- a/src/cpp/ripple/SerializedTransaction.h +++ b/src/cpp/ripple/SerializedTransaction.h @@ -77,6 +77,12 @@ public: std::string getSQL(uint32 inLedger, char status) const; std::string getSQL(Serializer rawTxn, uint32 inLedger, char status) const; + // SQL Functions with metadata + static std::string getMetaSQLValueHeader(); + static std::string getMetaSQLInsertHeader(); + std::string getMetaSQL(uint32 inLedger, const std::string& escapedMetaData) const; + std::string getMetaSQL(Serializer rawTxn, uint32 inLedger, char status, const std::string& escapedMetaData) const; + }; #endif diff --git a/src/cpp/ripple/Transaction.cpp b/src/cpp/ripple/Transaction.cpp index 5305c784f4..889eea6a24 100644 --- a/src/cpp/ripple/Transaction.cpp +++ b/src/cpp/ripple/Transaction.cpp @@ -246,6 +246,7 @@ Transaction::pointer Transaction::transactionFromSQL(const std::string& sql) return tr; } + Transaction::pointer Transaction::load(const uint256& id) { std::string sql = "SELECT LedgerSeq,Status,RawTxn FROM Transactions WHERE TransID='"; diff --git a/src/cpp/ripple/utils.h b/src/cpp/ripple/utils.h index c6568d5104..10eae9673b 100644 --- a/src/cpp/ripple/utils.h +++ b/src/cpp/ripple/utils.h @@ -158,6 +158,12 @@ inline static std::string sqlEscape(const std::string& strSrc) return str(f % strHex(strSrc)); } +inline static std::string sqlEscape(const std::vector& vecSrc) +{ + static boost::format f("X'%s'"); + return str(f % strHex(vecSrc)); +} + template bool isZero(Iterator first, int iSize) {