2019-08-27 00:12:43 +02:00
|
|
|
// Matrix Construct
|
|
|
|
//
|
|
|
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
|
|
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
|
|
|
//
|
|
|
|
// 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 is present in all copies. The
|
|
|
|
// full license for this software is available in the LICENSE file.
|
|
|
|
|
|
|
|
namespace ircd::m::vm::fetch
|
|
|
|
{
|
|
|
|
struct evaltab;
|
|
|
|
|
|
|
|
static void hook_handle_prev(const event &, vm::eval &, evaltab &, const room &);
|
2019-08-30 04:38:53 +02:00
|
|
|
static void auth_chain(const room &, const string_view &remote);
|
2019-08-27 00:12:43 +02:00
|
|
|
static void hook_handle_auth(const event &, vm::eval &, evaltab &, const room &);
|
|
|
|
static void hook_handle(const event &, vm::eval &);
|
|
|
|
|
2019-08-28 04:34:46 +02:00
|
|
|
extern conf::item<seconds> auth_timeout;
|
2019-08-27 00:12:43 +02:00
|
|
|
extern conf::item<bool> enable;
|
|
|
|
extern hookfn<vm::eval &> hook;
|
|
|
|
extern log::log log;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ircd::m::vm::fetch::evaltab
|
|
|
|
{
|
|
|
|
size_t auth_count {0};
|
|
|
|
size_t auth_exists {0};
|
|
|
|
size_t prev_count {0};
|
|
|
|
size_t prev_exists {0};
|
|
|
|
size_t prev_fetching {0};
|
|
|
|
size_t prev_fetched {0};
|
|
|
|
};
|
|
|
|
|
|
|
|
ircd::mapi::header
|
|
|
|
IRCD_MODULE
|
|
|
|
{
|
|
|
|
"Matrix VM Fetch Unit"
|
|
|
|
};
|
|
|
|
|
|
|
|
decltype(ircd::m::vm::fetch::log)
|
|
|
|
ircd::m::vm::fetch::log
|
|
|
|
{
|
|
|
|
"m.vm.fetch"
|
|
|
|
};
|
|
|
|
|
|
|
|
decltype(ircd::m::vm::fetch::enable)
|
|
|
|
ircd::m::vm::fetch::enable
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.vm.fetch.enable" },
|
|
|
|
{ "default", true },
|
|
|
|
};
|
|
|
|
|
2019-08-28 04:34:46 +02:00
|
|
|
decltype(ircd::m::vm::fetch::auth_timeout)
|
|
|
|
ircd::m::vm::fetch::auth_timeout
|
|
|
|
{
|
|
|
|
{ "name", "ircd.m.vm.fetch.auth.timeout" },
|
|
|
|
{ "default", 15L },
|
|
|
|
};
|
|
|
|
|
2019-08-27 00:12:43 +02:00
|
|
|
decltype(ircd::m::vm::fetch::hook)
|
|
|
|
ircd::m::vm::fetch::hook
|
|
|
|
{
|
|
|
|
hook_handle,
|
|
|
|
{
|
|
|
|
{ "_site", "vm.fetch" }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// fetch_phase
|
|
|
|
//
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::m::vm::fetch::hook_handle(const event &event,
|
|
|
|
vm::eval &eval)
|
|
|
|
try
|
|
|
|
{
|
|
|
|
assert(eval.opts);
|
|
|
|
assert(eval.opts->fetch);
|
|
|
|
const auto &opts{*eval.opts};
|
|
|
|
|
|
|
|
const auto &type
|
|
|
|
{
|
|
|
|
at<"type"_>(event)
|
|
|
|
};
|
|
|
|
|
|
|
|
if(type == "m.room.create")
|
|
|
|
return;
|
|
|
|
|
|
|
|
const m::event::id &event_id
|
|
|
|
{
|
|
|
|
event.event_id
|
|
|
|
};
|
|
|
|
|
|
|
|
const m::room::id &room_id
|
|
|
|
{
|
|
|
|
at<"room_id"_>(event)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Can't construct m::room with the event_id argument because it
|
|
|
|
// won't be found (we're evaluating that event here!) so we just set
|
|
|
|
// the member manually to make further use of the room struct.
|
|
|
|
m::room room{room_id};
|
|
|
|
room.event_id = event_id;
|
|
|
|
|
|
|
|
evaltab tab;
|
|
|
|
if(opts.fetch_auth_check)
|
|
|
|
hook_handle_auth(event, eval, tab, room);
|
|
|
|
|
|
|
|
if(opts.fetch_prev_check)
|
|
|
|
hook_handle_prev(event, eval, tab, room);
|
|
|
|
|
|
|
|
log::debug
|
|
|
|
{
|
|
|
|
log, "%s %s ac:%zu ae:%zu pc:%zu pe:%zu pf:%zu",
|
|
|
|
loghead(eval),
|
|
|
|
json::get<"room_id"_>(event),
|
|
|
|
tab.auth_count,
|
|
|
|
tab.auth_exists,
|
|
|
|
tab.prev_count,
|
|
|
|
tab.prev_exists,
|
|
|
|
tab.prev_fetched,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
log::derror
|
|
|
|
{
|
|
|
|
log, "%s :%s",
|
|
|
|
loghead(eval),
|
|
|
|
e.what(),
|
|
|
|
};
|
|
|
|
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ircd::m::vm::fetch::hook_handle_auth(const event &event,
|
|
|
|
vm::eval &eval,
|
|
|
|
evaltab &tab,
|
|
|
|
const room &room)
|
|
|
|
|
|
|
|
{
|
|
|
|
// Count how many of the auth_events provided exist locally.
|
|
|
|
const auto &opts{*eval.opts};
|
|
|
|
const event::prev prev{event};
|
|
|
|
tab.auth_count = prev.auth_events_count();
|
|
|
|
for(size_t i(0); i < tab.auth_count; ++i)
|
|
|
|
{
|
|
|
|
const auto &auth_id
|
|
|
|
{
|
|
|
|
prev.auth_event(i)
|
|
|
|
};
|
|
|
|
|
|
|
|
tab.auth_exists += bool(m::exists(auth_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
// We are satisfied at this point if all auth_events for this event exist,
|
|
|
|
// as those events have themselves been successfully evaluated and so forth.
|
|
|
|
assert(tab.auth_exists <= tab.auth_count);
|
|
|
|
if(tab.auth_exists == tab.auth_count)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// At this point we are missing one or more auth_events for this event.
|
|
|
|
log::dwarning
|
|
|
|
{
|
|
|
|
log, "%s auth_events:%zu hit:%zu miss:%zu",
|
|
|
|
loghead(eval),
|
|
|
|
tab.auth_count,
|
|
|
|
tab.auth_exists,
|
|
|
|
tab.auth_count - tab.auth_exists,
|
|
|
|
};
|
|
|
|
|
|
|
|
// We need to figure out where best to sling a request to fetch these
|
|
|
|
// missing auth_events. We prefer the remote client conducting this eval
|
|
|
|
// with their /federation/send/ request which we stored in the opts.
|
|
|
|
const string_view &remote
|
|
|
|
{
|
|
|
|
opts.node_id?
|
|
|
|
opts.node_id:
|
|
|
|
!my_host(json::get<"origin"_>(event))?
|
|
|
|
string_view(json::get<"origin"_>(event)):
|
|
|
|
!my_host(room.room_id.host())? //TODO: XXX
|
|
|
|
room.room_id.host():
|
|
|
|
string_view{}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Bail out here if we can't or won't attempt fetching auth_events.
|
|
|
|
if(!opts.fetch_auth || !bool(m::vm::fetch::enable) || !remote)
|
|
|
|
throw vm::error
|
|
|
|
{
|
|
|
|
vm::fault::EVENT, "Failed to fetch auth_events for %s in %s",
|
|
|
|
string_view{event.event_id},
|
|
|
|
json::get<"room_id"_>(event)
|
|
|
|
};
|
|
|
|
|
|
|
|
// This is a blocking call to recursively fetch and evaluate the auth_chain
|
|
|
|
// for this event. Upon return all of the auth_events for this event will
|
|
|
|
// have themselves been fetched and auth'ed recursively or throws.
|
2019-08-28 04:34:46 +02:00
|
|
|
auth_chain(room, remote);
|
2019-08-27 00:12:43 +02:00
|
|
|
tab.auth_exists = tab.auth_count;
|
|
|
|
}
|
|
|
|
|
2019-08-28 04:34:46 +02:00
|
|
|
void
|
|
|
|
ircd::m::vm::fetch::auth_chain(const room &room,
|
2019-08-30 04:38:53 +02:00
|
|
|
const string_view &remote)
|
2019-08-28 04:34:46 +02:00
|
|
|
try
|
|
|
|
{
|
|
|
|
log::debug
|
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
log, "Fetching auth chain for %s in %s (hint: %s)",
|
2019-08-28 04:34:46 +02:00
|
|
|
string_view{room.event_id},
|
|
|
|
string_view{room.room_id},
|
2019-08-30 04:38:53 +02:00
|
|
|
remote,
|
|
|
|
};
|
|
|
|
|
|
|
|
m::fetch::opts opts;
|
|
|
|
opts.op = m::fetch::op::auth;
|
|
|
|
opts.room_id = room.room_id;
|
|
|
|
opts.event_id = room.event_id;
|
|
|
|
opts.hint = remote;
|
|
|
|
auto future
|
|
|
|
{
|
|
|
|
m::fetch::start(opts)
|
2019-08-28 04:34:46 +02:00
|
|
|
};
|
|
|
|
|
2019-08-30 04:38:53 +02:00
|
|
|
const auto result
|
2019-08-28 04:34:46 +02:00
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
future.get(seconds(auth_timeout))
|
2019-08-28 04:34:46 +02:00
|
|
|
};
|
|
|
|
|
2019-08-30 04:38:53 +02:00
|
|
|
const json::object response
|
2019-08-28 04:34:46 +02:00
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
result
|
2019-08-28 04:34:46 +02:00
|
|
|
};
|
|
|
|
|
2019-08-30 04:38:53 +02:00
|
|
|
const json::array &auth_chain
|
2019-08-28 04:34:46 +02:00
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
response["auth_chain"]
|
2019-08-28 04:34:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
log::debug
|
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
log, "Evaluating %zu auth events in chain for %s in %s",
|
|
|
|
auth_chain.size(),
|
2019-08-28 04:34:46 +02:00
|
|
|
string_view{room.event_id},
|
|
|
|
string_view{room.room_id},
|
|
|
|
};
|
|
|
|
|
|
|
|
m::vm::opts vmopts;
|
|
|
|
vmopts.infolog_accept = true;
|
|
|
|
vmopts.fetch_prev_check = false;
|
|
|
|
vmopts.fetch_state_check = false;
|
|
|
|
vmopts.warnlog &= ~vm::fault::EXISTS;
|
|
|
|
m::vm::eval
|
|
|
|
{
|
2019-08-30 04:38:53 +02:00
|
|
|
auth_chain, vmopts
|
2019-08-28 04:34:46 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
thread_local char rembuf[64];
|
|
|
|
log::error
|
|
|
|
{
|
|
|
|
log, "Fetching auth chain for %s in %s from %s :%s",
|
|
|
|
string_view{room.event_id},
|
|
|
|
string_view{room.room_id},
|
|
|
|
string(rembuf, remote),
|
|
|
|
e.what(),
|
|
|
|
};
|
|
|
|
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
|
2019-08-27 00:12:43 +02:00
|
|
|
void
|
|
|
|
ircd::m::vm::fetch::hook_handle_prev(const event &event,
|
|
|
|
vm::eval &eval,
|
|
|
|
evaltab &tab,
|
|
|
|
const room &room)
|
|
|
|
{
|
|
|
|
const auto &opts{*eval.opts};
|
|
|
|
const event::prev prev{event};
|
|
|
|
tab.prev_count = prev.prev_events_count();
|
2019-08-31 10:38:20 +02:00
|
|
|
|
|
|
|
std::list<ctx::future<m::fetch::result>> futures;
|
2019-08-27 00:12:43 +02:00
|
|
|
for(size_t i(0); i < tab.prev_count; ++i)
|
|
|
|
{
|
|
|
|
const auto &prev_id
|
|
|
|
{
|
|
|
|
prev.prev_event(i)
|
|
|
|
};
|
|
|
|
|
|
|
|
if(m::exists(prev_id))
|
|
|
|
{
|
|
|
|
++tab.prev_exists;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-08-31 10:38:20 +02:00
|
|
|
if(!opts.fetch_prev || !m::vm::fetch::enable)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const int64_t room_depth
|
2019-08-27 00:12:43 +02:00
|
|
|
{
|
2019-08-31 10:38:20 +02:00
|
|
|
m::depth(std::nothrow, room)
|
2019-08-27 00:12:43 +02:00
|
|
|
};
|
|
|
|
|
2019-08-31 10:38:20 +02:00
|
|
|
//TODO: XXX
|
|
|
|
const bool recent_event
|
|
|
|
{
|
|
|
|
at<"depth"_>(event) >= room_depth - 20L //TODO: XXX
|
|
|
|
};
|
|
|
|
|
|
|
|
if(!recent_event)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const ssize_t limit
|
2019-08-27 00:12:43 +02:00
|
|
|
{
|
2019-08-31 10:38:20 +02:00
|
|
|
at<"depth"_>(event) - room_depth
|
2019-08-27 00:12:43 +02:00
|
|
|
};
|
|
|
|
|
2019-08-31 10:38:20 +02:00
|
|
|
m::fetch::opts opts;
|
|
|
|
opts.op = m::fetch::op::backfill;
|
|
|
|
opts.limit = std::min(limit, 32L);
|
|
|
|
opts.room_id = room.room_id;
|
|
|
|
opts.event_id = prev_id;
|
|
|
|
futures.emplace_back(m::fetch::start(opts));
|
2019-08-27 00:12:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we have all of the referenced prev_events we are satisfied here.
|
2019-08-31 10:38:20 +02:00
|
|
|
tab.prev_fetching = futures.size();
|
2019-08-27 00:12:43 +02:00
|
|
|
assert(tab.prev_exists <= tab.prev_count);
|
|
|
|
if(tab.prev_exists == tab.prev_count)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// At this point one or more prev_events are missing; the fetches were
|
|
|
|
// launched asynchronously if the options allowed for it.
|
|
|
|
log::dwarning
|
|
|
|
{
|
|
|
|
log, "%s prev_events:%zu hit:%zu miss:%zu fetching:%zu",
|
|
|
|
loghead(eval),
|
|
|
|
tab.prev_count,
|
|
|
|
tab.prev_exists,
|
|
|
|
tab.prev_count - tab.prev_exists,
|
|
|
|
tab.prev_fetching,
|
|
|
|
};
|
|
|
|
|
|
|
|
// If the options want to wait for the fetch+evals of the prev_events to occur
|
|
|
|
// before we continue processing this event further, we block in here.
|
2019-08-31 10:38:20 +02:00
|
|
|
//const bool &prev_wait{opts.fetch_prev_wait};
|
|
|
|
//if(prev_wait && tab.prev_fetching) for(size_t i(0); i < tab.prev_count; ++i)
|
|
|
|
if(!tab.prev_fetching)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto fetching
|
|
|
|
{
|
|
|
|
ctx::when_all(begin(futures), end(futures))
|
|
|
|
};
|
|
|
|
|
|
|
|
fetching.wait();
|
|
|
|
for(auto &future : futures) try
|
|
|
|
{
|
|
|
|
m::fetch::result result
|
|
|
|
{
|
|
|
|
future.get()
|
|
|
|
};
|
|
|
|
|
|
|
|
const json::array &pdus
|
|
|
|
{
|
|
|
|
json::object(result).get("pdus")
|
|
|
|
};
|
|
|
|
|
|
|
|
log::debug
|
|
|
|
{
|
|
|
|
log, "%s fetched %zu pdus; evaluating...",
|
|
|
|
loghead(eval),
|
|
|
|
pdus.size(),
|
|
|
|
};
|
|
|
|
|
|
|
|
m::vm::eval
|
|
|
|
{
|
|
|
|
pdus, opts
|
|
|
|
};
|
|
|
|
}
|
|
|
|
catch(const std::exception &e)
|
|
|
|
{
|
|
|
|
log::derror
|
|
|
|
{
|
|
|
|
log, "%s :%s",
|
|
|
|
loghead(eval),
|
|
|
|
e.what(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
for(size_t i(0); i < tab.prev_count; ++i)
|
2019-08-27 00:12:43 +02:00
|
|
|
{
|
|
|
|
const auto &prev_id
|
|
|
|
{
|
|
|
|
prev.prev_event(i)
|
|
|
|
};
|
|
|
|
|
|
|
|
tab.prev_fetched += m::exists(prev_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Aborts this event if the options want us to guarantee at least one
|
|
|
|
// prev_event was fetched and evaluated for this event. This is generally
|
|
|
|
// used in conjunction with the fetch_prev_wait option to be effective.
|
|
|
|
const bool &prev_any{opts.fetch_prev_any};
|
|
|
|
if(prev_any && tab.prev_exists + tab.prev_fetched == 0)
|
|
|
|
throw vm::error
|
|
|
|
{
|
|
|
|
vm::fault::EVENT, "Failed to fetch any prev_events for %s in %s",
|
|
|
|
string_view{event.event_id},
|
|
|
|
json::get<"room_id"_>(event)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Aborts this event if the options want us to guarantee ALL of the
|
|
|
|
// prev_events were fetched and evaluated for this event.
|
|
|
|
const bool &prev_all{opts.fetch_prev_all};
|
|
|
|
if(prev_all && tab.prev_exists + tab.prev_fetched < tab.prev_count)
|
|
|
|
throw vm::error
|
|
|
|
{
|
|
|
|
vm::fault::EVENT, "Failed to fetch all %zu required prev_events for %s in %s",
|
|
|
|
tab.prev_count,
|
|
|
|
string_view{event.event_id},
|
|
|
|
json::get<"room_id"_>(event)
|
|
|
|
};
|
|
|
|
}
|