From 0fd5570c1432e3394ae040c57eb01c7aa814229a Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Mon, 11 May 2020 19:37:55 -0700 Subject: [PATCH] ircd::m::vm: Add enumeration for evaluation phases. --- include/ircd/m/vm.h | 65 ++++++------ matrix/room.cc | 2 +- matrix/room_bootstrap.cc | 4 +- matrix/room_create.cc | 2 +- matrix/vm.cc | 30 ++++++ matrix/vm_eval.cc | 2 +- matrix/vm_execute.cc | 173 ++++++++++++++++++++++++++++---- modules/console.cc | 8 +- modules/federation/send_join.cc | 2 +- modules/m_vm_fetch.cc | 2 +- 10 files changed, 227 insertions(+), 63 deletions(-) diff --git a/include/ircd/m/vm.h b/include/ircd/m/vm.h index 910a4b131..1b690654c 100644 --- a/include/ircd/m/vm.h +++ b/include/ircd/m/vm.h @@ -21,6 +21,7 @@ namespace ircd::m::vm struct copts; struct eval; enum fault :uint; + enum phase :uint; using fault_t = std::underlying_type::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 &); @@ -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()> 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}; diff --git a/matrix/room.cc b/matrix/room.cc index b31fc8abd..15f18e892 100644 --- a/matrix/room.cc +++ b/matrix/room.cc @@ -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 { diff --git a/matrix/room_bootstrap.cc b/matrix/room_bootstrap.cc index 754f310f7..78ce7028b 100644 --- a/matrix/room_bootstrap.cc +++ b/matrix/room_bootstrap.cc @@ -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 { diff --git a/matrix/room_create.cc b/matrix/room_create.cc index 7e9f77e68..8cb6863df 100644 --- a/matrix/room_create.cc +++ b/matrix/room_create.cc @@ -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 diff --git a/matrix/vm.cc b/matrix/vm.cc index 9b4d5f8d2..e8e40a428 100644 --- a/matrix/vm.cc +++ b/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) { diff --git a/matrix/vm_eval.cc b/matrix/vm_eval.cc index 889aadbec..45e00f69a 100644 --- a/matrix/vm_eval.cc +++ b/matrix/vm_eval.cc @@ -291,7 +291,7 @@ ircd::m::vm::eval::operator()(const vector_view &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 diff --git a/matrix/vm_execute.cc b/matrix/vm_execute.cc index 6f0e775dc..8dccf76d5 100644 --- a/matrix/vm_execute.cc +++ b/matrix/vm_execute.cc @@ -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); diff --git a/modules/console.cc b/modules/console.cc index 05a9cf57c..fe73f64b3 100644 --- a/modules/console.cc +++ b/modules/console.cc @@ -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) << " " diff --git a/modules/federation/send_join.cc b/modules/federation/send_join.cc index 6bf49d8b8..7e7a10519 100644 --- a/modules/federation/send_join.cc +++ b/modules/federation/send_join.cc @@ -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 diff --git a/modules/m_vm_fetch.cc b/modules/m_vm_fetch.cc index f551b5d83..41319e358 100644 --- a/modules/m_vm_fetch.cc +++ b/modules/m_vm_fetch.cc @@ -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