mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
Refactor View classes:
The View hierarchy of classes is reorganized to include new classes with member functions moved and renamed, to solve defects in the original design: OpenView accumulates raw state and tx changes and can be applied to the base. ApplyView accumulates changes for a single transaction, including metadata, and can be applied to an OpenView. The Sandbox allows changes with the option to apply or throw them out. The PaymentSandbox provides a sandbox with account credit deferral. Call sites are changed to use the class appropriate for the task.
This commit is contained in:
589
src/ripple/ledger/impl/ApplyStateTable.cpp
Normal file
589
src/ripple/ledger/impl/ApplyStateTable.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/ledger/detail/ApplyStateTable.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace ripple {
|
||||
namespace 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::apply (OpenView& to,
|
||||
STTx const& tx, TER ter,
|
||||
boost::optional<STAmount> const& deliver,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Build metadata and insert
|
||||
auto const sTx =
|
||||
std::make_shared<Serializer>();
|
||||
tx.add(*sTx);
|
||||
std::shared_ptr<Serializer> sMeta;
|
||||
if (to.closed())
|
||||
{
|
||||
TxMeta meta;
|
||||
// VFALCO Shouldn't TxMeta ctor do this?
|
||||
meta.init (tx.getTransactionID(), to.seq());
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
Mods newMod;
|
||||
// VFALCO NOTE getForMod can insert items with
|
||||
// Action::cache during the loop.
|
||||
for (auto& item : items_)
|
||||
{
|
||||
SField const* type;
|
||||
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 nodeType = curNode
|
||||
? curNode->getFieldU16 (sfLedgerEntryType)
|
||||
: origNode->getFieldU16 (sfLedgerEntryType);
|
||||
meta.setAffectedNode (item.first, *type, nodeType);
|
||||
if (type == &sfDeletedNode)
|
||||
{
|
||||
assert (origNode && curNode);
|
||||
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::sMD_ChangeOrig) &&
|
||||
! curNode->hasMatchingEntry (obj))
|
||||
prevs.emplace_back (obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty ())
|
||||
meta.getAffectedNode(item.first).emplace_back(std::move(prevs));
|
||||
|
||||
STObject finals (sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// go through the final node for final fields
|
||||
if (obj.getFName().shouldMeta(
|
||||
SField::sMD_Always | SField::sMD_DeleteFinal))
|
||||
finals.emplace_back (obj);
|
||||
}
|
||||
|
||||
if (!finals.empty ())
|
||||
meta.getAffectedNode (item.first).emplace_back (std::move(finals));
|
||||
}
|
||||
else if (type == &sfModifiedNode)
|
||||
{
|
||||
assert (curNode && origNode);
|
||||
|
||||
if (curNode->isThreadedType ()) // thread transaction to node item modified
|
||||
threadTx (meta, curNode, newMod);
|
||||
|
||||
STObject prevs (sfPreviousFields);
|
||||
for (auto const& obj : *origNode)
|
||||
{
|
||||
// search the original node for values saved on modify
|
||||
if (obj.getFName ().shouldMeta (SField::sMD_ChangeOrig) && !curNode->hasMatchingEntry (obj))
|
||||
prevs.emplace_back (obj);
|
||||
}
|
||||
|
||||
if (!prevs.empty ())
|
||||
meta.getAffectedNode (item.first).emplace_back (std::move(prevs));
|
||||
|
||||
STObject finals (sfFinalFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// search the final node for values saved always
|
||||
if (obj.getFName ().shouldMeta (SField::sMD_Always | SField::sMD_ChangeNew))
|
||||
finals.emplace_back (obj);
|
||||
}
|
||||
|
||||
if (!finals.empty ())
|
||||
meta.getAffectedNode (item.first).emplace_back (std::move(finals));
|
||||
}
|
||||
else if (type == &sfCreatedNode) // if created, thread to owner(s)
|
||||
{
|
||||
assert (curNode && !origNode);
|
||||
threadOwners (to, meta, curNode, newMod, j);
|
||||
|
||||
if (curNode->isThreadedType ()) // always thread to self
|
||||
threadTx (meta, curNode, newMod);
|
||||
|
||||
STObject news (sfNewFields);
|
||||
for (auto const& obj : *curNode)
|
||||
{
|
||||
// save non-default values
|
||||
if (!obj.isDefault () &&
|
||||
obj.getFName().shouldMeta(
|
||||
SField::sMD_Create | SField::sMD_Always))
|
||||
news.emplace_back (obj);
|
||||
}
|
||||
|
||||
if (!news.empty ())
|
||||
meta.getAffectedNode (item.first).emplace_back (std::move(news));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (false);
|
||||
}
|
||||
}
|
||||
|
||||
// add any new modified nodes to the modification set
|
||||
for (auto& mod : newMod)
|
||||
update (to, mod.second);
|
||||
|
||||
sMeta = std::make_shared<Serializer>();
|
||||
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 (0);
|
||||
}
|
||||
to.rawTxInsert(
|
||||
tx.getTransactionID(),
|
||||
sTx, sMeta);
|
||||
apply(to);
|
||||
}
|
||||
|
||||
//---
|
||||
|
||||
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;
|
||||
}
|
||||
if (! k.check(*sle))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto
|
||||
ApplyStateTable::succ (ReadView const& base,
|
||||
key_type const& key, boost::optional<
|
||||
key_type> const& last) const ->
|
||||
boost::optional<key_type>
|
||||
{
|
||||
boost::optional<key_type> 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 boost::none;
|
||||
return next;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
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<SLE>
|
||||
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>(*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<SLE> const& sle)
|
||||
{
|
||||
auto const iter =
|
||||
items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
LogicError("ApplyStateTable::erase: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
LogicError("ApplyStateTable::erase: unknown SLE");
|
||||
switch(item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("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<SLE> 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:
|
||||
LogicError("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<SLE> 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:
|
||||
LogicError("ApplyStateTable::insert: already cached");
|
||||
case Action::insert:
|
||||
LogicError("ApplyStateTable::insert: already inserted");
|
||||
case Action::modify:
|
||||
LogicError("ApplyStateTable::insert: already modified");
|
||||
case Action::erase:
|
||||
break;
|
||||
}
|
||||
item.first = Action::modify;
|
||||
item.second = sle;
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::replace (ReadView const& base,
|
||||
std::shared_ptr<SLE> 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:
|
||||
LogicError("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<SLE> const& sle)
|
||||
{
|
||||
auto const iter =
|
||||
items_.find(sle->key());
|
||||
if (iter == items_.end())
|
||||
LogicError("ApplyStateTable::update: missing key");
|
||||
auto& item = iter->second;
|
||||
if (item.second != sle)
|
||||
LogicError("ApplyStateTable::update: unknown SLE");
|
||||
switch (item.first)
|
||||
{
|
||||
case Action::erase:
|
||||
LogicError("ApplyStateTable::update: erased");
|
||||
break;
|
||||
case Action::cache:
|
||||
item.first = Action::modify;
|
||||
break;
|
||||
case Action::insert:
|
||||
case Action::modify:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ApplyStateTable::destroyXRP(std::uint64_t feeDrops)
|
||||
{
|
||||
dropsDestroyed_ += feeDrops;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
ApplyStateTable::threadTx (TxMeta& meta,
|
||||
std::shared_ptr<SLE> const& to,
|
||||
Mods& mods)
|
||||
{
|
||||
key_type prevTxID;
|
||||
std::uint32_t prevLgrID;
|
||||
if (! to->thread(meta.getTxID(),
|
||||
meta.getLgrSeq(), prevTxID, prevLgrID))
|
||||
return false;
|
||||
if (prevTxID.isZero () ||
|
||||
TxMeta::thread(
|
||||
meta.getAffectedNode(to,
|
||||
sfModifiedNode), prevTxID,
|
||||
prevLgrID))
|
||||
return true;
|
||||
assert (false);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
ApplyStateTable::getForMod (ReadView const& base,
|
||||
key_type const& key, Mods& mods, beast::Journal j)
|
||||
{
|
||||
auto iter = items_.find (key);
|
||||
if (iter != items_.end ())
|
||||
{
|
||||
auto const& item = iter->second;
|
||||
if (item.first == Action::erase)
|
||||
{
|
||||
// VFALCO We need to think about throwing
|
||||
// an exception or calling LogicError
|
||||
JLOG(j.fatal) <<
|
||||
"Trying to thread to deleted node";
|
||||
return nullptr;
|
||||
}
|
||||
return item.second;
|
||||
}
|
||||
{
|
||||
auto miter = mods.find (key);
|
||||
if (miter != mods.end ())
|
||||
{
|
||||
assert (miter->second);
|
||||
return miter->second;
|
||||
}
|
||||
}
|
||||
auto sle = peek(base,
|
||||
keylet::unchecked(key));
|
||||
if (! sle)
|
||||
{
|
||||
// VFALCO We need to think about throwing
|
||||
// an exception or calling LogicError
|
||||
JLOG(j.fatal) <<
|
||||
"ApplyStateTable::getForMod: key not found";
|
||||
return nullptr;
|
||||
}
|
||||
mods.emplace(key, sle);
|
||||
return sle;
|
||||
}
|
||||
|
||||
bool
|
||||
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);
|
||||
assert(sle);
|
||||
if (! sle)
|
||||
{
|
||||
// VFALCO We need to think about throwing
|
||||
// an exception or calling LogicError
|
||||
JLOG(j.fatal) <<
|
||||
"Threading to non-existent account: " <<
|
||||
toBase58(to);
|
||||
return false;
|
||||
}
|
||||
|
||||
return threadTx (meta, sle, mods);
|
||||
}
|
||||
|
||||
bool
|
||||
ApplyStateTable::threadOwners (ReadView const& base,
|
||||
TxMeta& meta, std::shared_ptr<
|
||||
SLE const> const& sle, Mods& mods,
|
||||
beast::Journal j)
|
||||
{
|
||||
// thread new or modified sle to owner or owners
|
||||
// VFALCO Why not isFieldPresent?
|
||||
if (sle->getType() != ltACCOUNT_ROOT &&
|
||||
sle->getFieldIndex(sfAccount) != -1)
|
||||
{
|
||||
// thread to owner's account
|
||||
return threadTx (base, meta, sle->getAccountID(
|
||||
sfAccount), mods, j);
|
||||
}
|
||||
else if (sle->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
// thread to owner's accounts
|
||||
return
|
||||
threadTx (base, meta, sle->getFieldAmount(
|
||||
sfLowLimit).getIssuer(), mods, j) &&
|
||||
threadTx (base, meta, sle->getFieldAmount(
|
||||
sfHighLimit).getIssuer(), mods, j);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // ripple
|
||||
Reference in New Issue
Block a user