diff --git a/newcoin.vcxproj b/newcoin.vcxproj index 07ca73e10..cd9990a88 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -186,6 +186,7 @@ + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 483cb0c2d..a4f8be012 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -270,6 +270,9 @@ Source Files + + Source Files + Source Files diff --git a/ripple2010.vcxproj b/ripple2010.vcxproj index d3e9aff5c..e04414111 100644 --- a/ripple2010.vcxproj +++ b/ripple2010.vcxproj @@ -185,6 +185,7 @@ + diff --git a/ripple2010.vcxproj.filters b/ripple2010.vcxproj.filters index bbdf82255..14f56c632 100644 --- a/ripple2010.vcxproj.filters +++ b/ripple2010.vcxproj.filters @@ -267,6 +267,9 @@ Source Files + + Source Files + Source Files diff --git a/src/cpp/database/SqliteDatabase.cpp b/src/cpp/database/SqliteDatabase.cpp index 4bb0850bf..c9a540ff8 100644 --- a/src/cpp/database/SqliteDatabase.cpp +++ b/src/cpp/database/SqliteDatabase.cpp @@ -279,6 +279,14 @@ SqliteStatement::SqliteStatement(SqliteDatabase* db, const char *sql) throw j; } +SqliteStatement::SqliteStatement(SqliteDatabase* db, const std::string& sql) +{ + assert(db); + int j = sqlite3_prepare_v2(db->peekConnection(), sql.c_str(), sql.size() + 1, &statement, NULL); + if (j != SQLITE_OK) + throw j; +} + SqliteStatement::~SqliteStatement() { sqlite3_finalize(statement); diff --git a/src/cpp/database/SqliteDatabase.h b/src/cpp/database/SqliteDatabase.h index ed2d82945..19569a69b 100644 --- a/src/cpp/database/SqliteDatabase.h +++ b/src/cpp/database/SqliteDatabase.h @@ -73,6 +73,7 @@ protected: public: SqliteStatement(SqliteDatabase* db, const char *statement); + SqliteStatement(SqliteDatabase* db, const std::string& statement); ~SqliteStatement(); sqlite3_stmt* peekStatement(); diff --git a/src/cpp/ripple/AcceptedLedger.cpp b/src/cpp/ripple/AcceptedLedger.cpp index 0043d332d..6226c3ebc 100644 --- a/src/cpp/ripple/AcceptedLedger.cpp +++ b/src/cpp/ripple/AcceptedLedger.cpp @@ -14,17 +14,21 @@ ALTransaction::ALTransaction(uint32 seq, SerializerIterator& sit) mMeta = boost::make_shared(mTxn->getTransactionID(), seq, mRawMeta); mAffected = mMeta->getAffectedAccounts(); mResult = mMeta->getResultTER(); + buildJson(); } ALTransaction::ALTransaction(SerializedTransaction::ref txn, TransactionMetaSet::ref met) : mTxn(txn), mMeta(met), mAffected(met->getAffectedAccounts()) { mResult = mMeta->getResultTER(); + buildJson(); } ALTransaction::ALTransaction(SerializedTransaction::ref txn, TER result) : mTxn(txn), mResult(result), mAffected(txn->getMentionedAccounts()) -{ ; } +{ + buildJson(); +} std::string ALTransaction::getEscMeta() const { @@ -32,16 +36,16 @@ std::string ALTransaction::getEscMeta() const return sqlEscape(mRawMeta); } -Json::Value ALTransaction::getJson(int j) const +void ALTransaction::buildJson() { - Json::Value ret(Json::objectValue); - ret["transaction"] = mTxn->getJson(j); + mJson = Json::objectValue; + mJson["transaction"] = mTxn->getJson(0); if (mMeta) { - ret["meta"] = mMeta->getJson(j); - ret["raw_meta"] = strHex(mRawMeta); + mJson["meta"] = mMeta->getJson(0); + mJson["raw_meta"] = strHex(mRawMeta); } - ret["result"] = transHuman(mResult); + mJson["result"] = transHuman(mResult); if (!mAffected.empty()) { @@ -50,10 +54,8 @@ Json::Value ALTransaction::getJson(int j) const { affected.append(ra.humanAccountID()); } - ret["affected"] = affected; + mJson["affected"] = affected; } - - return ret; } AcceptedLedger::AcceptedLedger(Ledger::ref ledger) : mLedger(ledger) diff --git a/src/cpp/ripple/AcceptedLedger.h b/src/cpp/ripple/AcceptedLedger.h index 4b6297e55..04f917a78 100644 --- a/src/cpp/ripple/AcceptedLedger.h +++ b/src/cpp/ripple/AcceptedLedger.h @@ -14,6 +14,9 @@ protected: TER mResult; std::vector mAffected; std::vector mRawMeta; + Json::Value mJson; + + void buildJson(); public: @@ -28,11 +31,12 @@ public: uint256 getTransactionID() const { return mTxn->getTransactionID(); } TransactionType getTxnType() const { return mTxn->getTxnType(); } TER getResult() const { return mResult; } + uint32 getTxnSeq() const { return mMeta->getIndex(); } bool isApplied() const { return !!mMeta; } int getIndex() const { return mMeta ? mMeta->getIndex() : 0; } std::string getEscMeta() const; - Json::Value getJson(int) const; + Json::Value getJson() const { return mJson; } }; class AcceptedLedger diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index 7546aa9cf..df38bbee7 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -158,7 +158,13 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) mIsNative = !currency.isString() || currency.asString().empty() || (currency.asString() == SYSTEM_CURRENCY_CODE); - if (!mIsNative) { + if (mIsNative) + { + if (v.isObject()) + throw std::runtime_error("XRP may not be specified as an object"); + } + else + { // non-XRP if (!currencyFromString(mCurrency, currency.asString())) throw std::runtime_error("invalid currency"); @@ -799,7 +805,9 @@ STAmount operator+(const STAmount& v1, const STAmount& v2) // this addition cannot overflow an int64, it can overflow an STAmount and the constructor will throw int64 fv = vv1 + vv2; - if (fv >= 0) + if ((fv >= -10) && (fv <= 10)) + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer); + else if (fv >= 0) return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, fv, ov1, false); else return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, -fv, ov1, true); @@ -834,7 +842,9 @@ STAmount operator-(const STAmount& v1, const STAmount& v2) // this subtraction cannot overflow an int64, it can overflow an STAmount and the constructor will throw int64 fv = vv1 - vv2; - if (fv >= 0) + if ((fv >= -10) && (fv <= 10)) + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer); + else if (fv >= 0) return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, fv, ov1, false); else return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, -fv, ov1, true); diff --git a/src/cpp/ripple/AmountRound.cpp b/src/cpp/ripple/AmountRound.cpp index 12deef7c9..68633a6f4 100644 --- a/src/cpp/ripple/AmountRound.cpp +++ b/src/cpp/ripple/AmountRound.cpp @@ -105,7 +105,9 @@ STAmount STAmount::addRound(const STAmount& v1, const STAmount& v2, bool roundUp } int64 fv = vv1 + vv2; - if (fv >= 0) + if ((fv >= -10) && (fv <= -10)) + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer); + else if (fv >= 0) { uint64 v = static_cast(fv); canonicalizeRound(false, v, ov1, roundUp); @@ -168,7 +170,9 @@ STAmount STAmount::subRound(const STAmount& v1, const STAmount& v2, bool roundUp } int64 fv = vv1 + vv2; - if (fv >= 0) + if ((fv >= -10) && (fv <= -10)) + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer); + else if (fv >= 0) { uint64 v = static_cast(fv); canonicalizeRound(false, v, ov1, roundUp); diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index e7961cc33..c8e1a1453 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -46,6 +46,7 @@ DatabaseCon::~DatabaseCon() } Application::Application() : +// mIOService(2), mIOWork(mIOService), mAuxWork(mAuxService), mUNL(mIOService), mNetOps(mIOService, &mLedgerMaster), mTempNodeCache("NodeCache", 16384, 90), mHashedObjectStore(16384, 300), mSLECache("LedgerEntryCache", 4096, 120), mSNTPClient(mAuxService), mJobQueue(mIOService), mFeeTrack(), @@ -149,6 +150,9 @@ void Application::setup() mLedgerDB->getDB()->setupCheckpointing(&mJobQueue); mHashNodeDB->getDB()->setupCheckpointing(&mJobQueue); + if (!theConfig.RUN_STANDALONE) + updateTables(); + if (theConfig.START_UP == Config::FRESH) { cLog(lsINFO) << "Starting new Ledger"; @@ -423,12 +427,12 @@ void Application::loadOldLedger(const std::string& l) mLedgerMaster.switchLedgers(loadLedger, openLedger); mNetOps.setLastCloseTime(loadLedger->getCloseTimeNC()); } - catch (SHAMapMissingNode& mn) + catch (SHAMapMissingNode&) { cLog(lsFATAL) << "Data is missing for selected ledger"; exit(-1); } - catch (boost::bad_lexical_cast& blc) + catch (boost::bad_lexical_cast&) { cLog(lsFATAL) << "Ledger specified '" << l << "' is not valid"; exit(-1); diff --git a/src/cpp/ripple/Application.h b/src/cpp/ripple/Application.h index 53836e110..6318e4972 100644 --- a/src/cpp/ripple/Application.h +++ b/src/cpp/ripple/Application.h @@ -90,6 +90,7 @@ class Application volatile bool mShutdown; + void updateTables(); void startNewLedger(); void loadOldLedger(const std::string&); diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 622486940..41ba8a749 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -505,6 +505,16 @@ Json::Value RPCParser::parseSignSubmit(const Json::Value& jvParams) return rpcError(rpcINVALID_PARAMS); } +// sms +Json::Value RPCParser::parseSMS(const Json::Value& jvParams) +{ + Json::Value jvRequest; + + jvRequest["text"] = jvParams[0u].asString(); + + return jvRequest; +} + // tx Json::Value RPCParser::parseTx(const Json::Value& jvParams) { @@ -669,6 +679,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) { "random", &RPCParser::parseAsIs, 0, 0 }, { "ripple_path_find", &RPCParser::parseRipplePathFind, 1, 2 }, { "sign", &RPCParser::parseSignSubmit, 2, 2 }, + { "sms", &RPCParser::parseSMS, 1, 1 }, { "submit", &RPCParser::parseSignSubmit, 1, 2 }, { "server_info", &RPCParser::parseAsIs, 0, 0 }, { "server_state", &RPCParser::parseAsIs, 0, 0 }, diff --git a/src/cpp/ripple/CallRPC.h b/src/cpp/ripple/CallRPC.h index 4bc3e8439..586006941 100644 --- a/src/cpp/ripple/CallRPC.h +++ b/src/cpp/ripple/CallRPC.h @@ -33,6 +33,7 @@ protected: Json::Value parseOwnerInfo(const Json::Value& jvParams); Json::Value parseRandom(const Json::Value& jvParams); Json::Value parseRipplePathFind(const Json::Value& jvParams); + Json::Value parseSMS(const Json::Value& jvParams); Json::Value parseSignSubmit(const Json::Value& jvParams); Json::Value parseTx(const Json::Value& jvParams); Json::Value parseTxHistory(const Json::Value& jvParams); diff --git a/src/cpp/ripple/Config.cpp b/src/cpp/ripple/Config.cpp index eb904eb59..d1e166cee 100644 --- a/src/cpp/ripple/Config.cpp +++ b/src/cpp/ripple/Config.cpp @@ -45,6 +45,11 @@ #define SECTION_RPC_USER "rpc_user" #define SECTION_RPC_PASSWORD "rpc_password" #define SECTION_RPC_STARTUP "rpc_startup" +#define SECTION_SMS_FROM "sms_from" +#define SECTION_SMS_KEY "sms_key" +#define SECTION_SMS_SECRET "sms_secret" +#define SECTION_SMS_TO "sms_to" +#define SECTION_SMS_URL "sms_url" #define SECTION_SNTP "sntp_servers" #define SECTION_SSL_VERIFY "ssl_verify" #define SECTION_SSL_VERIFY_FILE "ssl_verify_file" @@ -473,6 +478,12 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp)) ACCOUNT_PROBE_MAX = boost::lexical_cast(strTemp); + (void) sectionSingleB(secConfig, SECTION_SMS_FROM, SMS_FROM); + (void) sectionSingleB(secConfig, SECTION_SMS_KEY, SMS_KEY); + (void) sectionSingleB(secConfig, SECTION_SMS_SECRET, SMS_SECRET); + (void) sectionSingleB(secConfig, SECTION_SMS_TO, SMS_TO); + (void) sectionSingleB(secConfig, SECTION_SMS_URL, SMS_URL); + if (sectionSingleB(secConfig, SECTION_VALIDATORS_FILE, strTemp)) VALIDATORS_FILE = strTemp; diff --git a/src/cpp/ripple/Config.h b/src/cpp/ripple/Config.h index f6e65856f..2f31731e6 100644 --- a/src/cpp/ripple/Config.h +++ b/src/cpp/ripple/Config.h @@ -185,6 +185,12 @@ public: std::string SSL_VERIFY_FILE; std::string SSL_VERIFY_DIR; + std::string SMS_FROM; + std::string SMS_KEY; + std::string SMS_SECRET; + std::string SMS_TO; + std::string SMS_URL; + Config(); int getSize(SizedItemName); diff --git a/src/cpp/ripple/DBInit.cpp b/src/cpp/ripple/DBInit.cpp index 60210514a..83a89b522 100644 --- a/src/cpp/ripple/DBInit.cpp +++ b/src/cpp/ripple/DBInit.cpp @@ -26,11 +26,12 @@ const char *TxnDBInit[] = { TransID CHARACTER(64), \ Account CHARACTER(64), \ LedgerSeq BIGINT UNSIGNED \ + TxnSeq INTEGER \ );", "CREATE INDEX AcctTxIDIndex ON \ AccountTransactions(TransID);", "CREATE INDEX AcctTxIndex ON \ - AccountTransactions(Account, LedgerSeq, TransID);", + AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);", "CREATE INDEX AcctLgrIndex ON \ AccountTransactions(LedgerSeq, Account, TransID);", diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index 0725d5530..cc8b21a68 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -95,7 +95,7 @@ void HashedObjectStore::bulkWrite() { Database* db = theApp->getHashNodeDB()->getDB(); ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); - static SqliteStatement pSt(db->getSqliteDB(), + SqliteStatement pSt(db->getSqliteDB(), "INSERT OR IGNORE INTO CommittedObjects " "(Hash,ObjType,LedgerIndex,Object) VALUES (?, ?, ?, ?);"); @@ -183,17 +183,17 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) #ifndef NO_SQLITE3_PREPARE { + std::string sql = "SELECT ObjType,LedgerIndex,Object FROM CommittedObjects WHERE Hash = '"; + sql.append(hash.GetHex()); + sql.append("';"); + ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtDISK, "HOS::retrieve")); - static SqliteStatement pSt(theApp->getHashNodeDB()->getDB()->getSqliteDB(), - "SELECT ObjType,LedgerIndex,Object FROM CommittedObjects WHERE Hash = ?;"); - - pSt.bind(1, hash.GetHex()); + SqliteStatement pSt(theApp->getHashNodeDB()->getDB()->getSqliteDB(), sql.c_str()); int ret = pSt.step(); if (pSt.isDone(ret)) { - pSt.reset(); mNegativeCache.add(hash); cLog(lsTRACE) << "HOS: " << hash <<" fetch: not in db"; return obj; @@ -202,7 +202,6 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) type = pSt.peekString(0); index = pSt.getUInt32(1); pSt.getBlob(2).swap(data); - pSt.reset(); } #else @@ -271,7 +270,7 @@ int HashedObjectStore::import(const std::string& file) uint256 hash; std::string hashStr; importDB->getStr("Hash", hashStr); - hash.SetHex(hashStr, true); + hash.SetHexExact(hashStr); if (hash.isZero()) { cLog(lsWARNING) << "zero hash found in import table"; diff --git a/src/cpp/ripple/HttpsClient.cpp b/src/cpp/ripple/HttpsClient.cpp index 1144d9859..9e4c313c7 100644 --- a/src/cpp/ripple/HttpsClient.cpp +++ b/src/cpp/ripple/HttpsClient.cpp @@ -443,4 +443,48 @@ void HttpsClient::httpsRequest( client->httpsRequest(bSSL, deqSites, setRequest, timeout, complete); } +#define SMS_TIMEOUT 30 + +bool responseSMS(const boost::system::error_code& ecResult, int iStatus, const std::string& strData) { + cLog(lsINFO) << "SMS: Response:" << iStatus << " :" << strData; + + return true; +} + +void HttpsClient::sendSMS(boost::asio::io_service& io_service, const std::string& strText) { + std::string strScheme; + std::string strDomain; + int iPort; + std::string strPath; + + if (theConfig.SMS_URL == "" || !parseUrl(theConfig.SMS_URL, strScheme, strDomain, iPort, strPath)) + { + cLog(lsWARNING) << "SMSRequest: Bad URL:" << theConfig.SMS_URL; + } + else + { + bool bSSL = strScheme == "https"; + + std::deque deqSites(1, strDomain); + std::string strURI = + boost::str(boost::format("%s?from=%s&to=%s&api_key=%s&api_secret=%s&text=%s") + % (strPath.empty() ? "/" : strPath) + % theConfig.SMS_FROM + % theConfig.SMS_TO + % theConfig.SMS_KEY + % theConfig.SMS_SECRET + % urlEncode(strText)); + + // cLog(lsINFO) << "SMS: Request:" << strURI; + cLog(lsINFO) << "SMS: Request: '" << strText << "'"; + + if (iPort < 0) + iPort = bSSL ? 443 : 80; + + boost::shared_ptr client(new HttpsClient(io_service, iPort, CLIENT_MAX_HEADER)); + + client->httpsGet(bSSL, deqSites, strURI, boost::posix_time::seconds(SMS_TIMEOUT), + BIND_TYPE(&responseSMS, P_1, P_2, P_3)); + } +} // vim:ts=4 diff --git a/src/cpp/ripple/HttpsClient.h b/src/cpp/ripple/HttpsClient.h index 909594efe..a433c0a9d 100644 --- a/src/cpp/ripple/HttpsClient.h +++ b/src/cpp/ripple/HttpsClient.h @@ -116,6 +116,8 @@ public: std::size_t responseMax, boost::posix_time::time_duration timeout, FUNCTION_TYPE complete); + + static void sendSMS(boost::asio::io_service& io_service, const std::string& strText); }; #endif // vim:ts=4 diff --git a/src/cpp/ripple/JobQueue.cpp b/src/cpp/ripple/JobQueue.cpp index d169a9dda..8ba9ca548 100644 --- a/src/cpp/ripple/JobQueue.cpp +++ b/src/cpp/ripple/JobQueue.cpp @@ -272,9 +272,7 @@ void JobQueue::IOThread(boost::mutex::scoped_lock& sl) NameThread("IO+"); try { - do - NameThread("IO+"); - while ((mIOService.poll_one() == 1) && !theApp->isShutdown()); + mIOService.poll(); } catch (...) { @@ -291,12 +289,19 @@ void JobQueue::threadEntry() while (1) { NameThread("waiting"); +// bool didIO = false; while (mJobSet.empty() && !mShuttingDown) { - if ((mIOThreadCount < mMaxIOThreadCount) && !theApp->isShutdown()) - IOThread(sl); - else +// if ((mIOThreadCount < mMaxIOThreadCount) && !didIO && !theApp->isShutdown()) +// { +// IOThread(sl); +// didIO = true; +// } +// else +// { mJobCond.wait(sl); +// didIO = false; +// } } if (mShuttingDown) diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index 737bda480..4a195cbca 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -44,7 +44,8 @@ Ledger::Ledger(const RippleAddress& masterID, uint64 startAmount) : mTotCoins(st } Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash, - uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, int closeFlags, int closeResolution, uint32 ledgerSeq) + uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, + int closeFlags, int closeResolution, uint32 ledgerSeq, bool& loaded) : mParentHash(parentHash), mTransHash(transHash), mAccountHash(accountHash), mTotCoins(totCoins), mLedgerSeq(ledgerSeq), mCloseTime(closeTime), mParentCloseTime(parentCloseTime), mCloseResolution(closeResolution), mCloseFlags(closeFlags), @@ -53,10 +54,27 @@ Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint25 mAccountStateMap(boost::make_shared(smtSTATE, accountHash)) { // This will throw if the root nodes are not available locally updateHash(); - if (mTransHash.isNonZero()) - mTransactionMap->fetchRoot(mTransHash); - if (mAccountHash.isNonZero()) - mAccountStateMap->fetchRoot(mAccountHash); + loaded = true; + try + { + if (mTransHash.isNonZero()) + mTransactionMap->fetchRoot(mTransHash); + } + catch (...) + { + loaded = false; + cLog(lsWARNING) << "Don't have TX root for ledger"; + } + try + { + if (mAccountHash.isNonZero()) + mAccountStateMap->fetchRoot(mAccountHash); + } + catch (...) + { + loaded = false; + cLog(lsWARNING) << "Don't have AS root for ledger"; + } mTransactionMap->setImmutable(); mAccountStateMap->setImmutable(); zeroFees(); @@ -461,7 +479,7 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus) const std::vector& accts = vt.second.getAffected(); if (!accts.empty()) { - std::string sql = "INSERT INTO AccountTransactions (TransID, Account, LedgerSeq) VALUES "; + std::string sql = "INSERT INTO AccountTransactions (TransID, Account, LedgerSeq, TxnSeq) VALUES "; bool first = true; for (std::vector::const_iterator it = accts.begin(), end = accts.end(); it != end; ++it) { @@ -477,6 +495,8 @@ void Ledger::saveAcceptedLedger(Job&, bool fromConsensus) sql += it->humanAccountID(); sql += "',"; sql += boost::lexical_cast(getLedgerSeq()); + sql += ","; + sql += boost::lexical_cast(vt.second.getTxnSeq()); sql += ")"; } sql += ";"; @@ -518,14 +538,13 @@ Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) Database* db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); - static SqliteStatement pSt(db->getSqliteDB(), "SELECT " + SqliteStatement pSt(db->getSqliteDB(), "SELECT " "LedgerHash,PrevHash,AccountSetHash,TransSetHash,TotalCoins," "ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags,LedgerSeq" " from Ledgers WHERE LedgerSeq = ?;"); pSt.bind(1, ledgerIndex); ledger = getSQL1(&pSt); - pSt.reset(); } if (ledger) Ledger::getSQL2(ledger); @@ -539,14 +558,13 @@ Ledger::pointer Ledger::loadByHash(const uint256& ledgerHash) Database* db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); - static SqliteStatement pSt(db->getSqliteDB(), "SELECT " + SqliteStatement pSt(db->getSqliteDB(), "SELECT " "LedgerHash,PrevHash,AccountSetHash,TransSetHash,TotalCoins," "ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags,LedgerSeq" " from Ledgers WHERE LedgerHash = ?;"); pSt.bind(1, ledgerHash.GetHex()); ledger = getSQL1(&pSt); - pSt.reset(); } if (ledger) { @@ -593,13 +611,13 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) return Ledger::pointer(); db->getStr("LedgerHash", hash); - ledgerHash.SetHex(hash, true); + ledgerHash.SetHexExact(hash); db->getStr("PrevHash", hash); - prevHash.SetHex(hash, true); + prevHash.SetHexExact(hash); db->getStr("AccountSetHash", hash); - accountHash.SetHex(hash, true); + accountHash.SetHexExact(hash); db->getStr("TransSetHash", hash); - transHash.SetHex(hash, true); + transHash.SetHexExact(hash); totCoins = db->getBigInt("TotalCoins"); closingTime = db->getBigInt("ClosingTime"); prevClosingTime = db->getBigInt("PrevClosingTime"); @@ -610,8 +628,11 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) } // CAUTION: code below appears in two places - Ledger::pointer ret = boost::make_shared(prevHash, transHash, accountHash, totCoins, - closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq); + bool loaded; + Ledger::pointer ret(new Ledger(prevHash, transHash, accountHash, totCoins, + closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq, loaded)); + if (!loaded) + return Ledger::pointer(); ret->setClosed(); if (theApp->getOPs().haveLedger(ledgerSeq)) ret->setAccepted(); @@ -650,10 +671,10 @@ Ledger::pointer Ledger::getSQL1(SqliteStatement *stmt) unsigned closeFlags; std::string hash; - ledgerHash.SetHex(stmt->peekString(0), true); - prevHash.SetHex(stmt->peekString(1), true); - accountHash.SetHex(stmt->peekString(2), true); - transHash.SetHex(stmt->peekString(3), true); + ledgerHash.SetHexExact(stmt->peekString(0)); + prevHash.SetHexExact(stmt->peekString(1)); + accountHash.SetHexExact(stmt->peekString(2)); + transHash.SetHexExact(stmt->peekString(3)); totCoins = stmt->getInt64(4); closingTime = stmt->getUInt32(5); prevClosingTime = stmt->getUInt32(6); @@ -662,8 +683,12 @@ Ledger::pointer Ledger::getSQL1(SqliteStatement *stmt) ledgerSeq = stmt->getUInt32(9); // CAUTION: code below appears in two places - return boost::make_shared(prevHash, transHash, accountHash, totCoins, - closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq); + bool loaded; + Ledger::pointer ret(new Ledger(prevHash, transHash, accountHash, totCoins, + closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq, loaded)); + if (!loaded) + return Ledger::pointer(); + return ret; } void Ledger::getSQL2(Ledger::ref ret) @@ -692,7 +717,7 @@ uint256 Ledger::getHashByIndex(uint32 ledgerIndex) db->endIterRows(); } - ret.SetHex(hash, true); + ret.SetHexExact(hash); return ret; } @@ -703,7 +728,7 @@ bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& DatabaseCon *con = theApp->getLedgerDB(); ScopedLock sl(con->getDBLock()); - static SqliteStatement pSt(con->getDB()->getSqliteDB(), + SqliteStatement pSt(con->getDB()->getSqliteDB(), "SELECT LedgerHash,PrevHash FROM Ledgers INDEXED BY SeqLedger Where LedgerSeq = ?;"); pSt.bind(1, ledgerIndex); @@ -711,21 +736,18 @@ bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& int ret = pSt.step(); if (pSt.isDone(ret)) { - pSt.reset(); cLog(lsTRACE) << "Don't have ledger " << ledgerIndex; return false; } if (!pSt.isRow(ret)) { - pSt.reset(); assert(false); cLog(lsFATAL) << "Unexpected statement result " << ret; return false; } - ledgerHash.SetHex(pSt.peekString(0), true); - parentHash.SetHex(pSt.peekString(1), true); - pSt.reset(); + ledgerHash.SetHexExact(pSt.peekString(0)); + parentHash.SetHexExact(pSt.peekString(1)); return true; @@ -746,8 +768,8 @@ bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& db->endIterRows(); } - ledgerHash.SetHex(hash, true); - parentHash.SetHex(prevHash, true); + ledgerHash.SetHexExact(hash); + parentHash.SetHexExact(prevHash); assert(ledgerHash.isNonZero() && ((ledgerIndex == 0) || parentHash.isNonZero())); @@ -763,7 +785,7 @@ std::map< uint32, std::pair > Ledger::getHashesByIndex(uint32 DatabaseCon *con = theApp->getLedgerDB(); ScopedLock sl(con->getDBLock()); - static SqliteStatement pSt(con->getDB()->getSqliteDB(), + SqliteStatement pSt(con->getDB()->getSqliteDB(), "SELECT LedgerSeq,LedgerHash,PrevHash FROM Ledgers INDEXED BY SeqLedger " "WHERE LedgerSeq >= ? AND LedgerSeq <= ?;"); @@ -776,17 +798,11 @@ std::map< uint32, std::pair > Ledger::getHashesByIndex(uint32 { int r = pSt.step(); if (pSt.isDone(r)) - { - pSt.reset(); return ret; - } if (!pSt.isRow(r)) - { - pSt.reset(); return ret; - } - hashes.first.SetHex(pSt.peekString(1), true); - hashes.second.SetHex(pSt.peekString(2), true); + hashes.first.SetHexExact(pSt.peekString(1)); + hashes.second.SetHexExact(pSt.peekString(2)); ret[pSt.getUInt32(0)] = hashes; } while(1); diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index 5dda84880..36922a179 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -109,7 +109,7 @@ public: Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash, uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, int closeFlags, int closeResolution, - uint32 ledgerSeq); // used for database ledgers + uint32 ledgerSeq, bool& loaded); // used for database ledgers Ledger(const std::vector& rawLedger, bool hasPrefix); Ledger(const std::string& rawLedger, bool hasPrefix); diff --git a/src/cpp/ripple/LedgerAcquire.cpp b/src/cpp/ripple/LedgerAcquire.cpp index 30b61e0af..2ca423e98 100644 --- a/src/cpp/ripple/LedgerAcquire.cpp +++ b/src/cpp/ripple/LedgerAcquire.cpp @@ -196,7 +196,7 @@ void LedgerAcquire::onTimer(bool progress) if (getTimeouts() > LEDGER_TIMEOUT_COUNT) { - cLog(lsWARNING) << "Too many timeouts for ledger " << mHash; + cLog(lsWARNING) << "Too many timeouts( " << getTimeouts() << ") for ledger " << mHash; setFailed(); done(); return; diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index d96a820fd..5f58c2593 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -150,8 +150,9 @@ void LedgerEntrySet::entryCreate(SLE::ref sle) case taaMODIFY: throw std::runtime_error("Create after modify"); + case taaCREATE: - throw std::runtime_error("Create after create"); // We could make this work + throw std::runtime_error("Create after create"); // This could be made to work case taaCACHED: throw std::runtime_error("Create after cache"); @@ -532,7 +533,7 @@ TER LedgerEntrySet::dirCount(const uint256& uRootIndex, uint32& uCount) // <-- uNodeDir: For deletion, present to make dirDelete efficient. // --> uRootIndex: The index of the base of the directory. Nodes are based off of this. // --> uLedgerIndex: Value to add to directory. -// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. +// Only append. This allow for things that watch append only structure to just monitor from the last node on ward. // Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. TER LedgerEntrySet::dirAdd( uint64& uNodeDir, @@ -850,7 +851,7 @@ bool LedgerEntrySet::dirFirst( sleNode = entryCache(ltDIR_NODE, uRootIndex); uDirEntry = 0; - assert(sleNode); // We never probe for directories. + assert(sleNode); // Never probe for directories. return LedgerEntrySet::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); } @@ -1243,13 +1244,38 @@ TER LedgerEntrySet::trustCreate( ownerCountAdjust(!bSetDst ? uSrcAccountID : uDstAccountID, 1, sleAccount); - sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); + sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); // ONLY: Create ripple balance. } return terResult; } -// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. +TER LedgerEntrySet::trustDelete(SLE::ref sleRippleState, const uint160& uLowAccountID, const uint160& uHighAccountID) +{ + bool bLowNode = sleRippleState->isFieldPresent(sfLowNode); // Detect legacy dirs. + bool bHighNode = sleRippleState->isFieldPresent(sfHighNode); + uint64 uLowNode = sleRippleState->getFieldU64(sfLowNode); + uint64 uHighNode = sleRippleState->getFieldU64(sfHighNode); + TER terResult; + + cLog(lsTRACE) << "trustDelete: Deleting ripple line: low"; + terResult = dirDelete(false, uLowNode, Ledger::getOwnerDirIndex(uLowAccountID), sleRippleState->getIndex(), false, !bLowNode); + + if (tesSUCCESS == terResult) + { + cLog(lsTRACE) << "trustDelete: Deleting ripple line: high"; + terResult = dirDelete(false, uHighNode, Ledger::getOwnerDirIndex(uHighAccountID), sleRippleState->getIndex(), false, !bHighNode); + } + + cLog(lsINFO) << "trustDelete: Deleting ripple line: state"; + entryDelete(sleRippleState); + + return terResult; +} + +// Direct send w/o fees: +// - Redeeming IOUs and/or sending sender's own IOUs. +// - Create trust line of needed. TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) { uint160 uIssuerID = saAmount.getIssuer(); @@ -1306,14 +1332,46 @@ TER LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRecei % saAmount.getFullText() % saBalance.getFullText()); + bool bDelete = false; + uint32 uFlags; + + // YYY Could skip this if rippling in reverse. + if (saBefore.isPositive() // Sender balance was positive. + && !saBalance.isPositive() // Sender is zero or negative. + && isSetBit((uFlags = sleRippleState->getFieldU32(sfFlags)), !bSenderHigh ? lsfLowReserve : lsfHighReserve) // Sender reserve is set. + && !sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit) // Sender trust limit is 0. + && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) // Sender quality in is 0. + && !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut)) // Sender quality out is 0. + { + // Clear the reserve of the sender, possibly delete the line! + SLE::pointer sleSender = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)); + + ownerCountAdjust(uSenderID, -1, sleSender); + + sleRippleState->setFieldU32(sfFlags, uFlags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve)); // Clear reserve flag. + + bDelete = !saBalance // Balance is zero. + && !isSetBit(uFlags, bSenderHigh ? lsfLowReserve : lsfHighReserve); // Receiver reserve is clear. + } + if (bSenderHigh) saBalance.negate(); - sleRippleState->setFieldAmount(sfBalance, saBalance); + // Want to reflect balance to zero even if we are deleting line. + sleRippleState->setFieldAmount(sfBalance, saBalance); // ONLY: Adjust ripple balance. - entryModify(sleRippleState); + if (bDelete) + { + terResult = trustDelete(sleRippleState, + bSenderHigh ? uReceiverID : uSenderID, + !bSenderHigh ? uReceiverID : uSenderID); + } + else + { + entryModify(sleRippleState); - terResult = tesSUCCESS; + terResult = tesSUCCESS; + } } return terResult; @@ -1374,6 +1432,7 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv } else if (saAmount.isNative()) { + // XRP send which does not check reserve and can do pure adjustment. SLE::pointer sleSender = !!uSenderID ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) : SLE::pointer(); @@ -1398,14 +1457,14 @@ TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiv } else { - sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); + sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); // Decrement XRP balance. entryModify(sleSender); } } if (tesSUCCESS == terResult && sleReceiver) { - sleReceiver->setFieldAmount(sfBalance, sleReceiver->getFieldAmount(sfBalance) + saAmount); + sleReceiver->setFieldAmount(sfBalance, sleReceiver->getFieldAmount(sfBalance) + saAmount); // Increment XRP balance. entryModify(sleReceiver); } diff --git a/src/cpp/ripple/LedgerEntrySet.h b/src/cpp/ripple/LedgerEntrySet.h index 09f9bb0ce..44e61b53f 100644 --- a/src/cpp/ripple/LedgerEntrySet.h +++ b/src/cpp/ripple/LedgerEntrySet.h @@ -156,6 +156,7 @@ public: const STAmount& saSrcLimit, const uint32 uSrcQualityIn = 0, const uint32 uSrcQualityOut = 0); + TER trustDelete(SLE::ref sleRippleState, const uint160& uLowAccountID, const uint160& uHighAccountID); Json::Value getJson(int) const; void calcRawMeta(Serializer&, TER result, uint32 index); diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 67381e195..a9471493a 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -74,10 +74,10 @@ void LedgerMaster::switchLedgers(Ledger::pointer lastClosed, Ledger::pointer cur mFinalizedLedger->setClosed(); mFinalizedLedger->setAccepted(); mCurrentLedger = current; - } - assert(!mCurrentLedger->isClosed()); - mEngine.setLedger(mCurrentLedger); + assert(!mCurrentLedger->isClosed()); + mEngine.setLedger(mCurrentLedger); + } checkAccept(lastClosed->getHash(), lastClosed->getLedgerSeq()); } @@ -106,7 +106,7 @@ Ledger::pointer LedgerMaster::closeLedger(bool recover) ++recovers; } catch (...) - { + { // CHECKME: We got a few of these cLog(lsWARNING) << "Held transaction throws"; } } @@ -122,6 +122,7 @@ Ledger::pointer LedgerMaster::closeLedger(bool recover) TER LedgerMaster::doTransaction(SerializedTransaction::ref txn, TransactionEngineParams params, bool& didApply) { + boost::recursive_mutex::scoped_lock sl(mLock); TER result = mEngine.applyTransaction(*txn, params, didApply); // if (didApply) theApp->getOPs().pubProposedTransaction(mEngine.getLedger(), txn, result); diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 8b3e8aa98..7415a605b 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1093,13 +1093,15 @@ std::string boost::str(boost::format("SELECT %s FROM " "AccountTransactions INNER JOIN Transactions ON Transactions.TransID = AccountTransactions.TransID " "WHERE Account = '%s' %s %s " - "ORDER BY AccountTransactions.LedgerSeq %s, AccountTransactions.TransID %s LIMIT %u, %u;") + "ORDER BY AccountTransactions.LedgerSeq %s, AccountTransactions.TxnSeq %s, AccountTransactions.TransID %s " + "LIMIT %u, %u;") % selection % account.humanAccountID() % maxClause % minClause % (descending ? "DESC" : "ASC") % (descending ? "DESC" : "ASC") + % (descending ? "DESC" : "ASC") % boost::lexical_cast(offset) % boost::lexical_cast(numberOfResults) ); @@ -1375,7 +1377,7 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, SerializedTransac } } ALTransaction alt(stTxn, terResult); - cLog(lsTRACE) << "pubProposed: " << alt.getJson(0); + cLog(lsTRACE) << "pubProposed: " << alt.getJson(); pubAccountTransaction(lpCurrent, ALTransaction(stTxn, terResult), false); } @@ -1429,7 +1431,7 @@ void NetworkOPs::pubLedger(Ledger::ref accepted) { BOOST_FOREACH(const AcceptedLedger::value_type& vt, alpAccepted->getMap()) { - cLog(lsTRACE) << "pubAccepted: " << vt.second.getJson(0); + cLog(lsTRACE) << "pubAccepted: " << vt.second.getJson(); pubValidatedTransaction(lpAccepted, vt.second); } } diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index c1438825f..0a3a1b040 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -78,18 +78,26 @@ PathOption::PathOption(PathOption::pointer other) } #endif +// quality, length, liquidity, index +typedef boost::tuple path_LQ_t; + // Lower numbers have better quality. Sort higher quality first. -static bool bQualityCmp( - std::pair< std::pair, unsigned int> a, - std::pair< std::pair, unsigned int> b) +static bool bQualityCmp(const path_LQ_t& a, const path_LQ_t&b) { - if (a.first.first != b.first.first) - return a.first.first < b.first.first; + // 1) Higher quality (lower cost) is better + if (a.get<0>() != b.get<0>()) + return a.get<0>() < b.get<0>(); - if (a.first.second != b.first.second) - return a.first.second < b.first.second; + // 2) More liquidity (higher volume) is better + if (a.get<2>() != b.get<2>()) + return a.get<2>() > b.get<2>(); - return a.second < b.second; // FIXME: this biases accounts + // 3) Shorter paths are better + if (a.get<1>() != b.get<1>()) + return a.get<1>() < b.get<1>(); + + // 4) Tie breaker + return a.get<3>() > b.get<3>(); } // Return true, if path is a default path with an element. @@ -125,13 +133,15 @@ bool Pathfinder::bDefaultPath(const STPath& spPath) // Expand the current path. pspCurrent->setExpanded(lesActive, spPath, mDstAccountID, mSrcAccountID); + // XXX Need to report or act on errors returned in pspCurrent->terStatus. // Determine if expanded current path is the default. // When path is a default (implied). Don't need to add it to return set. bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes; cLog(lsTRACE) << "bDefaultPath: expanded path: " << pspCurrent->getJson(); - cLog(lsTRACE) << "bDefaultPath: default path: indirect: " << spPath.getJson(0); + cLog(lsTRACE) << "bDefaultPath: source path: " << spPath.getJson(0); + cLog(lsTRACE) << "bDefaultPath: default path: " << mPsDefault->getJson(); return bDefault; } @@ -376,6 +386,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } else if (!speEnd.mCurrencyID) { + // XXX Might restrict the number of times bridging through XRP. + // Cursor is for XRP, continue with qualifying books: XRP -> non-XRP std::vector xrpBooks; theApp->getOrderBookDB().getBooksByTakerPays(ACCOUNT_XRP, CURRENCY_XRP, xrpBooks); @@ -383,9 +395,9 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax { // New end is an order book with the currency and issuer. - // Don't allow looping through same order books. if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut())) { + // Not a order book already in path. STPath spNew(spPath); STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()); STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()); @@ -395,6 +407,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax cLog(lsDEBUG) << boost::str(boost::format("findPaths: XRP -> %s/%s") +// % STAmount::createHumanCurrency(book->getCurrencyOut()) +// % RippleAddress::createHumanAccountID(book->getIssuerOut()) % STAmount::createHumanCurrency(speBook.mCurrencyID) % RippleAddress::createHumanAccountID(speBook.mIssuerID)); @@ -508,7 +522,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax spNew.mPath.push_back(speBook); // Add the order book. - if (!book->getCurrencyOut()) + if (!!book->getCurrencyOut()) { // For non-XRP out, don't end on the book, add the issuing account. STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()); @@ -538,7 +552,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax // Only filter, sort, and limit if have non-default paths. if (iLimit) { - std::vector< std::pair< std::pair, unsigned int> > vMap; + std::vector vMap; // Build map of quality to entry. for (int i = vspResults.size(); i--;) @@ -587,7 +601,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax % uQuality % spCurrent.getJson(0)); - vMap.push_back(std::make_pair(std::make_pair(uQuality, spCurrent.mPath.size()), i)); + vMap.push_back(path_LQ_t(uQuality, spCurrent.mPath.size(), saDstAmountAct, i)); } else { @@ -600,18 +614,69 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax if (vMap.size()) { - iLimit = std::min(iMaxPaths, (unsigned int) vMap.size()); - - bFound = true; - std::sort(vMap.begin(), vMap.end(), bQualityCmp); // Lower is better and should be first. - // Output best quality entries. - for (int i = 0; i != iLimit; ++i) - { - spsDst.addPath(vspResults[vMap[i].second]); + STAmount remaining = mDstAmount; + if (bFound) + { // must subtract liquidity in default path from remaining amount + try + { + STAmount saMaxAmountAct, saDstAmountAct; + std::vector vpsExpanded; + LedgerEntrySet lesSandbox(lesActive.duplicate()); + + TER result = RippleCalc::rippleCalc( + lesSandbox, + saMaxAmountAct, + saDstAmountAct, + vpsExpanded, + mSrcAmount, + mDstAmount, + mDstAccountID, + mSrcAccountID, + STPathSet(), + true, // allow partial payment + false, + false, // don't suppress default paths, that's the point + true); + + if (tesSUCCESS == result) + { + cLog(lsDEBUG) << "Default path contributes: " << saDstAmountAct; + remaining -= saDstAmountAct; + } + else + { + cLog(lsDEBUG) << "Default path fails: " << transToken(result); + } + } + catch (...) + { + cLog(lsDEBUG) << "Default path causes exception"; + } } + for (int i = 0, iPathsLeft = iMaxPaths; (iPathsLeft > 0) && (i < vMap.size()); ++i) + { + path_LQ_t& lqt = vMap[i]; + if ((iPathsLeft != 1) || (lqt.get<2>() >= remaining)) + { // last path must fill + --iPathsLeft; + remaining -= lqt.get<2>(); + spsDst.addPath(vspResults[lqt.get<3>()]); + } + else + cLog(lsDEBUG) << "Skipping a non-filling path: " << vspResults[lqt.get<3>()].getJson(0); + } + + if (remaining.isPositive()) + { + bFound = false; + cLog(lsINFO) << "Paths could not send " << remaining << " of " << mDstAmount; + } + else + bFound = true; + cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: %s") % spsDst.getJson(0)); } else diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index 707b0ecf0..3a5f12a50 100644 --- a/src/cpp/ripple/PaymentTransactor.cpp +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -3,7 +3,7 @@ #include "RippleCalc.h" #include "Application.h" -#define RIPPLE_PATHS_MAX 3 +#define RIPPLE_PATHS_MAX 6 SETUP_LOG(); diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index 55bb4f4e0..ea3474bbd 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -1441,7 +1441,7 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) selectedPeer->sendPacket(boost::make_shared(packet, ripple::mtGET_LEDGER), false); return; } - cLog(lsERROR) << "We do not have the map our peer wants"; + cLog(lsERROR) << "We do not have the map our peer wants " << getIP(); punishPeer(LT_InvalidRequest); return; } diff --git a/src/cpp/ripple/PlatRand.cpp b/src/cpp/ripple/PlatRand.cpp index 6db620311..e367cbf84 100644 --- a/src/cpp/ripple/PlatRand.cpp +++ b/src/cpp/ripple/PlatRand.cpp @@ -8,12 +8,9 @@ bool AddSystemEntropy() { // Get entropy from the Windows crypto provider - RAND_screen(); // this isn't really that safe since it only works for end users not servers - -/* TODO: you need the cryptoAPI installed I think for the below to work. I suppose we should require people to install this to build the windows version char name[512], rand[128]; DWORD count = 500; - HCRYPTOPROV cryptoHandle; + HCRYPTPROV cryptoHandle; if (!CryptGetDefaultProvider(PROV_RSA_FULL, NULL, CRYPT_MACHINE_DEFAULT, name, &count)) { @@ -43,7 +40,6 @@ bool AddSystemEntropy() CryptReleaseContext(cryptoHandle, 0); RAND_seed(rand, 128); -*/ return true; } diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index dc798aefa..c33649a74 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -1334,7 +1334,7 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest, int& cost) jvEntry["source_amount"] = saMaxAmountAct.getJson(0); // jvEntry["paths_expanded"] = vpsExpanded.getJson(0); - jvEntry["paths_canonical"] = spsCanonical.getJson(0); + jvEntry["paths_canonical"] = Json::arrayValue; // spsCanonical.getJson(0); jvEntry["paths_computed"] = spsComputed.getJson(0); jvArray.append(jvEntry); @@ -2235,6 +2235,15 @@ Json::Value RPCHandler::doUnlScore(Json::Value, int& cost) return "scoring requested"; } +Json::Value RPCHandler::doSMS(Json::Value jvRequest, int& cost) +{ + if (!jvRequest.isMember("text")) + return rpcError(rpcINVALID_PARAMS); + + HttpsClient::sendSMS(theApp->getIOService(), jvRequest["text"].asString()); + + return "sms dispatched"; +} Json::Value RPCHandler::doStop(Json::Value, int& cost) { theApp->stop(); @@ -3250,6 +3259,7 @@ Json::Value RPCHandler::doCommand(const Json::Value& jvRequest, int iRole, int & { "submit", &RPCHandler::doSubmit, false, optCurrent }, { "server_info", &RPCHandler::doServerInfo, false, optNone }, { "server_state", &RPCHandler::doServerState, false, optNone }, + { "sms", &RPCHandler::doSMS, true, optNone }, { "stop", &RPCHandler::doStop, true, optNone }, { "transaction_entry", &RPCHandler::doTransactionEntry, false, optCurrent }, { "tx", &RPCHandler::doTx, false, optNetwork }, diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index b716ebc55..5ce16c45f 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -76,6 +76,7 @@ class RPCHandler Json::Value doServerState(Json::Value params, int& cost); // for machines Json::Value doSessionClose(Json::Value params, int& cost); Json::Value doSessionOpen(Json::Value params, int& cost); + Json::Value doSMS(Json::Value params, int& cost); Json::Value doStop(Json::Value params, int& cost); Json::Value doSign(Json::Value params, int& cost); Json::Value doSubmit(Json::Value params, int& cost); diff --git a/src/cpp/ripple/RippleAddress.cpp b/src/cpp/ripple/RippleAddress.cpp index 7ef204277..e2c0efcfa 100644 --- a/src/cpp/ripple/RippleAddress.cpp +++ b/src/cpp/ripple/RippleAddress.cpp @@ -1,3 +1,4 @@ +#include #include "RippleAddress.h" #include diff --git a/src/cpp/ripple/RippleAddress.h b/src/cpp/ripple/RippleAddress.h index 61a71a28e..d12cf101b 100644 --- a/src/cpp/ripple/RippleAddress.h +++ b/src/cpp/ripple/RippleAddress.h @@ -1,5 +1,5 @@ -#ifndef __NEWCOIN_ADDRESS__ -#define __NEWCOIN_ADDRESS__ +#ifndef __RIPPLE_ADDRESS__ +#define __RIPPLE_ADDRESS__ #include "base58.h" #include "uint256.h" diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index a8fe83207..89eba9d58 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -35,6 +35,55 @@ bool PaymentNode::operator==(const PaymentNode& pnOther) const { && pnOther.uIssuerID == uIssuerID; } +// This is for debugging not end users. Output names can be changed without warning. +Json::Value PaymentNode::getJson() const +{ + Json::Value jvNode(Json::objectValue); + Json::Value jvFlags(Json::arrayValue); + + jvNode["type"] = uFlags; + + if (isSetBit(uFlags, STPathElement::typeAccount) || !!uAccountID) + jvFlags.append(!!isSetBit(uFlags, STPathElement::typeAccount) == !!uAccountID ? "account" : "-account"); + + if (isSetBit(uFlags, STPathElement::typeCurrency) || !!uCurrencyID) + jvFlags.append(!!isSetBit(uFlags, STPathElement::typeCurrency) == !!uCurrencyID ? "currency" : "-currency"); + + if (isSetBit(uFlags, STPathElement::typeIssuer) || !!uIssuerID) + jvFlags.append(!!isSetBit(uFlags, STPathElement::typeIssuer) == !!uIssuerID ? "issuer" : "-issuer"); + + jvNode["flags"] = jvFlags; + + if (!!uAccountID) + jvNode["account"] = RippleAddress::createHumanAccountID(uAccountID); + + if (!!uCurrencyID) + jvNode["currency"] = STAmount::createHumanCurrency(uCurrencyID); + + if (!!uIssuerID) + jvNode["issuer"] = RippleAddress::createHumanAccountID(uIssuerID); + + if (saRevRedeem) + jvNode["rev_redeem"] = saRevRedeem.getFullText(); + + if (saRevIssue) + jvNode["rev_issue"] = saRevIssue.getFullText(); + + if (saRevDeliver) + jvNode["rev_deliver"] = saRevDeliver.getFullText(); + + if (saFwdRedeem) + jvNode["fwd_redeem"] = saFwdRedeem.getFullText(); + + if (saFwdIssue) + jvNode["fwd_issue"] = saFwdIssue.getFullText(); + + if (saFwdDeliver) + jvNode["fwd_deliver"] = saFwdDeliver.getFullText(); + + return jvNode; +} + // // PathState implementation // @@ -160,6 +209,12 @@ TER PathState::pushNode( terResult = temBAD_PATH; } + else if (!bAccount && !bCurrency && !bIssuer) + { + cLog(lsDEBUG) << "pushNode: offer must specify at least currency or issuer."; + + terResult = temBAD_PATH; + } else if (bAccount) { // Account link @@ -267,7 +322,9 @@ TER PathState::pushNode( } if (tesSUCCESS == terResult) + { vpnNodes.push_back(pnCur); + } } else { @@ -305,6 +362,7 @@ TER PathState::pushNode( vpnNodes.push_back(pnCur); } } + cLog(lsTRACE) << boost::str(boost::format("pushNode< : %s") % transToken(terResult)); return terResult; @@ -329,7 +387,7 @@ void PathState::setExpanded( const uint160 uOutIssuerID = saOutReq.getIssuer(); const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP. - // cLog(lsDEBUG) << boost::str(boost::format("setExpanded>")); + cLog(lsDEBUG) << boost::str(boost::format("setExpanded> %s") % spSourcePath.getJson(0)); lesEntries = lesSource.duplicate(); @@ -726,43 +784,7 @@ Json::Value PathState::getJson() const BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) { - Json::Value jvNode(Json::objectValue); - - Json::Value jvFlags(Json::arrayValue); - - if (pnNode.uFlags & STPathElement::typeAccount) - jvFlags.append("account"); - - jvNode["flags"] = jvFlags; - - if (pnNode.uFlags & STPathElement::typeAccount) - jvNode["account"] = RippleAddress::createHumanAccountID(pnNode.uAccountID); - - if (!!pnNode.uCurrencyID) - jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); - - if (!!pnNode.uIssuerID) - jvNode["issuer"] = RippleAddress::createHumanAccountID(pnNode.uIssuerID); - - if (pnNode.saRevRedeem) - jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); - - if (pnNode.saRevIssue) - jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); - - if (pnNode.saRevDeliver) - jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); - - if (pnNode.saFwdRedeem) - jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); - - if (pnNode.saFwdIssue) - jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); - - if (pnNode.saFwdDeliver) - jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); - - jvNodes.append(jvNode); + jvNodes.append(pnNode.getJson()); } jvPathState["status"] = terStatus; diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index c23f2be83..d1cdc9259 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -56,6 +56,8 @@ protected: public: bool operator==(const PaymentNode& pnOther) const; + + Json::Value getJson() const; }; // account id, currency id, issuer id :: node diff --git a/src/cpp/ripple/SHAMap.cpp b/src/cpp/ripple/SHAMap.cpp index 676290cca..a08028047 100644 --- a/src/cpp/ripple/SHAMap.cpp +++ b/src/cpp/ripple/SHAMap.cpp @@ -582,7 +582,7 @@ bool SHAMap::addGiveItem(SHAMapItem::ref item, bool isTransaction, bool hasMeta) stack.pop(); if (node->isLeaf() && (node->peekItem()->getTag() == tag)) - throw std::runtime_error("addGiveItem ends on leaf with same tag"); + return false; uint256 prevHash; returnNode(node, true); diff --git a/src/cpp/ripple/SNTPClient.cpp b/src/cpp/ripple/SNTPClient.cpp index cf3d7b993..c3febd27b 100644 --- a/src/cpp/ripple/SNTPClient.cpp +++ b/src/cpp/ripple/SNTPClient.cpp @@ -81,7 +81,7 @@ void SNTPClient::resolveComplete(const boost::system::error_code& error, boost:: query.mReceivedReply = false; query.mLocalTimeSent = now; getRand(reinterpret_cast(&query.mQueryNonce), sizeof(query.mQueryNonce)); - reinterpret_cast(SNTPQueryData)[NTP_OFF_XMITTS_INT] = time(NULL) + NTP_UNIX_OFFSET; + reinterpret_cast(SNTPQueryData)[NTP_OFF_XMITTS_INT] = static_cast(time(NULL)) + NTP_UNIX_OFFSET; reinterpret_cast(SNTPQueryData)[NTP_OFF_XMITTS_FRAC] = query.mQueryNonce; mSocket.async_send_to(boost::asio::buffer(SNTPQueryData, 48), *sel, boost::bind(&SNTPClient::sendComplete, this, @@ -148,7 +148,7 @@ void SNTPClient::processReply() return; } - time_t now = time(NULL); + int64 now = static_cast(time(NULL)); timev -= now; timev -= NTP_UNIX_OFFSET; diff --git a/src/cpp/ripple/TaggedCache.h b/src/cpp/ripple/TaggedCache.h index cd89c4fe2..8b12d80da 100644 --- a/src/cpp/ripple/TaggedCache.h +++ b/src/cpp/ripple/TaggedCache.h @@ -99,7 +99,7 @@ template void TaggedCache::setTa boost::recursive_mutex::scoped_lock sl(mLock); mTargetSize = s; if (s > 0) - mCache.rehash((s + (s >> 2)) / mCache.max_load_factor() + 1); + mCache.rehash(static_cast((s + (s >> 2)) / mCache.max_load_factor() + 1)); Log(lsDEBUG, TaggedCachePartition) << mName << " target size set to " << s; } @@ -136,7 +136,7 @@ template void TaggedCache::sweep int target = mLastSweep - mTargetAge; int cacheRemovals = 0, mapRemovals = 0, cc = 0; - if ((mTargetSize != 0) && (mCache.size() > mTargetSize)) + if ((mTargetSize != 0) && (static_cast(mCache.size()) > mTargetSize)) { target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); if (target > (mLastSweep - 2)) diff --git a/src/cpp/ripple/TransactionAcquire.cpp b/src/cpp/ripple/TransactionAcquire.cpp index 69630d4d5..662acf47d 100644 --- a/src/cpp/ripple/TransactionAcquire.cpp +++ b/src/cpp/ripple/TransactionAcquire.cpp @@ -45,6 +45,13 @@ void TransactionAcquire::done() void TransactionAcquire::onTimer(bool progress) { + if (getTimeouts() > 10) + { + cLog(lsWARNING) << "Giving up on TX set " << getHash(); + mFailed = true; + done(); + return; + } if (!getPeerCount()) { // out of peers cLog(lsWARNING) << "Out of peers for TX set " << getHash(); diff --git a/src/cpp/ripple/TransactionCheck.cpp b/src/cpp/ripple/TransactionCheck.cpp index 50b1a7f7c..433bd6960 100644 --- a/src/cpp/ripple/TransactionCheck.cpp +++ b/src/cpp/ripple/TransactionCheck.cpp @@ -7,5 +7,10 @@ bool TransactionEngine::checkInvariants(TER result, const SerializedTransaction& txn, TransactionEngineParams params) { + +// 1) Make sure transaction changed account sequence number to correct value + +// 2) Make sure transaction didn't create XRP + return true; } diff --git a/src/cpp/ripple/TransactionEngine.cpp b/src/cpp/ripple/TransactionEngine.cpp index 1c0049fb2..1747f114d 100644 --- a/src/cpp/ripple/TransactionEngine.cpp +++ b/src/cpp/ripple/TransactionEngine.cpp @@ -176,12 +176,20 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa if (isSetBit(params, tapOPEN_LEDGER)) { if (!mLedger->addTransaction(txID, s)) + { + cLog(lsFATAL) << "Tried to add transaction to open ledger that already had it"; assert(false); + throw std::runtime_error("Duplicate transaction applied"); + } } else { if (!mLedger->addTransaction(txID, s, m)) - assert(false); + { + cLog(lsFATAL) << "Tried to add transaction to ledger that already had it"; + assert(false); + throw std::runtime_error("Duplicate transaction applied to closed ledger"); + } // Charge whatever fee they specified. STAmount saPaid = txn.getTransactionFee(); diff --git a/src/cpp/ripple/Transactor.cpp b/src/cpp/ripple/Transactor.cpp index 7534546d5..c7924b7a8 100644 --- a/src/cpp/ripple/Transactor.cpp +++ b/src/cpp/ripple/Transactor.cpp @@ -142,7 +142,8 @@ TER Transactor::checkSeq() cLog(lsWARNING) << "applyTransaction: past sequence number"; return tefPAST_SEQ; - }else + } + else { mTxnAccount->setFieldU32(sfSequence, t_seq + 1); } diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp index 9333d5125..00925ea6e 100644 --- a/src/cpp/ripple/TrustSetTransactor.cpp +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -252,22 +252,7 @@ TER TrustSetTransactor::doApply() { // Can delete. - bool bLowNode = sleRippleState->isFieldPresent(sfLowNode); // Detect legacy dirs. - bool bHighNode = sleRippleState->isFieldPresent(sfHighNode); - uint64 uLowNode = sleRippleState->getFieldU64(sfLowNode); - uint64 uHighNode = sleRippleState->getFieldU64(sfHighNode); - - cLog(lsTRACE) << "doTrustSet: Deleting ripple line: low"; - terResult = mEngine->getNodes().dirDelete(false, uLowNode, Ledger::getOwnerDirIndex(uLowAccountID), sleRippleState->getIndex(), false, !bLowNode); - - if (tesSUCCESS == terResult) - { - cLog(lsTRACE) << "doTrustSet: Deleting ripple line: high"; - terResult = mEngine->getNodes().dirDelete(false, uHighNode, Ledger::getOwnerDirIndex(uHighAccountID), sleRippleState->getIndex(), false, !bHighNode); - } - - cLog(lsINFO) << "doTrustSet: Deleting ripple line: state"; - mEngine->entryDelete(sleRippleState); + terResult = mEngine->getNodes().trustDelete(sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue() < uReserveCreate) // Reserve is not scaled by load. diff --git a/src/cpp/ripple/UpdateTables.cpp b/src/cpp/ripple/UpdateTables.cpp new file mode 100644 index 000000000..6d829d949 --- /dev/null +++ b/src/cpp/ripple/UpdateTables.cpp @@ -0,0 +1,108 @@ + +#include "Application.h" +#include "Log.h" + +static std::vector getSchema(DatabaseCon* dbc, const std::string& dbName) +{ + std::vector schema; + + std::string sql = "SELECT sql FROM sqlite_master WHERE tbl_name='"; + sql += dbName; + sql += "';"; + + SQL_FOREACH(dbc->getDB(), sql) + { + dbc->getDB()->getStr("sql", sql); + schema.push_back(sql); + } + + return schema; +} + +static bool schemaHas(DatabaseCon* dbc, const std::string& dbName, int line, const std::string& content) +{ + std::vector schema = getSchema(dbc, dbName); + if (static_cast(schema.size()) <= line) + { + Log(lsFATAL) << "Schema for " << dbName << " has too few lines"; + throw std::runtime_error("bad schema"); + } + return schema[line].find(content) != std::string::npos; +} + +static void addTxnSeqField() +{ + if (schemaHas(theApp->getTxnDB(), "AccountTransactions", 0, "TxnSeq")) + return; + Log(lsWARNING) << "Transaction sequence field is missing"; + + Database* db = theApp->getTxnDB()->getDB(); + + std::vector< std::pair > txIDs; + txIDs.reserve(300000); + + Log(lsINFO) << "Parsing transactions"; + int i = 0; + uint256 transID; + SQL_FOREACH(db, "SELECT TransID,TxnMeta FROM Transactions;") + { + std::vector rawMeta; + int metaSize = 2048; + rawMeta.resize(metaSize); + metaSize = db->getBinary("TxnMeta", &*rawMeta.begin(), rawMeta.size()); + if (metaSize > static_cast(rawMeta.size())) + { + rawMeta.resize(metaSize); + db->getBinary("TxnMeta", &*rawMeta.begin(), rawMeta.size()); + } + else rawMeta.resize(metaSize); + + std::string tid; + db->getStr("TransID", tid); + transID.SetHex(tid, true); + + if (rawMeta.size() == 0) + { + txIDs.push_back(std::make_pair(transID, -1)); + Log(lsINFO) << "No metadata for " << transID; + } + else + { + TransactionMetaSet m(transID, 0, rawMeta); + txIDs.push_back(std::make_pair(transID, m.getIndex())); + } + + if ((++i % 1000) == 0) + Log(lsINFO) << i << " transactions read"; + } + + Log(lsINFO) << "All " << i << " transactions read"; + + db->executeSQL("BEGIN TRANSACTION;"); + + Log(lsINFO) << "Dropping old index"; + db->executeSQL("DROP INDEX AcctTxIndex;"); + + Log(lsINFO) << "Altering table"; + db->executeSQL("ALTER TABLE AccountTransactions ADD COLUMN TxnSeq INTEGER;"); + + typedef std::pair u256_int_pair_t; + boost::format fmt("UPDATE AccountTransactions SET TxnSeq = %d WHERE TransID = '%s';"); + i = 0; + BOOST_FOREACH(u256_int_pair_t& t, txIDs) + { + db->executeSQL(boost::str(fmt % t.second % t.first.GetHex())); + if ((++i % 1000) == 0) + Log(lsINFO) << i << " transactions updated"; + } + + db->executeSQL("CREATE INDEX AcctTxIndex ON AccountTransactions(Account, LedgerSeq, TxnSeq, TransID);"); + db->executeSQL("END TRANSACTION;"); +} + +void Application::updateTables() +{ // perform any needed table updates + assert(schemaHas(theApp->getTxnDB(), "AccountTransactions", 0, "TransID")); + assert(!schemaHas(theApp->getTxnDB(), "AccountTransactions", 0, "foobar")); + addTxnSeqField(); +} diff --git a/src/cpp/ripple/uint256.h b/src/cpp/ripple/uint256.h index 88048bb69..f24e7dd05 100644 --- a/src/cpp/ripple/uint256.h +++ b/src/cpp/ripple/uint256.h @@ -2,8 +2,8 @@ // Copyright (c) 2011 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. -#ifndef NEWCOIN_UINT256_H -#define NEWCOIN_UINT256_H +#ifndef RIPPLE_UINT256_H +#define RIPPLE_UINT256_H #include #include @@ -219,6 +219,41 @@ public: return strHex(begin(), size()); } + void SetHexExact(const char* psz) + { // must be precisely the correct number of hex digits + static signed char phexdigit[256] = { + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, + + -1,0xa,0xb,0xc, 0xd,0xe,0xf,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,0xa,0xb,0xc, 0xd,0xe,0xf,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + }; + + char* pOut = reinterpret_cast(pn); + for (int i = 0; i < sizeof(pn); ++i) + { + *pOut = phexdigit[*psz++] << 4; + *pOut++ |= phexdigit[*psz++]; + } + + assert(*psz == 0); + assert(pOut == reinterpret_cast(end())); + } + // Allow leading whitespace. // Allow leading "0x". // To be valid must be '\0' terminated. @@ -291,6 +326,11 @@ public: return SetHex(str.c_str(), bStrict); } + void SetHexExact(const std::string& str) + { + SetHexExact(str.c_str()); + } + std::string ToString() const { return GetHex(); diff --git a/src/cpp/ripple/utils.cpp b/src/cpp/ripple/utils.cpp index fb488f19f..de771bc7b 100644 --- a/src/cpp/ripple/utils.cpp +++ b/src/cpp/ripple/utils.cpp @@ -4,11 +4,17 @@ #include #include #endif + #ifdef __FreeBSD__ #include #include #endif +#ifdef WIN32 +#define _WINSOCK_ +#include +#endif + #include #include @@ -181,6 +187,38 @@ std::string strCopy(const std::vector& vucSrc) } +extern std::string urlEncode(const std::string& strSrc) +{ + std::string strDst; + int iOutput = 0; + int iSize = strSrc.length(); + + strDst.resize(iSize*3); + + for (int iInput = 0; iInput < iSize; iInput++) { + unsigned char c = strSrc[iInput]; + + if (c == ' ') + { + strDst[iOutput++] = '+'; + } + else if (isalnum(c)) + { + strDst[iOutput++] = c; + } + else + { + strDst[iOutput++] = '%'; + strDst[iOutput++] = charHex(c >> 4); + strDst[iOutput++] = charHex(c & 15); + } + } + + strDst.resize(iOutput); + + return strDst; +} + // // DH support // @@ -307,8 +345,6 @@ int strIPtoInt(std::string& ipStr) } */ #ifdef WIN32 -#define _WINSOCK_ -#include //#include "Winsock2.h" //#include diff --git a/src/cpp/ripple/utils.h b/src/cpp/ripple/utils.h index 2c60a773f..19333c3be 100644 --- a/src/cpp/ripple/utils.h +++ b/src/cpp/ripple/utils.h @@ -103,6 +103,8 @@ int iToSeconds(boost::posix_time::ptime ptWhen); boost::posix_time::ptime ptFromSeconds(int iSeconds); uint64_t utFromSeconds(int iSeconds); +extern std::string urlEncode(const std::string& strSrc); + /* void intIPtoStr(int ip,std::string& retStr); int strIPtoInt(std::string& ipStr); @@ -214,8 +216,8 @@ DH* DH_der_load(const std::string& strDer); std::string DH_der_gen(int iKeyLength); void getRand(unsigned char *buf, int num); -inline static void getRand(char *buf, int num) { return getRand(reinterpret_cast(buf), num); } -inline static void getRand(void *buf, int num) { return getRand(reinterpret_cast(buf), num); } +inline static void getRand(char *buf, int num) { return getRand(reinterpret_cast(buf), num); } +inline static void getRand(void *buf, int num) { return getRand(reinterpret_cast(buf), num); } inline std::string strGetEnv(const std::string& strKey) { diff --git a/src/js/remote.js b/src/js/remote.js index 085a27985..b363e6068 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -821,20 +821,37 @@ Remote.prototype.request_account_offers = function (accountID, account_index, cu .ledger_choose(current); }; -Remote.prototype.request_account_tx = function (accountID, ledger_min, ledger_max) { + +/* + account: account, + ledger_index_min: ledger_index, // optional, defaults to -1 if ledger_index_max is specified. + ledger_index_max: ledger_index, // optional, defaults to -1 if ledger_index_min is specified. + binary: boolean, // optional, defaults to false + count: boolean, // optional, defaults to false + descending: boolean, // optional, defaults to false + offset: integer, // optional, defaults to 0 + limit: integer // optional +*/ + +Remote.prototype.request_account_tx = function (obj) { // XXX Does this require the server to be trusted? //utils.assert(this.trusted); var request = new Request(this, 'account_tx'); - request.message.account = accountID; + request.message.account = obj.account; - if (ledger_min === ledger_max) { - request.message.ledger = ledger_min; + if (false && ledger_min === ledger_max) { + //request.message.ledger = ledger_min; } else { - request.message.ledger_min = ledger_min; - request.message.ledger_max = ledger_max; + if (obj.ledger_index_min) {request.message.ledger_index_min = obj.ledger_index_min;} + if (obj.ledger_index_max) {request.message.ledger_index_max = obj.ledger_index_max;} + if (obj.binary) {request.message.binary = obj.binary;} + if (obj.count) {request.message.count = obj.count;} + if (obj.descending) {request.message.descending = obj.descending;} + if (obj.offset) {request.message.offset = obj.offset;} + if (obj.limit) {request.message.limit = obj.limit;} } return request; diff --git a/test/account_tx-test.js b/test/account_tx-test.js new file mode 100644 index 000000000..cdc8b29f5 --- /dev/null +++ b/test/account_tx-test.js @@ -0,0 +1,190 @@ +var async = require("async"); +var buster = require("buster"); + +var Amount = require("../src/js/amount").Amount; +var Remote = require("../src/js/remote").Remote; +var Transaction = require("../src/js/transaction").Transaction; +var Server = require("./server").Server; + +var testutils = require("./testutils"); + +require('../src/js/config').load(require('./config')); + +buster.testRunner.timeout = 250000; //This is a very long test! + + +// Hard-coded limits we'll be testing: +var BINARY_LIMIT = 500; +var NONBINARY_LIMIT = 200; + +var ACCOUNT = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; +var FIRST_BATCH = 199; // Within both limits +var OFFSET = 180; +var LIMIT = 170; +var SECOND_BATCH = 10; // Between NONBINARY_LIMIT and BINARY_LIMIT +var THIRD_BATCH = 295; // Exceeds both limits + +buster.testCase("Account_tx tests", { + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "make a lot of transactions and query using account_tx" : + function (done) { + var self = this; + var final_create; + + var transactionCounter = 0; + + var createOfferFunction = function (callback) { + self.remote.transaction() + .offer_create("root", "500", "100/USD/root") + .on('proposed', function (m) { + transactionCounter++; + console.log('Submitted transaction', transactionCounter); + callback(m.result !== 'tesSUCCESS'); + }) + .on('final', function (m) { + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + }) + .submit(); + }; + + function lotsOfTransactions(number, whenDone) { + var bunchOfOffers = []; + for (var i=0; it2.inLedger || (t1.inLedger==t2.inLedger && t1.hash > t2.hash ), + "Transactions were not ordered correctly: "+t1.inLedger+"#"+t1.hash+" should not have come before "+t2.inLedger+"#"+t2.hash); + } + callback(false); + }) + .on('error', standardErrorHandler(callback)) + .request(); + }, + + + ], function (error) { + buster.refute(error); + finalCallback(); + } + ); + } + } +}); + + + +// TODO: +// Test the "count" feature. \ No newline at end of file diff --git a/test/path-test.js b/test/path-test.js index ee75afbd6..00587a2c1 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -1274,4 +1274,132 @@ buster.testCase("Quality paths", { }); }, }); + +buster.testCase("Trust auto clear", { + 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + 'tearDown' : testutils.build_teardown(), + + "trust normal clear" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/bob", + "bob" : "1000/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "bob", "1000/USD/alice", callback); + }, + function (callback) { + self.what = "Clear credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "0/USD/bob", + "bob" : "0/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "bob", "0/USD/alice", function (m) { + var success = m && 'remoteError' === m.error && 'entryNotFound' === m.remote.error; + + callback(!success); + }); + }, + // YYY Could verify owner counts are zero. + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "trust auto clear" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/bob", + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bob" : [ "50/USD/alice" ], + }, + callback); + }, + function (callback) { + self.what = "Clear credit limits."; + + // Mutual trust. + testutils.credit_limits(self.remote, + { + "alice" : "0/USD/bob", + }, + callback); + }, + function (callback) { + self.what = "Verify credit limits."; + + testutils.verify_limit(self.remote, "alice", "0/USD/bob", callback); + }, + function (callback) { + self.what = "Return funds."; + + testutils.payments(self.remote, + { + "alice" : [ "50/USD/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Verify credit limit gone."; + + testutils.verify_limit(self.remote, "bob", "0/USD/alice", function (m) { + var success = m && 'remoteError' === m.error && 'entryNotFound' === m.remote.error; + + callback(!success); + }); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); // vim:sw=2:sts=2:ts=8:et diff --git a/test/testutils.js b/test/testutils.js index 39495f213..0799ee97b 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -226,7 +226,7 @@ var verify_limit = function (remote, src, amount, callback) { callback(); }) - .on('error', function (m) { + .once('error', function (m) { // console.log("error: %s", JSON.stringify(m)); callback(m);