mock txmeta and pass to weakly executed hooks, add meta_slot to access this data

This commit is contained in:
Richard Holland
2022-05-16 10:18:01 +00:00
parent 0bb38809be
commit b01bbcae5c
10 changed files with 421 additions and 213 deletions

View File

@@ -284,7 +284,7 @@ namespace hook_api
"hook_param_set",
"hook_skip",
"hook_after",
"hook_weak"
"meta_slot"
};
};
#endif

View File

@@ -152,8 +152,7 @@ namespace hook_api
DECLARE_HOOK_FUNCTION(int64_t, hook_param, uint32_t write_ptr, uint32_t write_len,
uint32_t read_ptr, uint32_t read_len);
DECLARE_HOOK_FUNCNARG(int64_t, hook_after);
DECLARE_HOOK_FUNCNARG(int64_t, hook_weak);
DECLARE_HOOK_FUNCNARG(int64_t, hook_again);
DECLARE_HOOK_FUNCTION(int64_t, hook_skip, uint32_t read_ptr, uint32_t read_len, uint32_t flags);
DECLARE_HOOK_FUNCNARG(int64_t, hook_pos);
@@ -195,6 +194,7 @@ namespace hook_api
DECLARE_HOOK_FUNCNARG(int64_t, otxn_type );
DECLARE_HOOK_FUNCTION(int64_t, otxn_slot, uint32_t slot_no );
DECLARE_HOOK_FUNCTION(int64_t, meta_slot, uint32_t slot_no );
} /* end namespace hook_api */
@@ -225,10 +225,12 @@ namespace hook
ripple::ApplyContext& applyCtx,
ripple::AccountID const& account, /* the account the hook is INSTALLED ON not always the otxn account */
bool hasCallback,
bool isCallback = false,
bool isStrongTSH = false,
uint32_t wasmParam = 0,
int32_t hookChainPosition = -1
bool isCallback,
bool isStrongTSH,
uint32_t wasmParam,
int32_t hookChainPosition,
// result of apply() if this is weak exec
std::shared_ptr<STObject const> const& provisionalMeta
);
struct HookContext;
@@ -280,8 +282,9 @@ namespace hook
uint32_t overrideCount = 0;
int32_t hookChainPosition = -1;
bool foreignStateSetDisabled = false;
bool executeAgainAsWeak = false; // hook_after allows strong pre-apply to nominate
bool executeAgainAsWeak = false; // hook_again allows strong pre-apply to nominate
// additional weak post-apply execution
std::shared_ptr<STObject const> provisionalMeta;
};
class HookExecutor;
@@ -554,8 +557,7 @@ namespace hook
ADD_HOOK_FUNCTION(otxn_slot, ctx);
ADD_HOOK_FUNCTION(hook_account, ctx);
ADD_HOOK_FUNCTION(hook_hash, ctx);
ADD_HOOK_FUNCTION(hook_after, ctx);
ADD_HOOK_FUNCTION(hook_weak, ctx);
ADD_HOOK_FUNCTION(hook_again, ctx);
ADD_HOOK_FUNCTION(fee_base, ctx);
ADD_HOOK_FUNCTION(ledger_seq, ctx);
ADD_HOOK_FUNCTION(ledger_last_hash, ctx);
@@ -588,6 +590,8 @@ namespace hook
ADD_HOOK_FUNCTION(trace_num, ctx);
ADD_HOOK_FUNCTION(trace_float, ctx);
ADD_HOOK_FUNCTION(meta_slot, ctx);
WasmEdge_TableInstanceContext* hostTable = WasmEdge_TableInstanceCreate(tableType);
WasmEdge_ImportObjectAddTable(importObj, tableName, hostTable);
WasmEdge_MemoryInstanceContext* hostMem = WasmEdge_MemoryInstanceCreate(memType);

View File

