#include #include #include #include #include #include "../json/writer.h" #include "../database/SqliteDatabase.h" #include "Application.h" #include "Ledger.h" #include "utils.h" #include "ripple.pb.h" #include "PackedMessage.h" #include "Config.h" #include "BitcoinUtil.h" #include "Wallet.h" #include "LedgerTiming.h" #include "HashPrefixes.h" #include "Log.h" SETUP_LOG(); DECLARE_INSTANCE(Ledger); Ledger::Ledger(const RippleAddress& masterID, uint64 startAmount) : mTotCoins(startAmount), mLedgerSeq(1), mCloseTime(0), mParentCloseTime(0), mCloseResolution(LEDGER_TIME_ACCURACY), mCloseFlags(0), mClosed(false), mValidHash(false), mAccepted(false), mImmutable(false), mTransactionMap(boost::make_shared(smtTRANSACTION)), mAccountStateMap(boost::make_shared(smtSTATE)) { // special case: put coins in root account AccountState::pointer startAccount = boost::make_shared(masterID); startAccount->peekSLE().setFieldAmount(sfBalance, startAmount); startAccount->peekSLE().setFieldU32(sfSequence, 1); cLog(lsTRACE) << "root account: " << startAccount->peekSLE().getJson(0); mAccountStateMap->armDirty(); writeBack(lepCREATE, startAccount->getSLE()); SHAMap::flushDirty(*mAccountStateMap->disarmDirty(), 256, hotACCOUNT_NODE, mLedgerSeq); zeroFees(); } Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash, 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), mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true), mTransactionMap(boost::make_shared(smtTRANSACTION, transHash)), mAccountStateMap(boost::make_shared(smtSTATE, accountHash)) { // This will throw if the root nodes are not available locally updateHash(); loaded = true; try { if (mTransHash.isNonZero()) mTransactionMap->fetchRoot(mTransHash, NULL); } catch (...) { loaded = false; cLog(lsWARNING) << "Don't have TX root for ledger"; } try { if (mAccountHash.isNonZero()) mAccountStateMap->fetchRoot(mAccountHash, NULL); } catch (...) { loaded = false; cLog(lsWARNING) << "Don't have AS root for ledger"; } mTransactionMap->setImmutable(); mAccountStateMap->setImmutable(); zeroFees(); } Ledger::Ledger(Ledger& ledger, bool isMutable) : mParentHash(ledger.mParentHash), mTotCoins(ledger.mTotCoins), mLedgerSeq(ledger.mLedgerSeq), mCloseTime(ledger.mCloseTime), mParentCloseTime(ledger.mParentCloseTime), mCloseResolution(ledger.mCloseResolution), mCloseFlags(ledger.mCloseFlags), mClosed(ledger.mClosed), mValidHash(false), mAccepted(ledger.mAccepted), mImmutable(!isMutable), mTransactionMap(ledger.mTransactionMap->snapShot(isMutable)), mAccountStateMap(ledger.mAccountStateMap->snapShot(isMutable)) { // Create a new ledger that's a snapshot of this one updateHash(); zeroFees(); } Ledger::Ledger(bool /* dummy */, Ledger& prevLedger) : mTotCoins(prevLedger.mTotCoins), mLedgerSeq(prevLedger.mLedgerSeq + 1), mParentCloseTime(prevLedger.mCloseTime), mCloseResolution(prevLedger.mCloseResolution), mCloseFlags(0), mClosed(false), mValidHash(false), mAccepted(false), mImmutable(false), mTransactionMap(boost::make_shared(smtTRANSACTION)), mAccountStateMap(prevLedger.mAccountStateMap->snapShot(true)) { // Create a new ledger that follows this one prevLedger.updateHash(); mParentHash = prevLedger.getHash(); assert(mParentHash.isNonZero()); mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution(prevLedger.mCloseResolution, prevLedger.getCloseAgree(), mLedgerSeq); if (prevLedger.mCloseTime == 0) mCloseTime = roundCloseTime(theApp->getOPs().getCloseTimeNC(), mCloseResolution); else mCloseTime = prevLedger.mCloseTime + mCloseResolution; zeroFees(); } Ledger::Ledger(const std::vector& rawLedger, bool hasPrefix) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { Serializer s(rawLedger); setRaw(s, hasPrefix); zeroFees(); } Ledger::Ledger(const std::string& rawLedger, bool hasPrefix) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { Serializer s(rawLedger); setRaw(s, hasPrefix); zeroFees(); } void Ledger::setImmutable() { updateHash(); mImmutable = true; if (mTransactionMap) mTransactionMap->setImmutable(); if (mAccountStateMap) mAccountStateMap->setImmutable(); } void Ledger::updateHash() { if (!mImmutable) { if (mTransactionMap) mTransHash = mTransactionMap->getHash(); else mTransHash.zero(); if (mAccountStateMap) mAccountHash = mAccountStateMap->getHash(); else mAccountHash.zero(); } Serializer s(118); s.add32(sHP_Ledger); addRaw(s); mHash = s.getSHA512Half(); mValidHash = true; } void Ledger::setRaw(Serializer &s, bool hasPrefix) { SerializerIterator sit(s); if (hasPrefix) sit.get32(); mLedgerSeq = sit.get32(); mTotCoins = sit.get64(); mParentHash = sit.get256(); mTransHash = sit.get256(); mAccountHash = sit.get256(); mParentCloseTime = sit.get32(); mCloseTime = sit.get32(); mCloseResolution = sit.get8(); mCloseFlags = sit.get8(); updateHash(); if(mValidHash) { mTransactionMap = boost::make_shared(smtTRANSACTION, mTransHash); mAccountStateMap = boost::make_shared(smtSTATE, mAccountHash); } } void Ledger::addRaw(Serializer &s) const { s.add32(mLedgerSeq); s.add64(mTotCoins); s.add256(mParentHash); s.add256(mTransHash); s.add256(mAccountHash); s.add32(mParentCloseTime); s.add32(mCloseTime); s.add8(mCloseResolution); s.add8(mCloseFlags); } void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctCloseTime) { // used when we witnessed the consensus assert(mClosed && !mAccepted); mCloseTime = correctCloseTime ? roundCloseTime(closeTime, closeResolution) : closeTime; mCloseResolution = closeResolution; mCloseFlags = correctCloseTime ? 0 : sLCF_NoConsensusTime; mAccepted = true; setImmutable(); } void Ledger::setAccepted() { // used when we acquired the ledger // FIXME assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); if ((mCloseFlags & sLCF_NoConsensusTime) == 0) mCloseTime = roundCloseTime(mCloseTime, mCloseResolution); mAccepted = true; setImmutable(); } AccountState::pointer Ledger::getAccountState(const RippleAddress& accountID) { #ifdef DEBUG // std::cerr << "Ledger:getAccountState(" << accountID.humanAccountID() << ")" << std::endl; #endif SLE::pointer sle = getSLEi(Ledger::getAccountRootIndex(accountID)); if (!sle) { cLog(lsDEBUG) << boost::str(boost::format("Ledger:getAccountState: not found: %s: %s") % accountID.humanAccountID() % Ledger::getAccountRootIndex(accountID).GetHex()); return AccountState::pointer(); } if (sle->getType() != ltACCOUNT_ROOT) return AccountState::pointer(); return boost::make_shared(sle,accountID); } NicknameState::pointer Ledger::getNicknameState(const uint256& uNickname) { SHAMapItem::pointer item = mAccountStateMap->peekItem(Ledger::getNicknameIndex(uNickname)); if (!item) { return NicknameState::pointer(); } SerializedLedgerEntry::pointer sle = boost::make_shared(item->peekSerializer(), item->getTag()); if (sle->getType() != ltNICKNAME) return NicknameState::pointer(); return boost::make_shared(sle); } bool Ledger::addTransaction(const uint256& txID, const Serializer& txn) { // low-level - just add to table SHAMapItem::pointer item = boost::make_shared(txID, txn.peekData()); if (!mTransactionMap->addGiveItem(item, true, false)) { cLog(lsWARNING) << "Attempt to add transaction to ledger that already had it"; return false; } return true; } bool Ledger::addTransaction(const uint256& txID, const Serializer& txn, const Serializer& md) { // low-level - just add to table Serializer s(txn.getDataLength() + md.getDataLength() + 16); s.addVL(txn.peekData()); s.addVL(md.peekData()); SHAMapItem::pointer item = boost::make_shared(txID, s.peekData()); if (!mTransactionMap->addGiveItem(item, true, true)) { cLog(lsFATAL) << "Attempt to add transaction+MD to ledger that already had it"; return false; } return true; } Transaction::pointer Ledger::getTransaction(const uint256& transID) const { SHAMapTreeNode::TNType type; SHAMapItem::pointer item = mTransactionMap->peekItem(transID, type); if (!item) return Transaction::pointer(); Transaction::pointer txn = theApp->getMasterTransaction().fetch(transID, false); if (txn) return txn; if (type == SHAMapTreeNode::tnTRANSACTION_NM) txn = Transaction::sharedTransaction(item->getData(), true); else if (type == SHAMapTreeNode::tnTRANSACTION_MD) { std::vector txnData; int txnLength; if (!item->peekSerializer().getVL(txnData, 0, txnLength)) return Transaction::pointer(); txn = Transaction::sharedTransaction(txnData, false); } else { assert(false); return Transaction::pointer(); } if (txn->getStatus() == NEW) txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); theApp->getMasterTransaction().canonicalize(txn, false); return txn; } SerializedTransaction::pointer Ledger::getSTransaction(SHAMapItem::ref item, SHAMapTreeNode::TNType type) { SerializerIterator sit(item->peekSerializer()); if (type == SHAMapTreeNode::tnTRANSACTION_NM) return boost::make_shared(boost::ref(sit)); else if (type == SHAMapTreeNode::tnTRANSACTION_MD) { Serializer sTxn(sit.getVL()); SerializerIterator tSit(sTxn); return boost::make_shared(boost::ref(tSit)); } 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; SHAMapItem::pointer item = mTransactionMap->peekItem(txID, type); if (!item) return false; if (type == SHAMapTreeNode::tnTRANSACTION_NM) { // in tree with no metadata txn = theApp->getMasterTransaction().fetch(txID, false); meta.reset(); if (!txn) txn = Transaction::sharedTransaction(item->peekData(), true); } else if (type == SHAMapTreeNode::tnTRANSACTION_MD) { // in tree with metadata SerializerIterator it(item->peekSerializer()); txn = theApp->getMasterTransaction().fetch(txID, false); if (!txn) txn = Transaction::sharedTransaction(it.getVL(), true); else it.getVL(); // skip transaction meta = boost::make_shared(txID, mLedgerSeq, it.getVL()); } else return false; if (txn->getStatus() == NEW) txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); theApp->getMasterTransaction().canonicalize(txn, false); return true; } bool Ledger::getTransactionMeta(const uint256& txID, TransactionMetaSet::pointer& meta) { SHAMapTreeNode::TNType type; SHAMapItem::pointer item = mTransactionMap->peekItem(txID, type); if (!item) return false; if (type != SHAMapTreeNode::tnTRANSACTION_MD) return false; SerializerIterator it(item->peekSerializer()); it.getVL(); // skip transaction meta = boost::make_shared(txID, mLedgerSeq, it.getVL()); return true; } bool Ledger::getMetaHex(const uint256& transID, std::string& hex) { SHAMapTreeNode::TNType type; SHAMapItem::pointer item = mTransactionMap->peekItem(transID, type); if (!item) return false; if (type != SHAMapTreeNode::tnTRANSACTION_MD) return false; SerializerIterator it(item->peekSerializer()); it.getVL(); // skip transaction hex = strHex(it.getVL()); return true; } uint256 Ledger::getHash() { if (!mValidHash) updateHash(); return mHash; } void Ledger::saveAcceptedLedger(Job&, bool fromConsensus) { cLog(lsTRACE) << "saveAcceptedLedger " << (fromConsensus ? "fromConsensus " : "fromAcquire ") << getLedgerSeq(); static boost::format ledgerExists("SELECT LedgerSeq FROM Ledgers INDEXED BY SeqLedger where LedgerSeq = %u;"); static boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %u;"); static boost::format deleteTrans1("DELETE FROM Transactions WHERE LedgerSeq = %u;"); static boost::format deleteTrans2("DELETE FROM AccountTransactions WHERE LedgerSeq = %u;"); static boost::format deleteAcctTrans("DELETE FROM AccountTransactions WHERE TransID = '%s';"); static boost::format transExists("SELECT Status FROM Transactions WHERE TransID = '%s';"); static boost::format updateTx("UPDATE Transactions SET LedgerSeq = %u, Status = '%c', TxnMeta = %s WHERE TransID = '%s';"); static boost::format addLedger("INSERT OR REPLACE INTO Ledgers " "(LedgerHash,LedgerSeq,PrevHash,TotalCoins,ClosingTime,PrevClosingTime,CloseTimeRes,CloseFlags," "AccountSetHash,TransSetHash) VALUES ('%s','%u','%s','%s','%u','%u','%d','%u','%s','%s');"); if (!getAccountHash().isNonZero()) { cLog(lsFATAL) << "AH is zero: " << getJson(0); assert(false); } if (getAccountHash() != mAccountStateMap->getHash()) { cLog(lsFATAL) << "sAL: " << getAccountHash() << " != " << mAccountStateMap->getHash(); cLog(lsFATAL) << "saveAcceptedLedger: seq=" << mLedgerSeq << ", fromcons=" << fromConsensus; assert(false); } assert (getTransHash() == mTransactionMap->getHash()); // Save the ledger header in the hashed object store Serializer s(128); s.add32(sHP_Ledger); addRaw(s); theApp->getHashedObjectStore().store(hotLEDGER, mLedgerSeq, s.peekData(), mHash); AcceptedLedger::pointer aLedger = AcceptedLedger::makeAcceptedLedger(shared_from_this()); { ScopedLock sl(theApp->getLedgerDB()->getDBLock()); theApp->getLedgerDB()->getDB()->executeSQL(boost::str(deleteLedger % mLedgerSeq)); } { Database *db = theApp->getTxnDB()->getDB(); ScopedLock dbLock(theApp->getTxnDB()->getDBLock()); db->executeSQL("BEGIN TRANSACTION;"); db->executeSQL(boost::str(deleteTrans1 % mLedgerSeq)); db->executeSQL(boost::str(deleteTrans2 % mLedgerSeq)); BOOST_FOREACH(const AcceptedLedger::value_type& vt, aLedger->getMap()) { uint256 txID = vt.second.getTransactionID(); theApp->getMasterTransaction().inLedger(txID, mLedgerSeq); db->executeSQL(boost::str(deleteAcctTrans % txID.GetHex())); const std::vector& accts = vt.second.getAffected(); if (!accts.empty()) { 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) { if (!first) sql += ", ('"; else { sql += "('"; first = false; } sql += txID.GetHex(); sql += "','"; sql += it->humanAccountID(); sql += "',"; sql += boost::lexical_cast(getLedgerSeq()); sql += ","; sql += boost::lexical_cast(vt.second.getTxnSeq()); sql += ")"; } sql += ";"; cLog(lsTRACE) << "ActTx: " << sql; db->executeSQL(sql); } else cLog(lsWARNING) << "Transaction in ledger " << mLedgerSeq << " affects no accounts"; db->executeSQL(SerializedTransaction::getMetaSQLInsertReplaceHeader() + vt.second.getTxn()->getMetaSQL(getLedgerSeq(), vt.second.getEscMeta()) + ";"); } db->executeSQL("COMMIT TRANSACTION;"); } { ScopedLock sl(theApp->getLedgerDB()->getDBLock()); theApp->getLedgerDB()->getDB()->executeSQL(boost::str(addLedger % getHash().GetHex() % mLedgerSeq % mParentHash.GetHex() % boost::lexical_cast(mTotCoins) % mCloseTime % mParentCloseTime % mCloseResolution % mCloseFlags % mAccountHash.GetHex() % mTransHash.GetHex())); } if (!fromConsensus && (theConfig.NODE_SIZE < 2)) // tiny or small dropCache(); if (theApp->getJobQueue().getJobCountTotal(jtPUBOLDLEDGER) < 2) theApp->getLedgerMaster().resumeAcquiring(); else cLog(lsDEBUG) << "no resume, too many pending ledger saves"; } #ifndef NO_SQLITE3_PREPARE Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) { Ledger::pointer ledger; { Database* db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); 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); } if (ledger) { Ledger::getSQL2(ledger); ledger->setFull(); } return ledger; } Ledger::pointer Ledger::loadByHash(const uint256& ledgerHash) { Ledger::pointer ledger; { Database* db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); 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); } if (ledger) { assert(ledger->getHash() == ledgerHash); Ledger::getSQL2(ledger); ledger->setFull(); } return ledger; } #else Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) { // This is a low-level function with no caching std::string sql="SELECT * from Ledgers WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); return getSQL(sql); } Ledger::pointer Ledger::loadByHash(const uint256& ledgerHash) { // This is a low-level function with no caching and only gets accepted ledgers std::string sql="SELECT * from Ledgers WHERE LedgerHash='"; sql.append(ledgerHash.GetHex()); sql.append("';"); return getSQL(sql); } #endif Ledger::pointer Ledger::getSQL(const std::string& sql) { // only used with sqlite3 prepared statements not used uint256 ledgerHash, prevHash, accountHash, transHash; uint64 totCoins; uint32 closingTime, prevClosingTime, ledgerSeq; int closeResolution; unsigned closeFlags; std::string hash; { Database *db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); if (!db->executeSQL(sql) || !db->startIterRows()) return Ledger::pointer(); db->getStr("LedgerHash", hash); ledgerHash.SetHexExact(hash); db->getStr("PrevHash", hash); prevHash.SetHexExact(hash); db->getStr("AccountSetHash", hash); accountHash.SetHexExact(hash); db->getStr("TransSetHash", hash); transHash.SetHexExact(hash); totCoins = db->getBigInt("TotalCoins"); closingTime = db->getBigInt("ClosingTime"); prevClosingTime = db->getBigInt("PrevClosingTime"); closeResolution = db->getBigInt("CloseTimeRes"); closeFlags = db->getBigInt("CloseFlags"); ledgerSeq = db->getBigInt("LedgerSeq"); db->endIterRows(); } // CAUTION: code below appears in two places 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(); if (ret->getHash() != ledgerHash) { if (sLog(lsERROR)) { Log(lsERROR) << "Failed on ledger"; Json::Value p; ret->addJson(p, LEDGER_JSON_FULL); Log(lsERROR) << p; } assert(false); return Ledger::pointer(); } cLog(lsTRACE) << "Loaded ledger: " << ledgerHash; return ret; } Ledger::pointer Ledger::getSQL1(SqliteStatement *stmt) { int iRet = stmt->step(); if (stmt->isDone(iRet)) return Ledger::pointer(); if (!stmt->isRow(iRet)) { cLog(lsINFO) << "Ledger not found: " << iRet << " = " << stmt->getError(iRet); return Ledger::pointer(); } uint256 ledgerHash, prevHash, accountHash, transHash; uint64 totCoins; uint32 closingTime, prevClosingTime, ledgerSeq; int closeResolution; unsigned closeFlags; 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); closeResolution = stmt->getUInt32(7); closeFlags = stmt->getUInt32(8); ledgerSeq = stmt->getUInt32(9); // CAUTION: code below appears in two places 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) { ret->setClosed(); if (theApp->getOPs().haveLedger(ret->getLedgerSeq())) ret->setAccepted(); cLog(lsTRACE) << "Loaded ledger: " << ret->getHash().GetHex(); } uint256 Ledger::getHashByIndex(uint32 ledgerIndex) { uint256 ret; std::string sql="SELECT LedgerHash FROM Ledgers INDEXED BY SeqLedger WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); std::string hash; { Database *db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); if (!db->executeSQL(sql) || !db->startIterRows()) return ret; db->getStr("LedgerHash", hash); db->endIterRows(); } ret.SetHexExact(hash); return ret; } bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& parentHash) { #ifndef NO_SQLITE3_PREPARE DatabaseCon *con = theApp->getLedgerDB(); ScopedLock sl(con->getDBLock()); SqliteStatement pSt(con->getDB()->getSqliteDB(), "SELECT LedgerHash,PrevHash FROM Ledgers INDEXED BY SeqLedger Where LedgerSeq = ?;"); pSt.bind(1, ledgerIndex); int ret = pSt.step(); if (pSt.isDone(ret)) { cLog(lsTRACE) << "Don't have ledger " << ledgerIndex; return false; } if (!pSt.isRow(ret)) { assert(false); cLog(lsFATAL) << "Unexpected statement result " << ret; return false; } ledgerHash.SetHexExact(pSt.peekString(0)); parentHash.SetHexExact(pSt.peekString(1)); return true; #else std::string sql="SELECT LedgerHash,PrevHash FROM Ledgers WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); std::string hash, prevHash; { Database *db = theApp->getLedgerDB()->getDB(); ScopedLock sl(theApp->getLedgerDB()->getDBLock()); if (!db->executeSQL(sql) || !db->startIterRows()) return false; db->getStr("LedgerHash", hash); db->getStr("PrevHash", prevHash); db->endIterRows(); } ledgerHash.SetHexExact(hash); parentHash.SetHexExact(prevHash); assert(ledgerHash.isNonZero() && ((ledgerIndex == 0) || parentHash.isNonZero())); return true; #endif } std::map< uint32, std::pair > Ledger::getHashesByIndex(uint32 minSeq, uint32 maxSeq) { #ifndef NO_SQLITE_PREPARE std::map< uint32, std::pair > ret; DatabaseCon *con = theApp->getLedgerDB(); ScopedLock sl(con->getDBLock()); SqliteStatement pSt(con->getDB()->getSqliteDB(), "SELECT LedgerSeq,LedgerHash,PrevHash FROM Ledgers INDEXED BY SeqLedger " "WHERE LedgerSeq >= ? AND LedgerSeq <= ?;"); std::pair hashes; pSt.bind(1, minSeq); pSt.bind(2, maxSeq); do { int r = pSt.step(); if (pSt.isDone(r)) return ret; if (!pSt.isRow(r)) return ret; hashes.first.SetHexExact(pSt.peekString(1)); hashes.second.SetHexExact(pSt.peekString(2)); ret[pSt.getUInt32(0)] = hashes; } while(1); #else #error SQLite prepare is required #endif } Ledger::pointer Ledger::getLastFullLedger() { try { return getSQL("SELECT * from Ledgers order by LedgerSeq desc limit 1;"); } catch (SHAMapMissingNode& sn) { cLog(lsWARNING) << "Database contains ledger with missing nodes: " << sn; return Ledger::pointer(); } } void Ledger::addJson(Json::Value& ret, int options) { ret["ledger"] = getJson(options); } Json::Value Ledger::getJson(int options) { Json::Value ledger(Json::objectValue); bool bFull = isSetBit(options, LEDGER_JSON_FULL); boost::recursive_mutex::scoped_lock sl(mLock); ledger["seqNum"] = boost::lexical_cast(mLedgerSeq); // DEPRECATED ledger["parent_hash"] = mParentHash.GetHex(); ledger["ledger_index"] = boost::lexical_cast(mLedgerSeq); if (mClosed || bFull) { if (mClosed) ledger["closed"] = true; ledger["hash"] = mHash.GetHex(); // DEPRECATED ledger["totalCoins"] = boost::lexical_cast(mTotCoins); // DEPRECATED ledger["ledger_hash"] = mHash.GetHex(); ledger["transaction_hash"] = mTransHash.GetHex(); ledger["account_hash"] = mAccountHash.GetHex(); ledger["accepted"] = mAccepted; ledger["total_coins"] = boost::lexical_cast(mTotCoins); if (mCloseTime != 0) { ledger["close_time"] = mCloseTime; ledger["close_time_human"] = boost::posix_time::to_simple_string(ptFromSeconds(mCloseTime)); ledger["close_time_resolution"] = mCloseResolution; if ((mCloseFlags & sLCF_NoConsensusTime) != 0) ledger["close_time_estimated"] = true; } } else { ledger["closed"] = false; } if (mTransactionMap && (bFull || isSetBit(options, LEDGER_JSON_DUMP_TXRP))) { Json::Value txns(Json::arrayValue); SHAMapTreeNode::TNType type; ScopedLock l(mTransactionMap->Lock()); for (SHAMapItem::pointer item = mTransactionMap->peekFirstItem(type); !!item; item = mTransactionMap->peekNextItem(item->getTag(), type)) { if (bFull || isSetBit(options, LEDGER_JSON_EXPAND)) { if (type == SHAMapTreeNode::tnTRANSACTION_NM) { SerializerIterator sit(item->peekSerializer()); SerializedTransaction txn(sit); txns.append(txn.getJson(0)); } else if (type == SHAMapTreeNode::tnTRANSACTION_MD) { SerializerIterator sit(item->peekSerializer()); Serializer sTxn(sit.getVL()); SerializerIterator tsit(sTxn); SerializedTransaction txn(tsit); TransactionMetaSet meta(item->getTag(), mLedgerSeq, sit.getVL()); Json::Value txJson = txn.getJson(0); txJson["metaData"] = meta.getJson(0); txns.append(txJson); } else { Json::Value error = Json::objectValue; error[item->getTag().GetHex()] = type; txns.append(error); } } else txns.append(item->getTag().GetHex()); } ledger["transactions"] = txns; } if (mAccountStateMap && (bFull || isSetBit(options, LEDGER_JSON_DUMP_STATE))) { Json::Value state(Json::arrayValue); ScopedLock l(mAccountStateMap->Lock()); for (SHAMapItem::pointer item = mAccountStateMap->peekFirstItem(); !!item; item = mAccountStateMap->peekNextItem(item->getTag())) { if (bFull || isSetBit(options, LEDGER_JSON_EXPAND)) { SerializerIterator sit(item->peekSerializer()); SerializedLedgerEntry sle(sit, item->getTag()); state.append(sle.getJson(0)); } else state.append(item->getTag().GetHex()); } ledger["accountState"] = state; } return ledger; } void Ledger::setAcquiring(void) { if (!mTransactionMap || !mAccountStateMap) throw std::runtime_error("invalid map"); mTransactionMap->setSynching(); mAccountStateMap->setSynching(); } bool Ledger::isAcquiring(void) { return isAcquiringTx() || isAcquiringAS(); } bool Ledger::isAcquiringTx(void) { return mTransactionMap->isSynching(); } bool Ledger::isAcquiringAS(void) { return mAccountStateMap->isSynching(); } boost::posix_time::ptime Ledger::getCloseTime() const { return ptFromSeconds(mCloseTime); } void Ledger::setCloseTime(boost::posix_time::ptime ptm) { assert(!mImmutable); mCloseTime = iToSeconds(ptm); } // XXX Use shared locks where possible? LedgerStateParms Ledger::writeBack(LedgerStateParms parms, SLE::ref entry) { ScopedLock l(mAccountStateMap->Lock()); bool create = false; if (!mAccountStateMap->hasItem(entry->getIndex())) { if ((parms & lepCREATE) == 0) { Log(lsERROR) << "WriteBack non-existent node without create"; return lepMISSING; } create = true; } SHAMapItem::pointer item = boost::make_shared(entry->getIndex()); entry->add(item->peekSerializer()); if (create) { assert(!mAccountStateMap->hasItem(entry->getIndex())); if(!mAccountStateMap->addGiveItem(item, false, false)) { assert(false); return lepERROR; } return lepCREATED; } if (!mAccountStateMap->updateGiveItem(item, false, false)) { assert(false); return lepERROR; } return lepOKAY; } SLE::pointer Ledger::getSLE(const uint256& uHash) { SHAMapItem::pointer node = mAccountStateMap->peekItem(uHash); if (!node) return SLE::pointer(); return boost::make_shared(node->peekSerializer(), node->getTag()); } SLE::pointer Ledger::getSLEi(const uint256& uId) { uint256 hash; ScopedLock sl(mAccountStateMap->Lock()); SHAMapItem::pointer node = mAccountStateMap->peekItem(uId, hash); if (!node) return SLE::pointer(); SLE::pointer ret = theApp->getSLECache().fetch(hash); if (!ret) { ret = boost::make_shared(node->peekSerializer(), node->getTag()); ret->setImmutable(); theApp->getSLECache().canonicalize(hash, ret); } return ret; } uint256 Ledger::getFirstLedgerIndex() { SHAMapItem::pointer node = mAccountStateMap->peekFirstItem(); return node ? node->getTag() : uint256(); } uint256 Ledger::getLastLedgerIndex() { SHAMapItem::pointer node = mAccountStateMap->peekLastItem(); return node ? node->getTag() : uint256(); } uint256 Ledger::getNextLedgerIndex(const uint256& uHash) { SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); return node ? node->getTag() : uint256(); } uint256 Ledger::getNextLedgerIndex(const uint256& uHash, const uint256& uEnd) { SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); if ((!node) || (node->getTag() > uEnd)) return uint256(); return node->getTag(); } uint256 Ledger::getPrevLedgerIndex(const uint256& uHash) { SHAMapItem::pointer node = mAccountStateMap->peekPrevItem(uHash); return node ? node->getTag() : uint256(); } uint256 Ledger::getPrevLedgerIndex(const uint256& uHash, const uint256& uBegin) { SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); if ((!node) || (node->getTag() < uBegin)) return uint256(); return node->getTag(); } SLE::pointer Ledger::getASNodeI(const uint256& nodeID, LedgerEntryType let) { SLE::pointer node = getSLEi(nodeID); if (node && (node->getType() != let)) node.reset(); return node; } SLE::pointer Ledger::getASNode(LedgerStateParms& parms, const uint256& nodeID, LedgerEntryType let ) { SHAMapItem::pointer account = mAccountStateMap->peekItem(nodeID); if (!account) { if ( (parms & lepCREATE) == 0 ) { parms = lepMISSING; return SLE::pointer(); } parms = parms | lepCREATED | lepOKAY; SLE::pointer sle=boost::make_shared(let, nodeID); return sle; } SLE::pointer sle = boost::make_shared(account->peekSerializer(), nodeID); if (sle->getType() != let) { // maybe it's a currency or something parms = parms | lepWRONGTYPE; return SLE::pointer(); } parms = parms | lepOKAY; return sle; } SLE::pointer Ledger::getAccountRoot(const uint160& accountID) { return getASNodeI(getAccountRootIndex(accountID), ltACCOUNT_ROOT); } SLE::pointer Ledger::getAccountRoot(const RippleAddress& naAccountID) { return getASNodeI(getAccountRootIndex(naAccountID.getAccountID()), ltACCOUNT_ROOT); } // // Directory // SLE::pointer Ledger::getDirNode(const uint256& uNodeIndex) { return getASNodeI(uNodeIndex, ltDIR_NODE); } // // Generator Map // SLE::pointer Ledger::getGenerator(const uint160& uGeneratorID) { return getASNodeI(getGeneratorIndex(uGeneratorID), ltGENERATOR_MAP); } // // Nickname // SLE::pointer Ledger::getNickname(const uint256& uNickname) { return getASNodeI(uNickname, ltNICKNAME); } // // Offer // SLE::pointer Ledger::getOffer(const uint256& uIndex) { return getASNodeI(uIndex, ltOFFER); } // // Ripple State // SLE::pointer Ledger::getRippleState(const uint256& uNode) { return getASNodeI(uNode, ltRIPPLE_STATE); } // For an entry put in the 64 bit index or quality. uint256 Ledger::getQualityIndex(const uint256& uBase, const uint64 uNodeDir) { // Indexes are stored in big endian format: they print as hex as stored. // Most significant bytes are first. Least significant bytes represent adjacent entries. // We place uNodeDir in the 8 right most bytes to be adjacent. // Want uNodeDir in big endian format so ++ goes to the next entry for indexes. uint256 uNode(uBase); ((uint64*) uNode.end())[-1] = htobe64(uNodeDir); return uNode; } // Return the last 64 bits. uint64 Ledger::getQuality(const uint256& uBase) { return be64toh(((uint64*) uBase.end())[-1]); } uint256 Ledger::getQualityNext(const uint256& uBase) { static uint256 uNext("10000000000000000"); uint256 uResult = uBase; uResult += uNext; return uResult; } uint256 Ledger::getAccountRootIndex(const uint160& uAccountID) { Serializer s(22); s.add16(spaceAccount); // 2 s.add160(uAccountID); // 20 return s.getSHA512Half(); } uint256 Ledger::getLedgerFeeIndex() { // get the index of the node that holds the fee schedul Serializer s(2); s.add16(spaceFee); return s.getSHA512Half(); } uint256 Ledger::getLedgerFeatureIndex() { // get the index of the node that holds the last 256 ledgers Serializer s(2); s.add16(spaceFeature); return s.getSHA512Half(); } uint256 Ledger::getLedgerHashIndex() { // get the index of the node that holds the last 256 ledgers Serializer s(2); s.add16(spaceSkipList); return s.getSHA512Half(); } uint256 Ledger::getLedgerHashIndex(uint32 desiredLedgerIndex) { // get the index of the node that holds the set of 256 ledgers that includes this ledger's hash // (or the first ledger after it if it's not a multiple of 256) Serializer s(6); s.add16(spaceSkipList); s.add32(desiredLedgerIndex >> 16); return s.getSHA512Half(); } uint256 Ledger::getLedgerHash(uint32 ledgerIndex) { // return the hash of the specified ledger, 0 if not available // easy cases if (ledgerIndex > mLedgerSeq) { cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " future"; return uint256(); } if (ledgerIndex == mLedgerSeq) return getHash(); if (ledgerIndex == (mLedgerSeq - 1)) return mParentHash; // within 256 int diff = mLedgerSeq - ledgerIndex; if (diff <= 256) { SLE::pointer hashIndex = getSLEi(getLedgerHashIndex()); if (hashIndex) { assert(hashIndex->getFieldU32(sfLastLedgerSequence) == (mLedgerSeq - 1)); STVector256 vec = hashIndex->getFieldV256(sfHashes); if (vec.size() >= diff) return vec.at(vec.size() - diff); cLog(lsWARNING) << "Ledger " << mLedgerSeq << " missing hash for " << ledgerIndex << " (" << vec.size() << "," << diff << ")"; } else cLog(lsWARNING) << "Ledger " << mLedgerSeq << ":" << getHash() << " missing normal list"; } if ((ledgerIndex & 0xff) != 0) { cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " past"; return uint256(); } // in skiplist SLE::pointer hashIndex = getSLEi(getLedgerHashIndex(ledgerIndex)); if (hashIndex) { int lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence); assert(lastSeq >= ledgerIndex); assert((lastSeq & 0xff) == 0); int sDiff = (lastSeq - ledgerIndex) >> 8; STVector256 vec = hashIndex->getFieldV256(sfHashes); if (vec.size() > sDiff) return vec.at(vec.size() - sDiff - 1); } cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " error"; return uint256(); } std::vector< std::pair > Ledger::getLedgerHashes() { std::vector< std::pair > ret; SLE::pointer hashIndex = getSLEi(getLedgerHashIndex()); if (hashIndex) { STVector256 vec = hashIndex->getFieldV256(sfHashes); int size = vec.size(); ret.reserve(size); uint32 seq = hashIndex->getFieldU32(sfLastLedgerSequence) - size; for (int i = 0; i < size; ++i) ret.push_back(std::make_pair(++seq, vec.at(i))); } return ret; } // XRP to XRP not allowed. // Currencies must have appropriate issuer. // Currencies or accounts must differ. bool Ledger::isValidBook(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID, const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID) { if (uTakerPaysCurrency.isZero()) { // XRP in if (uTakerPaysIssuerID.isNonZero()) // XRP cannot have an issuer return false; if (uTakerGetsCurrency.isZero()) // XRP to XRP not allowed return false; if (uTakerGetsIssuerID.isZero()) // non-XRP must have issuer return false; return true; } // non-XRP in if (uTakerPaysIssuerID.isZero()) // non-XRP must have issuer return false; if (uTakerGetsCurrency.isZero()) // non-XRP to XRP { if (uTakerGetsIssuerID.isNonZero()) // XRP cannot have issuer return false; } else // non-XRP to non-XRP { if ((uTakerPaysCurrency == uTakerGetsCurrency) && (uTakerGetsIssuerID == uTakerPaysIssuerID)) return false; // Input and output cannot be identical } return true; } uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID, const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID) { Serializer s(82); s.add16(spaceBookDir); // 2 s.add160(uTakerPaysCurrency); // 20 s.add160(uTakerGetsCurrency); // 20 s.add160(uTakerPaysIssuerID); // 20 s.add160(uTakerGetsIssuerID); // 20 uint256 uBaseIndex = getQualityIndex(s.getSHA512Half()); // Return with quality 0. cLog(lsTRACE) << boost::str(boost::format("getBookBase(%s,%s,%s,%s) = %s") % STAmount::createHumanCurrency(uTakerPaysCurrency) % RippleAddress::createHumanAccountID(uTakerPaysIssuerID) % STAmount::createHumanCurrency(uTakerGetsCurrency) % RippleAddress::createHumanAccountID(uTakerGetsIssuerID) % uBaseIndex.ToString()); assert(isValidBook(uTakerPaysCurrency, uTakerPaysIssuerID, uTakerGetsCurrency, uTakerGetsIssuerID)); return uBaseIndex; } uint256 Ledger::getDirNodeIndex(const uint256& uDirRoot, const uint64 uNodeIndex) { if (uNodeIndex) { Serializer s(42); s.add16(spaceDirNode); // 2 s.add256(uDirRoot); // 32 s.add64(uNodeIndex); // 8 return s.getSHA512Half(); } else { return uDirRoot; } } uint256 Ledger::getGeneratorIndex(const uint160& uGeneratorID) { Serializer s(22); s.add16(spaceGenerator); // 2 s.add160(uGeneratorID); // 20 return s.getSHA512Half(); } // What is important: // --> uNickname: is a Sha256 // <-- SHA512/2: for consistency and speed in generating indexes. uint256 Ledger::getNicknameIndex(const uint256& uNickname) { Serializer s(34); s.add16(spaceNickname); // 2 s.add256(uNickname); // 32 return s.getSHA512Half(); } uint256 Ledger::getOfferIndex(const uint160& uAccountID, uint32 uSequence) { Serializer s(26); s.add16(spaceOffer); // 2 s.add160(uAccountID); // 20 s.add32(uSequence); // 4 return s.getSHA512Half(); } uint256 Ledger::getOwnerDirIndex(const uint160& uAccountID) { Serializer s(22); s.add16(spaceOwnerDir); // 2 s.add160(uAccountID); // 20 return s.getSHA512Half(); } uint256 Ledger::getRippleStateIndex(const RippleAddress& naA, const RippleAddress& naB, const uint160& uCurrency) { uint160 uAID = naA.getAccountID(); uint160 uBID = naB.getAccountID(); bool bAltB = uAID < uBID; Serializer s(62); s.add16(spaceRipple); // 2 s.add160(bAltB ? uAID : uBID); // 20 s.add160(bAltB ? uBID : uAID); // 20 s.add160(uCurrency); // 20 return s.getSHA512Half(); } bool Ledger::walkLedger() { std::vector missingNodes1, missingNodes2; mAccountStateMap->walkMap(missingNodes1, 32); if (sLog(lsINFO) && !missingNodes1.empty()) { Log(lsINFO) << missingNodes1.size() << " missing account node(s)"; Log(lsINFO) << "First: " << missingNodes1[0]; } mTransactionMap->walkMap(missingNodes2, 32); if (sLog(lsINFO) && !missingNodes2.empty()) { Log(lsINFO) << missingNodes2.size() << " missing transaction node(s)"; Log(lsINFO) << "First: " << missingNodes2[0]; } return missingNodes1.empty() && missingNodes2.empty(); } bool Ledger::assertSane() { if (mHash.isNonZero() && mAccountHash.isNonZero() && mAccountStateMap && mTransactionMap && (mAccountHash == mAccountStateMap->getHash()) && (mTransHash == mTransactionMap->getHash())) return true; Log(lsFATAL) << "ledger is not sane"; Json::Value j = getJson(0); j["accountTreeHash"] = mAccountHash.GetHex(); j["transTreeHash"] = mTransHash.GetHex(); assert(false); return false; } void Ledger::updateSkipList() { // update the skip list with the information from our previous ledger if (mLedgerSeq == 0) // genesis ledger has no previous ledger return; uint32 prevIndex = mLedgerSeq - 1; if ((prevIndex & 0xff) == 0) { // update record of every 256th ledger uint256 hash = getLedgerHashIndex(prevIndex); SLE::pointer skipList = getSLE(hash); std::vector hashes; if (!skipList) skipList = boost::make_shared(ltLEDGER_HASHES, hash); else hashes = skipList->getFieldV256(sfHashes).peekValue(); assert(hashes.size() <= 256); hashes.push_back(mParentHash); skipList->setFieldV256(sfHashes, STVector256(hashes)); skipList->setFieldU32(sfLastLedgerSequence, prevIndex); if (writeBack(lepCREATE, skipList) == lepERROR) { assert(false); } } // update record of past 256 ledger uint256 hash = getLedgerHashIndex(); SLE::pointer skipList = getSLE(hash); std::vector hashes; if (!skipList) { skipList = boost::make_shared(ltLEDGER_HASHES, hash); } else hashes = skipList->getFieldV256(sfHashes).peekValue(); assert(hashes.size() <= 256); if (hashes.size() == 256) hashes.erase(hashes.begin()); hashes.push_back(mParentHash); skipList->setFieldV256(sfHashes, STVector256(hashes)); skipList->setFieldU32(sfLastLedgerSequence, prevIndex); if (writeBack(lepCREATE, skipList) == lepERROR) { assert(false); } } uint32 Ledger::roundCloseTime(uint32 closeTime, uint32 closeResolution) { if (closeTime == 0) return 0; closeTime += (closeResolution / 2); return closeTime - (closeTime % closeResolution); } void Ledger::pendSave(bool fromConsensus) { if (!fromConsensus && !theApp->isNewFlag(getHash(), SF_SAVED)) return; assert(isImmutable()); theApp->getJobQueue().addJob(fromConsensus ? jtPUBLEDGER : jtPUBOLDLEDGER, fromConsensus ? "Ledger::pendSave" : "Ledger::pendOldSave", BIND_TYPE(&Ledger::saveAcceptedLedger, shared_from_this(), P_1, fromConsensus)); } void Ledger::ownerDirDescriber(SLE::ref sle, const uint160& owner) { sle->setFieldAccount(sfOwner, owner); } void Ledger::qualityDirDescriber(SLE::ref sle, const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuer, const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuer, const uint64& uRate) { sle->setFieldH160(sfTakerPaysCurrency, uTakerPaysCurrency); sle->setFieldH160(sfTakerPaysIssuer, uTakerPaysIssuer); sle->setFieldH160(sfTakerGetsCurrency, uTakerGetsCurrency); sle->setFieldH160(sfTakerGetsIssuer, uTakerGetsIssuer); sle->setFieldU64(sfExchangeRate, uRate); } void Ledger::zeroFees() { mBaseFee = 0; mReferenceFeeUnits = 0; mReserveBase = 0; mReserveIncrement = 0; } void Ledger::updateFees() { mBaseFee = theConfig.FEE_DEFAULT; mReferenceFeeUnits = 10; mReserveBase = theConfig.FEE_ACCOUNT_RESERVE; mReserveIncrement = theConfig.FEE_OWNER_RESERVE; LedgerStateParms p = lepNONE; SLE::pointer sle = getASNode(p, Ledger::getLedgerFeeIndex(), ltFEE_SETTINGS); if (!sle) return; if (sle->getFieldIndex(sfBaseFee) != -1) mBaseFee = sle->getFieldU64(sfBaseFee); if (sle->getFieldIndex(sfReferenceFeeUnits) != -1) mReferenceFeeUnits = sle->getFieldU32(sfReferenceFeeUnits); if (sle->getFieldIndex(sfReserveBase) != -1) mReserveBase = sle->getFieldU32(sfReserveBase); if (sle->getFieldIndex(sfReserveIncrement) != -1) mReserveIncrement = sle->getFieldU32(sfReserveIncrement); } uint64 Ledger::scaleFeeBase(uint64 fee) { if (!mBaseFee) updateFees(); return theApp->getFeeTrack().scaleFeeBase(fee, mBaseFee, mReferenceFeeUnits); } uint64 Ledger::scaleFeeLoad(uint64 fee, bool bAdmin) { if (!mBaseFee) updateFees(); return theApp->getFeeTrack().scaleFeeLoad(fee, mBaseFee, mReferenceFeeUnits, bAdmin); } std::vector Ledger::getNeededTransactionHashes(int max, SHAMapSyncFilter* filter) { std::vector ret; if (mTransHash.isNonZero()) { if (mTransactionMap->getHash().isZero()) ret.push_back(mTransHash); else ret = mTransactionMap->getNeededHashes(max, filter); } return ret; } std::vector Ledger::getNeededAccountStateHashes(int max, SHAMapSyncFilter* filter) { std::vector ret; if (mAccountHash.isNonZero()) { if (mAccountStateMap->getHash().isZero()) ret.push_back(mAccountHash); else ret = mAccountStateMap->getNeededHashes(max, filter); } return ret; } BOOST_AUTO_TEST_SUITE(quality) BOOST_AUTO_TEST_CASE( getquality ) { uint256 uBig("D2DC44E5DC189318DB36EF87D2104CDF0A0FE3A4B698BEEE55038D7EA4C68000"); if (6125895493223874560 != Ledger::getQuality(uBig)) BOOST_FAIL("Ledger::getQuality fails."); } BOOST_AUTO_TEST_SUITE_END() // vim:ts=4