mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
593 lines
17 KiB
C++
593 lines
17 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
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& 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;
|
|
}
|
|
// Track when a node gets modified only by metadata
|
|
if (item.first == Action::cache)
|
|
item.first = Action::modify;
|
|
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
|