@@ -683,7 +683,8 @@ hook::apply(
bool isCallback,
bool isStrong,
uint32_t wasmParam,
int32_t hookChainPosition)
int32_t hookChainPosition,
std::shared_ptr<STObject const> const& provisionalMeta)
{
HookContext hookCtx =
@@ -712,7 +713,8 @@ hook::apply(
.isStrong = isStrong,
.wasmParam = wasmParam,
.hookChainPosition = hookChainPosition,
.foreignStateSetDisabled = false
.foreignStateSetDisabled = false,
.provisionalMeta = provisionalMeta
},
.emitFailure =
isCallback && wasmParam & 1
@@ -4493,15 +4495,7 @@ DEFINE_HOOK_FUNCNARG(
DEFINE_HOOK_FUNCNARG(
int64_t,
hook_weak)
{
HOOK_SETUP();
return (hookCtx.result.isStrong ? 0 : 1);
}
DEFINE_HOOK_FUNCNARG(
int64_t,
hook_after)
hook_again)
{
HOOK_SETUP();
@@ -4516,3 +4510,38 @@ DEFINE_HOOK_FUNCNARG(
return PREREQUISITE_NOT_MET;
}
DEFINE_HOOK_FUNCTION(
int64_t,
meta_slot,
uint32_t slot_into )
{
HOOK_SETUP();
if (!hookCtx.result.provisionalMeta)
return PREREQUISITE_NOT_MET;
if (slot_into > hook_api::max_slots)
return INVALID_ARGUMENT;
// check if we can emplace the object to a slot
if (slot_into == 0 && no_free_slots(hookCtx))
return NO_FREE_SLOTS;
if (slot_into == 0)
slot_into = get_free_slot(hookCtx);
hookCtx.slot.emplace( std::pair<int, hook::SlotEntry> { slot_into, hook::SlotEntry {
.id = {
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
},
.storage = hookCtx.result.provisionalMeta,
.entry = 0
}});
hookCtx.slot[slot_into].entry = &(*hookCtx.slot[slot_into].storage);
return slot_into;
}

View File

@@ -101,6 +101,13 @@ public:
view_->rawDestroyXRP(fee);
}
TxMeta
generateProvisionalMeta()
{
return view_->generateProvisionalMeta(base_, tx, journal);
}
/** Applies all invariant checkers one by one.
@param result the result generated by processing this transaction.

View File

@@ -901,7 +901,8 @@ executeHookChain(
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
ripple::AccountID const& account,
bool strong)
bool strong,
std::shared_ptr<STObject const> const& provisionalMeta)
{
std::set<uint256> hookSkips;
std::map<
@@ -978,8 +979,9 @@ executeHookChain(
hasCallback,
false,
strong,
0,
hook_no));
(strong ? 0 : 1UL), // 0 = strong, 1 = weak
hook_no,
provisionalMeta));
executedHookCount_++;
@@ -1015,7 +1017,7 @@ executeHookChain(
}
void
Transactor::doHookCallback()
Transactor::doHookCallback(std::shared_ptr<STObject const> const& provisionalMeta)
{
// Finally check if there is a callback
if (!ctx_.tx.isFieldPresent(sfEmitDetails))
@@ -1117,7 +1119,8 @@ Transactor::doHookCallback()
false,
safe_cast<TxType>(ctx_.tx.getFieldU16(sfTransactionType)) == ttEMIT_FAILURE
? 1UL : 0UL,
hook_no - 1);
hook_no - 1,
provisionalMeta);
bool success = callbackResult.exitType == hook_api::ExitType::ACCEPT;
@@ -1185,7 +1188,8 @@ Transactor::
doTSH(
bool strong, // only strong iff true, only weak iff false
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results)
std::vector<hook::HookResult>& results,
std::shared_ptr<STObject const> const& provisionalMeta)
{
auto& view = ctx_.view();
@@ -1298,7 +1302,8 @@ doTSH(
stateMap,
results,
tshAccountID,
strong);
strong,
provisionalMeta);
if (canRollback && (tshResult != tesSUCCESS))
return tshResult;
@@ -1307,6 +1312,111 @@ doTSH(
return tesSUCCESS;
}
void
Transactor::doAaw(
AccountID const& hookAccountID,
std::set<uint256> const& hookHashes,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
std::shared_ptr<STObject const> const& provisionalMeta)
{
auto const& hooksArray = view().peek(keylet::hook(hookAccountID));
if (!hooksArray)
{
JLOG(j_.warn())
<< "HookError[]: Hook missing on aaw";
return;
}
if (!hooksArray->isFieldPresent(sfHooks))
{
JLOG(j_.warn())
<< "HookError[]: Hooks Array missing on aaw";
return;
}
auto const& hooks = hooksArray->getFieldArray(sfHooks);
int hook_no = 0;
for (auto const& hook : hooks)
{
hook_no++;
STObject const* hookObj = dynamic_cast<STObject const*>(&hook);
if (!hookObj->isFieldPresent(sfHookHash)) // skip blanks
continue;
uint256 const& hookHash = hookObj->getFieldH256(sfHookHash);
if (hookHashes.find(hookObj->getFieldH256(sfHookHash)) == hookHashes.end())
continue;
auto const& hookDef = view().peek(keylet::hookDefinition(hookHash));
if (!hookDef)
{
JLOG(j_.warn())
<< "HookError[]: Hook def missing on aaw, hash: "
<< hookHash;
continue;
}
// fetch the namespace either from the hook object of, if absent, the hook def
uint256 const& ns =
(hookObj->isFieldPresent(sfHookNamespace)
? hookObj->getFieldH256(sfHookNamespace)
: hookDef->getFieldH256(sfHookNamespace));
executedHookCount_++;
std::map<std::vector<uint8_t>, std::vector<uint8_t>> parameters;
if (hook::gatherHookParameters(hookDef, hookObj, parameters, j_))
{
JLOG(j_.warn())
<< "HookError[]: Failure: gatherHookParameters failed)";
return;
}
try
{
hook::HookResult aawResult =
hook::apply(
hookDef->getFieldH256(sfHookSetTxnID),
hookHash,
ns,
hookDef->getFieldVL(sfCreateCode),
parameters,
{},
stateMap,
ctx_,
hookAccountID,
hookDef->isFieldPresent(sfHookCallbackFee),
false,
false,
2UL, // param 2 = aaw
hook_no - 1,
provisionalMeta);
results.push_back(aawResult);
JLOG(j_.trace())
<< "HookInfo[" << hookAccountID << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< " aaw Hook ExitCode = "
<< aawResult.exitCode;
}
catch (std::exception& e)
{
JLOG(j_.fatal())
<< "HookError[" << hookAccountID << "-" <<ctx_.tx.getAccountID(sfAccount) << "]: "
<< "]: aaw failure " << e.what();
}
}
}
//------------------------------------------------------------------------------
std::pair<TER, bool>
Transactor::operator()()
@@ -1337,12 +1447,9 @@ Transactor::operator()()
bool const hooksEnabled = view().rules().enabled(featureHooks);
// This vector stores any "strong" hooks that requested a weak "after application" re-execution.
std::vector<
std::pair<
AccountID, // account of hook
uint256 // hash of hook
>> executeAgainAsWeak;
// AgainAsWeak map stores information about accounts whose strongly executed hooks
// request an additional weak execution after the otxn has finished application to the ledger
std::map<AccountID, std::set<uint256>> aawMap;
// Pre-application (Strong TSH) Hooks are executed here
// These TSH have the right to rollback.
@@ -1355,8 +1462,7 @@ Transactor::operator()()
hook::HookStateMap stateMap;
auto const& accountID = ctx_.tx.getAccountID(sfAccount);
std::vector<hook::HookResult> orgResults;
std::vector<hook::HookResult> tshResults;
std::vector<hook::HookResult> hookResults;
auto const& hooksOriginator = view().read(keylet::hook(accountID));
@@ -1366,9 +1472,10 @@ Transactor::operator()()
executeHookChain(
hooksOriginator,
stateMap,
orgResults,
hookResults,
accountID,
true);
true,
{});
if (isTesSuccess(result))
{
@@ -1376,7 +1483,7 @@ Transactor::operator()()
// here. Note these are only strong TSH (who have the right to rollback the txn),
// any weak TSH will be executed after doApply has been successful (callback as well)
result = doTSH(true, stateMap, tshResults);
result = doTSH(true, stateMap, hookResults, {});
}
// write state if all chains executed successfully
@@ -1387,20 +1494,18 @@ Transactor::operator()()
// this happens irrespective of whether final result was a tesSUCCESS
// because it contains error codes that any failed hooks would have
// returned for meta
for (auto& orgResult: orgResults)
{
hook::finalizeHookResult(orgResult, ctx_, isTesSuccess(result));
if (isTesSuccess(result) && orgResult.executeAgainAsWeak)
executeAgainAsWeak.emplace_back(orgResult.account, orgResult.hookHash);
}
for (auto& tshResult: tshResults)
for (auto& hookResult: hookResults)
{
hook::finalizeHookResult(tshResult, ctx_, isTesSuccess(result));
if (isTesSuccess(result) && tshResult.executeAgainAsWeak)
executeAgainAsWeak.emplace_back(tshResult.account, tshResult.hookHash);
hook::finalizeHookResult(hookResult, ctx_, isTesSuccess(result));
if (isTesSuccess(result) && hookResult.executeAgainAsWeak)
{
if (aawMap.find(hookResult.account) == aawMap.end())
aawMap[hookResult.account] = {hookResult.hookHash};
else
aawMap[hookResult.account].emplace(hookResult.hookHash);
}
}
}
// fall through allows normal apply
@@ -1416,38 +1521,6 @@ Transactor::operator()()
bool applied = isTesSuccess(result);
// Post-application (Weak TSH) Hooks are executed here.
// These TSH do not have the ability to rollback.
// The callback, if any, is also executed here.
if (hooksEnabled && applied)
{
// perform callback logic if applicable
if (ctx_.tx.isFieldPresent(sfEmitDetails))
doHookCallback();
// remove emission entry if this is an emitted transaction
hook::removeEmissionEntry(ctx_);
// process weak TSH
hook::HookStateMap stateMap;
std::vector<hook::HookResult> tshResults;
doTSH(false, stateMap, tshResults);
// execute any hooks that nominated for 'again as weak'
for (auto const& [accID, hash] : executeAgainAsWeak)
{
// RH UPTO: execute individual hooks again here
}
// write hook results
hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID());
for (auto& tshResult: tshResults)
hook::finalizeHookResult(tshResult, ctx_, isTesSuccess(result));
}
auto fee = ctx_.tx.getFieldAmount(sfFee).xrp();
if (ctx_.size() > oversizeMetaDataCap)
@@ -1533,6 +1606,44 @@ Transactor::operator()()
if (!isTecClaim(result) && !isTesSuccess(result))
applied = false;
}
// Post-application (Weak TSH/AAW) Hooks are executed here.
// These TSH do not have the ability to rollback.
// The callback, if any, is also executed here.
if (applied && hooksEnabled)
{
// weakly executed hooks have access to a provisional TxMeta
// for this tx application.
std::shared_ptr<STObject const>
proMeta = std::make_shared<STObject const>(std::move(ctx_.generateProvisionalMeta().getAsObject()));
// perform callback logic if applicable
if (ctx_.tx.isFieldPresent(sfEmitDetails))
doHookCallback(proMeta);
// remove emission entry if this is an emitted transaction
hook::removeEmissionEntry(ctx_);
// process weak TSH
hook::HookStateMap stateMap;
std::vector<hook::HookResult> weakResults;
doTSH(false, stateMap, weakResults, proMeta);
// execute any hooks that nominated for 'again as weak'
for (auto const& [accID, hookHashes] : aawMap)
doAaw(accID, hookHashes, stateMap, weakResults, proMeta);
// write hook results
hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID());
for (auto& weakResult: weakResults)
hook::finalizeHookResult(weakResult, ctx_, isTesSuccess(result));
if (ctx_.size() > oversizeMetaDataCap)
result = tecOVERSIZE;
}
if (applied)
{

View File

@@ -191,13 +191,27 @@ public:
protected:
void
doHookCallback();
doHookCallback(
std::shared_ptr<STObject const> const& provisionalMeta);
TER
doTSH(
bool strong, // only do strong TSH iff true, otheriwse only weak
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results);
std::vector<hook::HookResult>& result,
std::shared_ptr<STObject const> const& provisionalMeta);
// Execute a hook "Again As Weak" is a feature that allows
// a hook that which is being executed pre-application of the otxn
// to request an additional post-application execution.
void
doAaw(
AccountID const& hookAccountID,
std::set<uint256> const& hookHashes,
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
std::shared_ptr<STObject const> const& provisionalMeta);
TER
executeHookChain(
@@ -205,8 +219,10 @@ protected:
hook::HookStateMap& stateMap,
std::vector<hook::HookResult>& results,
ripple::AccountID const& account,
bool strong);
bool strong,
std::shared_ptr<STObject const> const& provisionalMeta);
void
addWeakTSHFromSandbox(detail::ApplyViewBase const& pv);

View File

@@ -72,6 +72,8 @@ public:
deliver_ = amount;
}
TxMeta
generateProvisionalMeta(OpenView const& to, STTx const& tx, beast::Journal j);
/* Set hook metadata for a hook execution
* Takes ownership / use std::move

View File

@@ -27,8 +27,10 @@
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxMeta.h>
#include <utility>
#include <memory>
namespace ripple {
namespace detail {
@@ -37,6 +39,7 @@ class ApplyStateTable
{
public:
using key_type = ReadView::key_type;
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
private:
enum class Action {
@@ -64,6 +67,15 @@ public:
void
apply(RawView& to) const;
std::pair<TxMeta, Mods>
generateTxMeta(
OpenView const& to,
STTx const& tx,
std::optional<STAmount> const& deliver,
std::vector<STObject> const& hookExecution,
beast::Journal j);
void
apply(
OpenView& to,
@@ -126,7 +138,6 @@ public:
}
private:
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
static void
threadItem(TxMeta& meta, std::shared_ptr<SLE> const& to);

View File

@@ -109,6 +109,147 @@ ApplyStateTable::visit(
}
}
// return a txmeta and a set of created nodes
// does not modify ledger
std::pair<TxMeta, ApplyStateTable::Mods>
ApplyStateTable::generateTxMeta(
OpenView const& to,
STTx const& tx,
std::optional<STAmount> const& deliver,
std::vector<STObject> const& hookExecution,
beast::Journal j)
{
TxMeta meta(tx.getTransactionID(), to.seq());
if (deliver)
meta.setDeliveredAmount(*deliver);
if (!hookExecution.empty())
meta.setHookExecutions(STArray{hookExecution, sfHookExecutions});
Mods newMod;
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
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::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
threadItem(meta, curNode);
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);
}
}
return {meta, newMod};
}
void
ApplyStateTable::apply(
OpenView& to,
@@ -124,133 +265,10 @@ ApplyStateTable::apply(
std::shared_ptr<Serializer> sMeta;
if (!to.open())
{
TxMeta meta(tx.getTransactionID(), to.seq());
if (deliver)
meta.setDeliveredAmount(*deliver);
if (!hookExecution.empty())
meta.setHookExecutions(STArray{hookExecution, sfHookExecutions});
Mods newMod;
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
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::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
threadItem(meta, curNode);
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);
}
}
// generate meta
auto [meta, newMod] =
generateTxMeta(to, tx, deliver, hookExecution, j);
// add any new modified nodes to the modification set
for (auto& mod : newMod)

View File

@@ -34,6 +34,16 @@ ApplyViewImpl::apply(OpenView& to, STTx const& tx, TER ter, beast::Journal j)
items_.apply(to, tx, ter, deliver_, hookExecution_, j);
}
TxMeta
ApplyViewImpl::
generateProvisionalMeta(OpenView const& to, STTx const& tx, beast::Journal j)
{
auto [meta, _] =
items_.generateTxMeta(to, tx, deliver_, hookExecution_, j);
return meta;
}
std::size_t
ApplyViewImpl::size()
{