mirror of
https://github.com/matrix-construct/construct
synced 2025-03-14 05:20:17 +01:00
ircd:Ⓜ️:vm: Add enumeration for evaluation phases.
This commit is contained in:
parent
a1708a687e
commit
0fd5570c14
10 changed files with 227 additions and 63 deletions
|
@ -21,6 +21,7 @@ namespace ircd::m::vm
|
|||
struct copts;
|
||||
struct eval;
|
||||
enum fault :uint;
|
||||
enum phase :uint;
|
||||
using fault_t = std::underlying_type<fault>::type;
|
||||
|
||||
extern const opts default_opts;
|
||||
|
@ -30,6 +31,7 @@ namespace ircd::m::vm
|
|||
extern bool ready;
|
||||
|
||||
string_view reflect(const fault &);
|
||||
string_view reflect(const phase &);
|
||||
http::code http_code(const fault &);
|
||||
string_view loghead(const mutable_buffer &, const eval &);
|
||||
string_view loghead(const eval &); // single tls buffer
|
||||
|
@ -88,6 +90,7 @@ struct ircd::m::vm::eval
|
|||
event::conforms report;
|
||||
string_view room_version;
|
||||
const hook::base::site *phase_hook {nullptr};
|
||||
vm::phase phase {vm::phase(0)};
|
||||
bool room_internal {false};
|
||||
|
||||
static bool for_each_pdu(const std::function<bool (const event &)> &);
|
||||
|
@ -149,6 +152,33 @@ enum ircd::m::vm::fault
|
|||
EVENT = 0x20, ///< Eval requires addl events in the ef register (#ef)
|
||||
};
|
||||
|
||||
/// Evaluation phases
|
||||
enum ircd::m::vm::phase
|
||||
:uint
|
||||
{
|
||||
NONE = 0x00, ///< No phase; not entered.
|
||||
DUPCHK = 0x01, ///< Duplicate check & hold.
|
||||
EXECUTE = 0x02, ///< Execution entered.
|
||||
ISSUE = 0x03, ///< Issue phase.
|
||||
CONFORM = 0x04, ///< Conformity phase.
|
||||
ACCESS = 0x05, ///< Access control phase.
|
||||
VERIFY = 0x06, ///< Signature verification.
|
||||
FETCH = 0x07, ///< Fetch phase.
|
||||
AUTHSTATIC = 0x08, ///< Authentication phase.
|
||||
PRECOMMIT = 0x09, ///< Precommit sequence.
|
||||
AUTHRELA = 0x0a, ///< Authentication phase.
|
||||
COMMIT = 0x0b, ///< Commit sequence.
|
||||
AUTHPRES = 0x0c, ///< Authentication phase.
|
||||
EVALUATE = 0x0d, ///< Evaluation phase.
|
||||
INDEX = 0x0e, ///< Indexing & transaction building phase.
|
||||
POST = 0x0f, ///< Transaction-included effects phase.
|
||||
WRITE = 0x10, ///< Write transaction.
|
||||
RETIRE = 0x11, ///< Retire phase
|
||||
NOTIFY = 0x12, ///< Notifications phase.
|
||||
EFFECTS = 0x13, ///< Effects phase.
|
||||
_NUM_
|
||||
};
|
||||
|
||||
/// Evaluation Options
|
||||
struct ircd::m::vm::opts
|
||||
{
|
||||
|
@ -161,46 +191,24 @@ struct ircd::m::vm::opts
|
|||
/// The txnid from the node conducting the eval.
|
||||
string_view txn_id;
|
||||
|
||||
/// Call conform hooks (detailed behavior can be tweaked below)
|
||||
bool conform {true};
|
||||
|
||||
/// Check various access controls before processing event further.
|
||||
bool access {true};
|
||||
|
||||
/// Make fetches or false to bypass fetch stage.
|
||||
bool fetch {true};
|
||||
|
||||
/// Call eval hooks or false to bypass this stage.
|
||||
bool eval {true};
|
||||
|
||||
/// Perform auth or false to bypass this state.
|
||||
bool auth {true};
|
||||
|
||||
/// Make writes to database
|
||||
bool write {true};
|
||||
/// Enabled phases of evaluation.
|
||||
std::bitset<num_of<vm::phase>()> phase {-1UL};
|
||||
|
||||
/// Custom write_opts to use during write.
|
||||
dbs::write_opts wopts;
|
||||
|
||||
/// Call post hooks or false to bypass post-write / pre-notify effects.
|
||||
bool post {true};
|
||||
|
||||
/// Broadcast to clients/servers. When true, individual notify options
|
||||
/// that follow are considered. When false, no notifications occur.
|
||||
short notify {true};
|
||||
|
||||
/// Broadcast to local clients (/sync stream).
|
||||
bool notify_clients {true};
|
||||
|
||||
/// Broadcast to federation servers (/federation/send/).
|
||||
bool notify_servers {true};
|
||||
|
||||
/// Apply effects of this event or false to bypass this stage.
|
||||
bool effects {true};
|
||||
|
||||
/// False to allow a dirty conforms report (not recommended).
|
||||
bool conforming {true};
|
||||
|
||||
/// False to bypass all auth phases.
|
||||
bool auth {true};
|
||||
|
||||
/// Mask of conformity failures to allow without considering dirty.
|
||||
event::conforms non_conform;
|
||||
|
||||
|
@ -255,9 +263,6 @@ struct ircd::m::vm::opts
|
|||
/// if the evaluator already performed this and the json source is good.
|
||||
bool json_source {false};
|
||||
|
||||
/// Verify the origin signature; recommended.
|
||||
bool verify {true};
|
||||
|
||||
/// Whether to gather all unknown keys from an input vector of events and
|
||||
/// perform a parallel/mass fetch before proceeding with the evals.
|
||||
bool mfetch_keys {true};
|
||||
|
|
|
@ -331,7 +331,7 @@ ircd::m::commit(const room &room,
|
|||
opts.non_conform |= event::conforms::MISMATCH_ORIGIN_SENDER;
|
||||
|
||||
// Don't need this here
|
||||
opts.verify = false;
|
||||
opts.phase.reset(vm::phase::VERIFY);
|
||||
|
||||
return vm::eval
|
||||
{
|
||||
|
|
|
@ -544,7 +544,7 @@ try
|
|||
};
|
||||
|
||||
vmopts.nothrows = vm::fault::EXISTS;
|
||||
vmopts.fetch = false;
|
||||
vmopts.phase.reset(vm::phase::FETCH);
|
||||
m::vm::eval
|
||||
{
|
||||
auth_chain, vmopts
|
||||
|
@ -781,7 +781,7 @@ try
|
|||
vmopts.infolog_accept = true;
|
||||
vmopts.room_version = room_version;
|
||||
vmopts.user_id = user_id;
|
||||
vmopts.fetch = false;
|
||||
vmopts.phase.reset(vm::phase::FETCH);
|
||||
vmopts.auth = false;
|
||||
const vm::eval eval
|
||||
{
|
||||
|
|
|
@ -464,7 +464,7 @@ ircd::m::_create_event(const createroom &c)
|
|||
|
||||
m::vm::copts opts;
|
||||
opts.room_version = room_version;
|
||||
opts.verify = false;
|
||||
opts.phase.reset(vm::phase::VERIFY);
|
||||
m::vm::eval
|
||||
{
|
||||
event, content, opts
|
||||
|
|
30
matrix/vm.cc
30
matrix/vm.cc
|
@ -157,6 +157,36 @@ ircd::m::vm::http_code(const fault &code)
|
|||
}
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::vm::reflect(const enum phase &code)
|
||||
{
|
||||
switch(code)
|
||||
{
|
||||
case phase::NONE: return "NONE";
|
||||
case phase::DUPCHK: return "DUPCHK";
|
||||
case phase::EXECUTE: return "EXECUTE";
|
||||
case phase::ISSUE: return "ISSUE";
|
||||
case phase::CONFORM: return "CONFORM";
|
||||
case phase::ACCESS: return "ACCESS";
|
||||
case phase::VERIFY: return "VERIFY";
|
||||
case phase::FETCH: return "FETCH";
|
||||
case phase::AUTHSTATIC: return "AUTHSTATIC";
|
||||
case phase::PRECOMMIT: return "PRECOMMIT";
|
||||
case phase::AUTHRELA: return "AUTHRELA";
|
||||
case phase::COMMIT: return "COMMIT";
|
||||
case phase::AUTHPRES: return "AUTHPRES";
|
||||
case phase::EVALUATE: return "EVALUATE";
|
||||
case phase::INDEX: return "INDEX";
|
||||
case phase::POST: return "POST";
|
||||
case phase::WRITE: return "WRITE";
|
||||
case phase::RETIRE: return "RETIRE";
|
||||
case phase::NOTIFY: return "NOTIFY";
|
||||
case phase::EFFECTS: return "EFFECTS";
|
||||
}
|
||||
|
||||
return "??????";
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::vm::reflect(const enum fault &code)
|
||||
{
|
||||
|
|
|
@ -291,7 +291,7 @@ ircd::m::vm::eval::operator()(const vector_view<m::event> &events)
|
|||
this->pdus, events
|
||||
};
|
||||
|
||||
if(likely(opts->verify && opts->mfetch_keys))
|
||||
if(likely(opts->phase[phase::VERIFY] && opts->mfetch_keys))
|
||||
mfetch_keys();
|
||||
|
||||
// Conduct each eval without letting any one exception ruin things for the
|
||||
|
|
|
@ -128,6 +128,11 @@ try
|
|||
eval::executing
|
||||
};
|
||||
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::EXECUTE
|
||||
};
|
||||
|
||||
const scope_notify notify
|
||||
{
|
||||
vm::dock
|
||||
|
@ -213,11 +218,18 @@ try
|
|||
// evaluation is finished. If the other was successful, the exists()
|
||||
// check will skip this, otherwise we have to try again here because
|
||||
// this evaluator might be using different options/credentials.
|
||||
if(likely(opts.unique) && event.event_id)
|
||||
if(likely(opts.phase[phase::DUPCHK] && opts.unique) && event.event_id)
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::DUPCHK
|
||||
};
|
||||
|
||||
sequence::dock.wait([&event]
|
||||
{
|
||||
return eval::count(event.event_id) <= 1;
|
||||
});
|
||||
}
|
||||
|
||||
// We can elide a lot of grief here by not proceeding further and simply
|
||||
// returning fault::EXISTS after an existence check. If we had to wait for
|
||||
|
@ -320,8 +332,15 @@ try
|
|||
|
||||
// The issue hook is only called when this server is injecting a newly
|
||||
// created event.
|
||||
if(eval.copts && eval.copts->issue)
|
||||
if(opts.phase[phase::ISSUE] && eval.copts && eval.copts->issue)
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::ISSUE
|
||||
};
|
||||
|
||||
call_hook(issue_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
// Branch on whether the event is an EDU or a PDU
|
||||
const fault ret
|
||||
|
@ -344,15 +363,29 @@ try
|
|||
|
||||
// The event was executed; now we broadcast the good news. This will
|
||||
// include notifying client `/sync` and the federation sender.
|
||||
if(likely(opts.notify))
|
||||
if(likely(opts.phase[phase::NOTIFY]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::NOTIFY
|
||||
};
|
||||
|
||||
call_hook(notify_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
// The "effects" of the event are created by listeners on the effect hook.
|
||||
// These can include the creation of even more events, such as creating a
|
||||
// PDU out of an EDU, etc. Unlike the post_hook in execute_pdu(), the
|
||||
// notify for the event at issue here has already been made.
|
||||
if(likely(opts.effects))
|
||||
if(likely(opts.phase[phase::EFFECTS]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::EFFECTS
|
||||
};
|
||||
|
||||
call_hook(effect_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
if(opts.infolog_accept || bool(log_accept_info))
|
||||
log::info
|
||||
|
@ -431,11 +464,25 @@ ircd::m::vm::fault
|
|||
ircd::m::vm::execute_edu(eval &eval,
|
||||
const event &event)
|
||||
{
|
||||
if(likely(eval.opts->eval))
|
||||
call_hook(eval_hook, eval, event, eval);
|
||||
if(likely(eval.opts->phase[phase::EVALUATE]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::EVALUATE
|
||||
};
|
||||
|
||||
call_hook(eval_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
if(likely(eval.opts->phase[phase::POST]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::POST
|
||||
};
|
||||
|
||||
if(likely(eval.opts->post))
|
||||
call_hook(post_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
return fault::ACCEPT;
|
||||
}
|
||||
|
@ -487,8 +534,13 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
|
||||
// The conform hook runs static checks on an event's formatting and
|
||||
// composure; these checks only require the event data itself.
|
||||
if(likely(opts.conform))
|
||||
if(likely(opts.phase[phase::CONFORM]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::CONFORM
|
||||
};
|
||||
|
||||
const ctx::critical_assertion ca;
|
||||
call_hook(conform_hook, eval, event, eval);
|
||||
}
|
||||
|
@ -501,11 +553,18 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
|
||||
// Wait for any pending duplicate evals before proceeding.
|
||||
assert(eval::count(event_id));
|
||||
if(likely(opts.unique))
|
||||
if(likely(opts.phase[phase::DUPCHK] && opts.unique))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::DUPCHK
|
||||
};
|
||||
|
||||
sequence::dock.wait([&event_id]
|
||||
{
|
||||
return eval::count(event_id) <= 1;
|
||||
});
|
||||
}
|
||||
|
||||
if(likely(opts.unique) && unlikely(eval::count(event_id) > 1))
|
||||
throw error
|
||||
|
@ -519,22 +578,41 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
fault::EXISTS, "Event has already been evaluated."
|
||||
};
|
||||
|
||||
if(likely(opts.access))
|
||||
call_hook(access_hook, eval, event, eval);
|
||||
if(likely(opts.phase[phase::ACCESS]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::ACCESS
|
||||
};
|
||||
|
||||
if(likely(opts.verify) && !verify(event))
|
||||
call_hook(access_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
if(likely(opts.phase[phase::VERIFY]) && unlikely(!verify(event)))
|
||||
throw m::BAD_SIGNATURE
|
||||
{
|
||||
"Signature verification failed"
|
||||
"Signature verification failed."
|
||||
};
|
||||
|
||||
// Fetch dependencies
|
||||
if(likely(opts.fetch))
|
||||
if(likely(opts.phase[phase::FETCH]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::FETCH
|
||||
};
|
||||
|
||||
call_hook(fetch_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
// Evaluation by auth system; throws
|
||||
if(likely(authenticate))
|
||||
if(likely(opts.phase[phase::AUTHSTATIC]) && authenticate)
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::AUTHSTATIC
|
||||
};
|
||||
|
||||
const auto &[pass, fail]
|
||||
{
|
||||
room::auth::check_static(event)
|
||||
|
@ -564,13 +642,18 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
log, "%s | event sequenced", loghead(eval)
|
||||
};
|
||||
|
||||
const scope_restore eval_phase_precommit
|
||||
{
|
||||
eval.phase, phase::PRECOMMIT
|
||||
};
|
||||
|
||||
// Wait until this is the lowest sequence number
|
||||
sequence::dock.wait([&eval]
|
||||
{
|
||||
return eval::seqnext(sequence::uncommitted) == &eval;
|
||||
});
|
||||
|
||||
if(likely(authenticate))
|
||||
if(likely(opts.phase[phase::AUTHRELA]) && authenticate)
|
||||
{
|
||||
const auto &[pass, fail]
|
||||
{
|
||||
|
@ -597,6 +680,11 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
assert(eval::sequnique(sequence::get(eval)));
|
||||
sequence::uncommitted = sequence::get(eval);
|
||||
|
||||
const scope_restore eval_phase_commit
|
||||
{
|
||||
eval.phase, phase::COMMIT
|
||||
};
|
||||
|
||||
// Wait until this is the lowest sequence number
|
||||
sequence::dock.wait([&eval]
|
||||
{
|
||||
|
@ -604,12 +692,26 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
});
|
||||
|
||||
// Reevaluation of auth against the present state of the room.
|
||||
if(likely(authenticate))
|
||||
if(likely(opts.phase[phase::AUTHPRES]) && authenticate)
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::AUTHPRES
|
||||
};
|
||||
|
||||
room::auth::check_present(event);
|
||||
}
|
||||
|
||||
// Evaluation by module hooks
|
||||
if(likely(opts.eval))
|
||||
if(likely(opts.phase[phase::EVALUATE]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::EVALUATE
|
||||
};
|
||||
|
||||
call_hook(eval_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
log::debug
|
||||
{
|
||||
|
@ -620,22 +722,49 @@ ircd::m::vm::execute_pdu(eval &eval,
|
|||
assert(sequence::retired < sequence::get(eval));
|
||||
sequence::committed = sequence::get(eval);
|
||||
|
||||
write_prepare(eval, event);
|
||||
write_append(eval, event);
|
||||
if(likely(opts.phase[phase::INDEX]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::INDEX
|
||||
};
|
||||
|
||||
write_prepare(eval, event);
|
||||
write_append(eval, event);
|
||||
}
|
||||
|
||||
// Generate post-eval/pre-notify effects. This function may conduct
|
||||
// an entire eval of several more events recursively before returning.
|
||||
if(likely(opts.post))
|
||||
if(likely(opts.phase[phase::POST]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::POST
|
||||
};
|
||||
|
||||
call_hook(post_hook, eval, event, eval);
|
||||
}
|
||||
|
||||
// Commit the transaction to database iff this eval is at the stack base.
|
||||
if(likely(opts.write) && !eval.sequence_shared[0])
|
||||
if(likely(opts.phase[phase::WRITE]) && !eval.sequence_shared[0])
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::WRITE
|
||||
};
|
||||
|
||||
write_commit(eval);
|
||||
}
|
||||
|
||||
// Wait for sequencing only if this is the stack base, otherwise we'll
|
||||
// never return back to that stack base.
|
||||
if(likely(!eval.sequence_shared[0]))
|
||||
{
|
||||
const scope_restore eval_phase
|
||||
{
|
||||
eval.phase, phase::RETIRE
|
||||
};
|
||||
|
||||
sequence::dock.wait([&eval]
|
||||
{
|
||||
return eval::seqnext(sequence::retired) == std::addressof(eval);
|
||||
|
|
|
@ -7968,11 +7968,11 @@ console_cmd__eval(opt &out, const string_view &line)
|
|||
break;
|
||||
|
||||
case "nowrite"_:
|
||||
opts.write = false;
|
||||
opts.phase.reset(m::vm::phase::WRITE);
|
||||
break;
|
||||
|
||||
case "noverify"_:
|
||||
opts.verify = false;
|
||||
opts.phase.reset(m::vm::phase::VERIFY);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -14909,7 +14909,7 @@ console_cmd__vm(opt &out, const string_view &line)
|
|||
<< std::right << std::setw(5) << "DONE" << " "
|
||||
<< std::right << std::setw(9) << "SEQUENCE" << " "
|
||||
<< std::right << std::setw(9) << "SEQSHARE" << " "
|
||||
<< std::left << std::setw(9) << "PHASE" << " "
|
||||
<< std::left << std::setw(10) << "PHASE" << " "
|
||||
<< std::right << std::setw(6) << "SIZE" << " "
|
||||
<< std::right << std::setw(5) << "CELLS" << " "
|
||||
<< std::right << std::setw(8) << "DEPTH" << " "
|
||||
|
@ -14942,7 +14942,7 @@ console_cmd__vm(opt &out, const string_view &line)
|
|||
<< std::right << std::setw(5) << done << " "
|
||||
<< std::right << std::setw(9) << eval->sequence << " "
|
||||
<< std::right << std::setw(9) << std::max(eval->sequence_shared[0], eval->sequence_shared[1]) << " "
|
||||
<< std::left << std::setw(9) << (eval->phase_hook? eval->phase_hook->name() : ""_sv) << " "
|
||||
<< std::left << std::setw(10) << trunc(reflect(eval->phase), 10) << " "
|
||||
<< std::right << std::setw(6) << (eval->txn? eval->txn->bytes() : 0UL) << " "
|
||||
<< std::right << std::setw(5) << (eval->txn? eval->txn->size() : 0UL) << " "
|
||||
<< std::right << std::setw(8) << (eval->event_ && eval->event_id? long(json::get<"depth"_>(*eval->event_)) : -1L) << " "
|
||||
|
|
|
@ -153,7 +153,7 @@ put__send_join(client &client,
|
|||
};
|
||||
|
||||
m::vm::opts vmopts;
|
||||
vmopts.fetch = false;
|
||||
vmopts.phase.reset(m::vm::phase::FETCH);
|
||||
m::vm::eval eval
|
||||
{
|
||||
event, vmopts
|
||||
|
|
|
@ -120,7 +120,7 @@ ircd::m::vm::fetch::handle(const event &event,
|
|||
try
|
||||
{
|
||||
assert(eval.opts);
|
||||
assert(eval.opts->fetch);
|
||||
assert(eval.opts->phase[phase::FETCH]);
|
||||
const auto &opts
|
||||
{
|
||||
*eval.opts
|
||||
|
|
Loading…
Add table
Reference in a new issue