#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { create_genesis_t const create_genesis{}; //------------------------------------------------------------------------------ class Ledger::sles_iter_impl : public sles_type::iter_base { private: SHAMap::const_iterator iter_; public: sles_iter_impl() = delete; sles_iter_impl& operator=(sles_iter_impl const&) = delete; sles_iter_impl(sles_iter_impl const&) = default; sles_iter_impl(SHAMap::const_iterator iter) : iter_(iter) { } [[nodiscard]] std::unique_ptr copy() const override { return std::make_unique(*this); } [[nodiscard]] bool equal(base_type const& impl) const override { if (auto const p = dynamic_cast(&impl)) return iter_ == p->iter_; return false; } void increment() override { ++iter_; } [[nodiscard]] sles_type::value_type dereference() const override { SerialIter sit(iter_->slice()); return std::make_shared(sit, iter_->key()); } }; //------------------------------------------------------------------------------ class Ledger::txs_iter_impl : public txs_type::iter_base { private: bool metadata_; SHAMap::const_iterator iter_; public: txs_iter_impl() = delete; txs_iter_impl& operator=(txs_iter_impl const&) = delete; txs_iter_impl(txs_iter_impl const&) = default; txs_iter_impl(bool metadata, SHAMap::const_iterator iter) : metadata_(metadata), iter_(iter) { } [[nodiscard]] std::unique_ptr copy() const override { return std::make_unique(*this); } [[nodiscard]] bool equal(base_type const& impl) const override { if (auto const p = dynamic_cast(&impl)) return iter_ == p->iter_; return false; } void increment() override { ++iter_; } [[nodiscard]] txs_type::value_type dereference() const override { auto const& item = *iter_; if (metadata_) return Ledger::deserializeTxPlusMeta(item); return {Ledger::deserializeTx(item), nullptr}; } }; //------------------------------------------------------------------------------ Ledger::Ledger( create_genesis_t, Rules rules, Fees const& fees, std::vector const& amendments, Family& family) : mImmutable(false) , txMap_(SHAMapType::TRANSACTION, family) , stateMap_(SHAMapType::STATE, family) , fees_(fees) , rules_(std::move(rules)) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.seq = 1; header_.drops = INITIAL_XRP; header_.closeTimeResolution = ledgerGenesisTimeResolution; static auto const id = calcAccountID(generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase")).first); { auto const sle = std::make_shared(keylet::account(id)); sle->setFieldU32(sfSequence, 1); sle->setAccountID(sfAccount, id); sle->setFieldAmount(sfBalance, header_.drops); rawInsert(sle); } if (!amendments.empty()) { auto const sle = std::make_shared(keylet::amendments()); sle->setFieldV256(sfAmendments, STVector256{amendments}); rawInsert(sle); } { auto sle = std::make_shared(keylet::fees()); // Whether featureXRPFees is supported will depend on startup options. if (std::ranges::find(amendments, featureXRPFees) != amendments.end()) { sle->at(sfBaseFeeDrops) = fees.base; sle->at(sfReserveBaseDrops) = fees.reserve; sle->at(sfReserveIncrementDrops) = fees.increment; } else { if (auto const f = fees.base.dropsAs()) sle->at(sfBaseFee) = *f; if (auto const f = fees.reserve.dropsAs()) sle->at(sfReserveBase) = *f; if (auto const f = fees.increment.dropsAs()) sle->at(sfReserveIncrement) = *f; sle->at(sfReferenceFeeUnits) = FEE_UNITS_DEPRECATED; } rawInsert(sle); } stateMap_.flushDirty(hotACCOUNT_NODE); setImmutable(); } Ledger::Ledger( LedgerHeader const& info, bool& loaded, bool acquire, Rules rules, Fees const& fees, Family& family, beast::Journal j) : mImmutable(true) , txMap_(SHAMapType::TRANSACTION, info.txHash, family) , stateMap_(SHAMapType::STATE, info.accountHash, family) , fees_(fees) , rules_(std::move(rules)) , header_(info) , j_(j) { loaded = true; if (header_.txHash.isNonZero() && !txMap_.fetchRoot(SHAMapHash{header_.txHash}, nullptr)) { loaded = false; JLOG(j.warn()) << "Don't have transaction root for ledger" << header_.seq; } if (header_.accountHash.isNonZero() && !stateMap_.fetchRoot(SHAMapHash{header_.accountHash}, nullptr)) { loaded = false; JLOG(j.warn()) << "Don't have state data root for ledger" << header_.seq; } txMap_.setImmutable(); stateMap_.setImmutable(); if (!setup()) loaded = false; if (!loaded) { header_.hash = calculateLedgerHash(header_); if (acquire) family.missingNodeAcquireByHash(header_.hash, header_.seq); } } // Create a new ledger that follows this one Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime) : mImmutable(false) , txMap_(SHAMapType::TRANSACTION, prevLedger.txMap_.family()) , stateMap_(prevLedger.stateMap_, true) , fees_(prevLedger.fees_) , rules_(prevLedger.rules_) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.seq = prevLedger.header_.seq + 1; header_.parentCloseTime = prevLedger.header_.closeTime; header_.hash = prevLedger.header().hash + uint256(1); header_.drops = prevLedger.header().drops; header_.closeTimeResolution = prevLedger.header_.closeTimeResolution; header_.parentHash = prevLedger.header().hash; header_.closeTimeResolution = getNextLedgerTimeResolution( prevLedger.header_.closeTimeResolution, getCloseAgree(prevLedger.header()), header_.seq); if (prevLedger.header_.closeTime == NetClock::time_point{}) { header_.closeTime = roundCloseTime(closeTime, header_.closeTimeResolution); } else { header_.closeTime = prevLedger.header_.closeTime + header_.closeTimeResolution; } } Ledger::Ledger(LedgerHeader const& info, Rules rules, Family& family) : mImmutable(true) , txMap_(SHAMapType::TRANSACTION, info.txHash, family) , stateMap_(SHAMapType::STATE, info.accountHash, family) , rules_(std::move(rules)) , header_(info) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.hash = calculateLedgerHash(header_); } Ledger::Ledger( std::uint32_t ledgerSeq, NetClock::time_point closeTime, Rules rules, Fees const& fees, Family& family) : mImmutable(false) , txMap_(SHAMapType::TRANSACTION, family) , stateMap_(SHAMapType::STATE, family) , fees_(fees) , rules_(std::move(rules)) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.seq = ledgerSeq; header_.closeTime = closeTime; header_.closeTimeResolution = ledgerDefaultTimeResolution; setup(); } void Ledger::setImmutable(bool rehash) { // Force update, since this is the only // place the hash transitions to valid if (!mImmutable && rehash) { header_.txHash = txMap_.getHash().as_uint256(); header_.accountHash = stateMap_.getHash().as_uint256(); } if (rehash) header_.hash = calculateLedgerHash(header_); mImmutable = true; txMap_.setImmutable(); stateMap_.setImmutable(); setup(); } void Ledger::setAccepted( NetClock::time_point closeTime, NetClock::duration closeResolution, bool correctCloseTime) { // Used when we witnessed the consensus. XRPL_ASSERT(!open(), "xrpl::Ledger::setAccepted : valid ledger state"); header_.closeTime = closeTime; header_.closeTimeResolution = closeResolution; header_.closeFlags = correctCloseTime ? 0 : sLCF_NoConsensusTime; setImmutable(); } bool Ledger::addSLE(SLE const& sle) { auto const s = sle.getSerializer(); return stateMap_.addItem( SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle.key(), s.slice())); } //------------------------------------------------------------------------------ std::shared_ptr Ledger::deserializeTx(SHAMapItem const& item) { SerialIter sit(item.slice()); return std::make_shared(sit); } std::pair, std::shared_ptr> Ledger::deserializeTxPlusMeta(SHAMapItem const& item) { std::pair, std::shared_ptr> result; SerialIter sit(item.slice()); { SerialIter s(sit.getSlice(sit.getVLDataLength())); result.first = std::make_shared(s); } { SerialIter s(sit.getSlice(sit.getVLDataLength())); result.second = std::make_shared(s, sfMetadata); } return result; } //------------------------------------------------------------------------------ bool Ledger::exists(Keylet const& k) const { // VFALCO NOTE Perhaps check the type for debug builds? return stateMap_.hasItem(k.key); } bool Ledger::exists(uint256 const& key) const { return stateMap_.hasItem(key); } std::optional Ledger::succ(uint256 const& key, std::optional const& last) const { auto item = stateMap_.upper_bound(key); if (item == stateMap_.end()) return std::nullopt; if (last && item->key() >= last) return std::nullopt; return item->key(); } std::shared_ptr Ledger::read(Keylet const& k) const { if (k.key == beast::zero) { // LCOV_EXCL_START UNREACHABLE("xrpl::Ledger::read : zero key"); return nullptr; // LCOV_EXCL_STOP } auto const& item = stateMap_.peekItem(k.key); if (!item) return nullptr; auto sle = std::make_shared(SerialIter{item->slice()}, item->key()); if (!k.check(*sle)) return nullptr; return sle; } //------------------------------------------------------------------------------ auto Ledger::slesBegin() const -> std::unique_ptr { return std::make_unique(stateMap_.begin()); } auto Ledger::slesEnd() const -> std::unique_ptr { return std::make_unique(stateMap_.end()); } auto Ledger::slesUpperBound(uint256 const& key) const -> std::unique_ptr { return std::make_unique(stateMap_.upper_bound(key)); } auto Ledger::txsBegin() const -> std::unique_ptr { return std::make_unique(!open(), txMap_.begin()); } auto Ledger::txsEnd() const -> std::unique_ptr { return std::make_unique(!open(), txMap_.end()); } bool Ledger::txExists(uint256 const& key) const { return txMap_.hasItem(key); } auto Ledger::txRead(key_type const& key) const -> tx_type { auto const& item = txMap_.peekItem(key); if (!item) return {}; if (!open()) { auto result = deserializeTxPlusMeta(*item); return {std::move(result.first), std::move(result.second)}; } return {deserializeTx(*item), nullptr}; } auto Ledger::digest(key_type const& key) const -> std::optional { SHAMapHash digest; // VFALCO Unfortunately this loads the item // from the NodeStore needlessly. if (!stateMap_.peekItem(key, digest)) return std::nullopt; return digest.as_uint256(); } //------------------------------------------------------------------------------ void Ledger::rawErase(std::shared_ptr const& sle) { if (!stateMap_.delItem(sle->key())) Throw("Ledger::rawErase: key not found"); } void Ledger::rawErase(uint256 const& key) { if (!stateMap_.delItem(key)) Throw("Ledger::rawErase: key not found"); } void Ledger::rawInsert(std::shared_ptr const& sle) { Serializer ss; sle->add(ss); if (!stateMap_.addGiveItem( SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice()))) Throw("Ledger::rawInsert: key already exists"); } void Ledger::rawReplace(std::shared_ptr const& sle) { Serializer ss; sle->add(ss); if (!stateMap_.updateGiveItem( SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice()))) Throw("Ledger::rawReplace: key not found"); } void Ledger::rawTxInsert( uint256 const& key, std::shared_ptr const& txn, std::shared_ptr const& metaData) { XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsert : non-null metadata input"); // low-level - just add to table Serializer s(txn->getDataLength() + metaData->getDataLength() + 16); s.addVL(txn->peekData()); s.addVL(metaData->peekData()); if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, make_shamapitem(key, s.slice()))) Throw("duplicate_tx: " + to_string(key)); } uint256 Ledger::rawTxInsertWithHash( uint256 const& key, std::shared_ptr const& txn, std::shared_ptr const& metaData) { XRPL_ASSERT(metaData, "xrpl::Ledger::rawTxInsertWithHash : non-null metadata input"); // low-level - just add to table Serializer s(txn->getDataLength() + metaData->getDataLength() + 16); s.addVL(txn->peekData()); s.addVL(metaData->peekData()); auto item = make_shamapitem(key, s.slice()); auto hash = sha512Half(HashPrefix::txNode, item->slice(), item->key()); if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, std::move(item))) Throw("duplicate_tx: " + to_string(key)); return hash; } bool Ledger::setup() { bool ret = true; try { rules_ = makeRulesGivenLedger(*this, rules_); } catch (SHAMapMissingNode const&) { ret = false; } catch (std::exception const& ex) { JLOG(j_.error()) << "Exception in " << __func__ << ": " << ex.what(); Rethrow(); } try { if (auto const sle = read(keylet::fees())) { bool oldFees = false; bool newFees = false; { auto const baseFee = sle->at(~sfBaseFee); auto const reserveBase = sle->at(~sfReserveBase); auto const reserveIncrement = sle->at(~sfReserveIncrement); if (baseFee) fees_.base = *baseFee; if (reserveBase) fees_.reserve = *reserveBase; if (reserveIncrement) fees_.increment = *reserveIncrement; oldFees = baseFee || reserveBase || reserveIncrement; } { auto const baseFeeXRP = sle->at(~sfBaseFeeDrops); auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops); auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops); auto assign = [&ret](XRPAmount& dest, std::optional const& src) { if (src) { if (src->native()) { dest = src->xrp(); } else { ret = false; } } }; assign(fees_.base, baseFeeXRP); assign(fees_.reserve, reserveBaseXRP); assign(fees_.increment, reserveIncrementXRP); newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP; } if (oldFees && newFees) { // Should be all of one or the other, but not both ret = false; } if (!rules_.enabled(featureXRPFees) && newFees) { // Can't populate the new fees before the amendment is enabled ret = false; } } } catch (SHAMapMissingNode const&) { ret = false; } catch (std::exception const& ex) { JLOG(j_.error()) << "Exception in " << __func__ << ": " << ex.what(); Rethrow(); } return ret; } std::shared_ptr Ledger::peek(Keylet const& k) const { auto const& value = stateMap_.peekItem(k.key); if (!value) return nullptr; auto sle = std::make_shared(SerialIter{value->slice()}, value->key()); if (!k.check(*sle)) return nullptr; return sle; } hash_set Ledger::negativeUNL() const { hash_set negUnl; if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfDisabledValidators)) { auto const& nUnlData = sle->getFieldArray(sfDisabledValidators); for (auto const& n : nUnlData) { if (n.isFieldPresent(sfPublicKey)) { auto d = n.getFieldVL(sfPublicKey); auto s = makeSlice(d); if (!publicKeyType(s)) { continue; } negUnl.emplace(s); } } } return negUnl; } std::optional Ledger::validatorToDisable() const { if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToDisable)) { auto d = sle->getFieldVL(sfValidatorToDisable); auto s = makeSlice(d); if (publicKeyType(s)) return PublicKey(s); } return std::nullopt; } std::optional Ledger::validatorToReEnable() const { if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToReEnable)) { auto d = sle->getFieldVL(sfValidatorToReEnable); auto s = makeSlice(d); if (publicKeyType(s)) return PublicKey(s); } return std::nullopt; } void Ledger::updateNegativeUNL() { auto sle = peek(keylet::negativeUNL()); if (!sle) return; bool const hasToDisable = sle->isFieldPresent(sfValidatorToDisable); bool const hasToReEnable = sle->isFieldPresent(sfValidatorToReEnable); if (!hasToDisable && !hasToReEnable) return; STArray newNUnl; if (sle->isFieldPresent(sfDisabledValidators)) { auto const& oldNUnl = sle->getFieldArray(sfDisabledValidators); for (auto const& v : oldNUnl) { if (hasToReEnable && v.isFieldPresent(sfPublicKey) && v.getFieldVL(sfPublicKey) == sle->getFieldVL(sfValidatorToReEnable)) continue; newNUnl.push_back(v); } } if (hasToDisable) { newNUnl.push_back(STObject::makeInnerObject(sfDisabledValidator)); newNUnl.back().setFieldVL(sfPublicKey, sle->getFieldVL(sfValidatorToDisable)); newNUnl.back().setFieldU32(sfFirstLedgerSequence, seq()); } if (!newNUnl.empty()) { sle->setFieldArray(sfDisabledValidators, newNUnl); if (hasToReEnable) sle->makeFieldAbsent(sfValidatorToReEnable); if (hasToDisable) sle->makeFieldAbsent(sfValidatorToDisable); rawReplace(sle); } else { rawErase(sle); } } //------------------------------------------------------------------------------ bool Ledger::walkLedger(beast::Journal j, bool parallel) const { std::vector missingNodes1; std::vector missingNodes2; if (stateMap_.getHash().isZero() && !header_.accountHash.isZero() && !stateMap_.fetchRoot(SHAMapHash{header_.accountHash}, nullptr)) { missingNodes1.emplace_back(SHAMapType::STATE, SHAMapHash{header_.accountHash}); } else { if (parallel) { return stateMap_.walkMapParallel(missingNodes1, 32); } stateMap_.walkMap(missingNodes1, 32); } if (!missingNodes1.empty()) { if (auto stream = j.info()) { stream << missingNodes1.size() << " missing account node(s)"; stream << "First: " << missingNodes1[0].what(); } } if (txMap_.getHash().isZero() && header_.txHash.isNonZero() && !txMap_.fetchRoot(SHAMapHash{header_.txHash}, nullptr)) { missingNodes2.emplace_back(SHAMapType::TRANSACTION, SHAMapHash{header_.txHash}); } else { txMap_.walkMap(missingNodes2, 32); } if (!missingNodes2.empty()) { if (auto stream = j.info()) { stream << missingNodes2.size() << " missing transaction node(s)"; stream << "First: " << missingNodes2[0].what(); } } return missingNodes1.empty() && missingNodes2.empty(); } bool Ledger::isSensible() const { if (header_.hash.isZero()) return false; if (header_.accountHash.isZero()) return false; if (header_.accountHash != stateMap_.getHash().as_uint256()) return false; if (header_.txHash != txMap_.getHash().as_uint256()) return false; return true; } // update the skip list with the information from our previous ledger // VFALCO TODO Document this skip list concept void Ledger::updateSkipList() { if (header_.seq == 0) // genesis ledger has no previous ledger return; std::uint32_t const prevIndex = header_.seq - 1; // update record of every 256th ledger if ((prevIndex & 0xff) == 0) { auto const k = keylet::skip(prevIndex); auto sle = peek(k); std::vector hashes; bool created = false; if (!sle) { sle = std::make_shared(k); created = true; } else { hashes = static_cast(sle->getFieldV256(sfHashes)); created = false; } XRPL_ASSERT( hashes.size() <= 256, "xrpl::Ledger::updateSkipList : first maximum hashes size"); hashes.push_back(header_.parentHash); sle->setFieldV256(sfHashes, STVector256(hashes)); sle->setFieldU32(sfLastLedgerSequence, prevIndex); if (created) { rawInsert(sle); } else { rawReplace(sle); } } // update record of past 256 ledger auto const k = keylet::skip(); auto sle = peek(k); std::vector hashes; bool created = false; if (!sle) { sle = std::make_shared(k); created = true; } else { hashes = static_cast(sle->getFieldV256(sfHashes)); created = false; } XRPL_ASSERT(hashes.size() <= 256, "xrpl::Ledger::updateSkipList : second maximum hashes size"); if (hashes.size() == 256) hashes.erase(hashes.begin()); hashes.push_back(header_.parentHash); sle->setFieldV256(sfHashes, STVector256(hashes)); sle->setFieldU32(sfLastLedgerSequence, prevIndex); if (created) { rawInsert(sle); } else { rawReplace(sle); } } bool Ledger::isFlagLedger() const { return ::xrpl::isFlagLedger(header_.seq); } bool Ledger::isVotingLedger() const { return ::xrpl::isVotingLedger(header_.seq + 1); } void Ledger::unshare() const { stateMap_.unshare(); txMap_.unshare(); } void Ledger::invariants() const { stateMap_.invariants(); txMap_.invariants(); } } // namespace xrpl