#include #include #include #include #include #include #include // IWYU pragma: keep #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::detail { void ApplyStateTable::apply(RawView& to) const { to.rawDestroyXRP(dropsDestroyed_); for (auto const& item : items_) { auto const& sle = item.second.second; switch (item.second.first) { case Action::Cache: break; case Action::Erase: to.rawErase(sle); break; case Action::Insert: to.rawInsert(sle); break; case Action::Modify: to.rawReplace(sle); break; }; } } std::size_t ApplyStateTable::size() const { std::size_t ret = 0; for (auto& item : items_) { switch (item.second.first) { case Action::Erase: case Action::Insert: case Action::Modify: ++ret; default: break; } } return ret; } void ApplyStateTable::visit( ReadView const& to, std::function const& before, std::shared_ptr const& after)> const& func) const { for (auto& item : items_) { switch (item.second.first) { case Action::Erase: func(item.first, true, to.read(keylet::unchecked(item.first)), item.second.second); break; case Action::Insert: func(item.first, false, nullptr, item.second.second); break; case Action::Modify: func(item.first, false, to.read(keylet::unchecked(item.first)), item.second.second); break; default: break; } } } std::optional ApplyStateTable::apply( OpenView& to, STTx const& tx, TER ter, std::optional const& deliver, std::optional const& parentBatchId, bool isDryRun, beast::Journal j) { // Build metadata and insert auto const sTx = std::make_shared(); tx.add(*sTx); std::shared_ptr sMeta; std::optional metadata; if (!to.open() || isDryRun) { TxMeta meta(tx.getTransactionID(), to.seq()); meta.setDeliveredAmount(deliver); meta.setParentBatchID(parentBatchId); Mods newMod; for (auto& item : items_) { SField const* type = nullptr; switch (item.second.first) { default: case Action::Cache: continue; case Action::Erase: type = &sfDeletedNode; break; case Action::Insert: type = &sfCreatedNode; break; case Action::Modify: type = &sfModifiedNode; break; } auto const origNode = to.read(keylet::unchecked(item.first)); auto curNode = item.second.second; if ((type == &sfModifiedNode) && (*curNode == *origNode)) continue; std::uint16_t const nodeType = curNode ? curNode->getFieldU16(sfLedgerEntryType) : origNode->getFieldU16(sfLedgerEntryType); meta.setAffectedNode(item.first, *type, nodeType); if (type == &sfDeletedNode) { XRPL_ASSERT( origNode && curNode, "xrpl::detail::ApplyStateTable::apply : valid nodes for " "deletion"); threadOwners(to, meta, origNode, newMod, j); STObject prevs(sfPreviousFields); for (auto const& obj : *origNode) { // go through the original node for // modified fields saved on modification if (obj.getFName().shouldMeta(SField::kSmdChangeOrig) && !curNode->hasMatchingEntry(obj)) prevs.emplaceBack(obj); } if (!prevs.empty()) meta.getAffectedNode(item.first).emplaceBack(std::move(prevs)); STObject finals(sfFinalFields); for (auto const& obj : *curNode) { // go through the final node for final fields if (obj.getFName().shouldMeta(SField::kSmdAlways | SField::kSmdDeleteFinal)) finals.emplaceBack(obj); } if (!finals.empty()) meta.getAffectedNode(item.first).emplaceBack(std::move(finals)); } else if (type == &sfModifiedNode) { XRPL_ASSERT( curNode && origNode, "xrpl::detail::ApplyStateTable::apply : valid nodes for " "modification"); if (curNode->isThreadedType(to.rules())) { // thread transaction to node // item modified threadItem(meta, curNode); } STObject prevs(sfPreviousFields); for (auto const& obj : *origNode) { // search the original node for values saved on modify if (obj.getFName().shouldMeta(SField::kSmdChangeOrig) && !curNode->hasMatchingEntry(obj)) prevs.emplaceBack(obj); } if (!prevs.empty()) meta.getAffectedNode(item.first).emplaceBack(std::move(prevs)); STObject finals(sfFinalFields); for (auto const& obj : *curNode) { // search the final node for values saved always if (obj.getFName().shouldMeta(SField::kSmdAlways | SField::kSmdChangeNew)) finals.emplaceBack(obj); } if (!finals.empty()) meta.getAffectedNode(item.first).emplaceBack(std::move(finals)); } else if (type == &sfCreatedNode) // if created, thread to owner(s) { XRPL_ASSERT( curNode && !origNode, "xrpl::detail::ApplyStateTable::apply : valid nodes for " "creation"); threadOwners(to, meta, curNode, newMod, j); if (curNode->isThreadedType(to.rules())) // always thread to self threadItem(meta, curNode); STObject news(sfNewFields); for (auto const& obj : *curNode) { // save non-default values if (!obj.isDefault() && obj.getFName().shouldMeta(SField::kSmdCreate | SField::kSmdAlways)) news.emplaceBack(obj); } if (!news.empty()) meta.getAffectedNode(item.first).emplaceBack(std::move(news)); } else { // LCOV_EXCL_START UNREACHABLE( "xrpl::detail::ApplyStateTable::apply : unsupported " "operation type"); // LCOV_EXCL_STOP } } if (!isDryRun) { // add any new modified nodes to the modification set for (auto const& mod : newMod) to.rawReplace(mod.second); } sMeta = std::make_shared(); meta.addRaw(*sMeta, ter, to.txCount()); // VFALCO For diagnostics do we want to show // metadata even when the base view is open? JLOG(j.trace()) << "metadata " << meta.getJson(JsonOptions::Values::None); metadata = meta; } if (!isDryRun) { to.rawTxInsert(tx.getTransactionID(), sTx, sMeta); apply(to); } return metadata; } //--- bool ApplyStateTable::exists(ReadView const& base, Keylet const& k) const { auto const iter = items_.find(k.key); if (iter == items_.end()) return base.exists(k); auto const& item = iter->second; auto const& sle = item.second; switch (item.first) { case Action::Erase: return false; case Action::Cache: case Action::Insert: case Action::Modify: break; } return k.check(*sle); } auto ApplyStateTable::succ( ReadView const& base, key_type const& key, std::optional const& last) const -> std::optional { std::optional next = key; items_t::const_iterator iter; // Find base successor that is // not also deleted in our list do { next = base.succ(*next, last); if (!next) break; iter = items_.find(*next); } while (iter != items_.end() && iter->second.first == Action::Erase); // Find non-deleted successor in our list for (iter = items_.upper_bound(key); iter != items_.end(); ++iter) { if (iter->second.first != Action::Erase) { // Found both, return the lower key if (!next || next > iter->first) next = iter->first; break; } } // Nothing in our list, return // what we got from the parent. if (last && next >= last) return std::nullopt; return next; } std::shared_ptr ApplyStateTable::read(ReadView const& base, Keylet const& k) const { auto const iter = items_.find(k.key); if (iter == items_.end()) return base.read(k); auto const& item = iter->second; auto const& sle = item.second; switch (item.first) { case Action::Erase: return nullptr; case Action::Cache: case Action::Insert: case Action::Modify: break; }; if (!k.check(*sle)) return nullptr; return sle; } std::shared_ptr ApplyStateTable::peek(ReadView const& base, Keylet const& k) { auto iter = items_.lower_bound(k.key); if (iter == items_.end() || iter->first != k.key) { auto const sle = base.read(k); if (!sle) return nullptr; // Make our own copy using namespace std; iter = items_.emplace_hint( iter, piecewise_construct, forward_as_tuple(sle->key()), forward_as_tuple(Action::Cache, make_shared(*sle))); return iter->second.second; } auto const& item = iter->second; auto const& sle = item.second; switch (item.first) { case Action::Erase: return nullptr; case Action::Cache: case Action::Insert: case Action::Modify: break; }; if (!k.check(*sle)) return nullptr; return sle; } void ApplyStateTable::erase(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) Throw("ApplyStateTable::erase: missing key"); auto& item = iter->second; if (item.second != sle) Throw("ApplyStateTable::erase: unknown SLE"); switch (item.first) { case Action::Erase: Throw("ApplyStateTable::erase: double erase"); break; case Action::Insert: items_.erase(iter); break; case Action::Cache: case Action::Modify: item.first = Action::Erase; break; } } void ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr const& sle) { using namespace std; auto const result = items_.emplace( piecewise_construct, forward_as_tuple(sle->key()), forward_as_tuple(Action::Erase, sle)); if (result.second) return; auto& item = result.first->second; switch (item.first) { case Action::Erase: Throw("ApplyStateTable::rawErase: double erase"); break; case Action::Insert: items_.erase(result.first); break; case Action::Cache: case Action::Modify: item.first = Action::Erase; item.second = sle; break; } } void ApplyStateTable::insert(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.lower_bound(sle->key()); if (iter == items_.end() || iter->first != sle->key()) { using namespace std; items_.emplace_hint( iter, piecewise_construct, forward_as_tuple(sle->key()), forward_as_tuple(Action::Insert, sle)); return; } auto& item = iter->second; switch (item.first) { case Action::Cache: Throw("ApplyStateTable::insert: already cached"); case Action::Insert: Throw("ApplyStateTable::insert: already inserted"); case Action::Modify: Throw("ApplyStateTable::insert: already modified"); case Action::Erase: break; } item.first = Action::Modify; item.second = sle; } void ApplyStateTable::replace(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.lower_bound(sle->key()); if (iter == items_.end() || iter->first != sle->key()) { using namespace std; items_.emplace_hint( iter, piecewise_construct, forward_as_tuple(sle->key()), forward_as_tuple(Action::Modify, sle)); return; } auto& item = iter->second; switch (item.first) { case Action::Erase: Throw("ApplyStateTable::replace: already erased"); case Action::Cache: item.first = Action::Modify; break; case Action::Insert: case Action::Modify: break; } item.second = sle; } void ApplyStateTable::update(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) Throw("ApplyStateTable::update: missing key"); auto& item = iter->second; if (item.second != sle) Throw("ApplyStateTable::update: unknown SLE"); switch (item.first) { case Action::Erase: Throw("ApplyStateTable::update: erased"); break; case Action::Cache: item.first = Action::Modify; break; case Action::Insert: case Action::Modify: break; }; } void ApplyStateTable::destroyXRP(XRPAmount const& fee) { dropsDestroyed_ += fee; } //------------------------------------------------------------------------------ // Insert this transaction to the SLE's threading list void ApplyStateTable::threadItem(TxMeta& meta, std::shared_ptr const& sle) { key_type prevTxID; LedgerIndex prevLgrID = 0; if (!sle->thread(meta.getTxID(), meta.getLgrSeq(), prevTxID, prevLgrID)) return; if (!prevTxID.isZero()) { auto& node = meta.getAffectedNode(sle, sfModifiedNode); if (node.getFieldIndex(sfPreviousTxnID) == -1) { XRPL_ASSERT( node.getFieldIndex(sfPreviousTxnLgrSeq) == -1, "xrpl::ApplyStateTable::threadItem : previous ledger is not " "set"); node.setFieldH256(sfPreviousTxnID, prevTxID); node.setFieldU32(sfPreviousTxnLgrSeq, prevLgrID); } XRPL_ASSERT( node.getFieldH256(sfPreviousTxnID) == prevTxID, "xrpl::ApplyStateTable::threadItem : previous transaction is a " "match"); XRPL_ASSERT( node.getFieldU32(sfPreviousTxnLgrSeq) == prevLgrID, "xrpl::ApplyStateTable::threadItem : previous ledger is a match"); } } std::shared_ptr ApplyStateTable::getForMod(ReadView const& base, key_type const& key, Mods& mods, beast::Journal j) { { auto miter = mods.find(key); if (miter != mods.end()) { XRPL_ASSERT(miter->second, "xrpl::ApplyStateTable::getForMod : non-null result"); return miter->second; } } { auto iter = items_.find(key); if (iter != items_.end()) { auto const& item = iter->second; if (item.first == Action::Erase) { // The Destination of an Escrow or a PayChannel may have been // deleted. In that case the account we're threading to will // not be found and it is appropriate to return a nullptr. JLOG(j.warn()) << "Trying to thread to deleted node"; return nullptr; } if (item.first != Action::Cache) return item.second; // If it's only cached, then the node is being modified only by // metadata; fall through and track it in the mods table. } } auto c = base.read(keylet::unchecked(key)); if (!c) { // The Destination of an Escrow or a PayChannel may have been // deleted. In that case the account we're threading to will // not be found and it is appropriate to return a nullptr. JLOG(j.warn()) << "ApplyStateTable::getForMod: key not found"; return nullptr; } auto sle = std::make_shared(*c); mods.emplace(key, sle); return sle; } void ApplyStateTable::threadTx( ReadView const& base, TxMeta& meta, AccountID const& to, Mods& mods, beast::Journal j) { auto const sle = getForMod(base, keylet::account(to).key, mods, j); if (!sle) { // The Destination of an Escrow or PayChannel may have been deleted. // In that case the account we are threading to will not be found. // So this logging is just a warning. JLOG(j.warn()) << "Threading to non-existent account: " << toBase58(to); return; } // threadItem only applied to AccountRoot XRPL_ASSERT( sle->isThreadedType(base.rules()), "xrpl::ApplyStateTable::threadTx : SLE is threaded"); threadItem(meta, sle); } void ApplyStateTable::threadOwners( ReadView const& base, TxMeta& meta, std::shared_ptr const& sle, Mods& mods, beast::Journal j) { LedgerEntryType const ledgerType{sle->getType()}; switch (ledgerType) { case ltACCOUNT_ROOT: { // Nothing to do break; } case ltRIPPLE_STATE: { threadTx(base, meta, (*sle)[sfLowLimit].getIssuer(), mods, j); threadTx(base, meta, (*sle)[sfHighLimit].getIssuer(), mods, j); break; } default: { // If sfAccount is present, thread to that account if (auto const optSleAcct{(*sle)[~sfAccount]}) threadTx(base, meta, *optSleAcct, mods, j); // If sfDestination is present, thread to that account if (auto const optSleDest{(*sle)[~sfDestination]}) threadTx(base, meta, *optSleDest, mods, j); } } } } // namespace xrpl::detail