mirror of
https://codeberg.org/wownero/wownero-lws
synced 2026-01-09 23:25:16 -08:00
ZMQ Pub Spends (#101)
This commit is contained in:
committed by
Lee *!* Clagett
parent
fe9d861dfb
commit
38c4999555
@@ -359,7 +359,7 @@ namespace db
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const char* map_webhook_type[] = {"tx-confirmation", "new-account"};
|
||||
constexpr const char* map_webhook_type[] = {"tx-confirmation", "new-account", "tx-spend"};
|
||||
|
||||
template<typename F, typename T>
|
||||
void map_webhook_key(F& format, T& self)
|
||||
@@ -423,6 +423,32 @@ namespace db
|
||||
);
|
||||
}
|
||||
|
||||
static void write_bytes(wire::writer& dest, const output::spend_meta_& self)
|
||||
{
|
||||
wire::object(dest,
|
||||
WIRE_FIELD_ID(0, id),
|
||||
wire::field<1>("amount", self.amount),
|
||||
wire::field<2>("mixin", self.mixin_count),
|
||||
wire::field<3>("index", self.index),
|
||||
WIRE_FIELD_ID(4, tx_public)
|
||||
);
|
||||
}
|
||||
|
||||
static void write_bytes(wire::writer& dest, const webhook_tx_spend::tx_info_& self)
|
||||
{
|
||||
wire::object(dest, WIRE_FIELD_ID(0, input), WIRE_FIELD_ID(1, source));
|
||||
}
|
||||
|
||||
void write_bytes(wire::writer& dest, const webhook_tx_spend& self)
|
||||
{
|
||||
wire::object(dest,
|
||||
wire::field<0>("event", std::cref(self.key.type)),
|
||||
wire::field<1>("token", std::cref(self.value.second.token)),
|
||||
wire::field<2>("event_id", std::cref(self.value.first.event_id)),
|
||||
WIRE_FIELD_ID(3, tx_info)
|
||||
);
|
||||
}
|
||||
|
||||
void write_bytes(wire::json_writer& dest, const webhook_event& self)
|
||||
{
|
||||
crypto::hash8 payment_id;
|
||||
|
||||
@@ -335,7 +335,8 @@ namespace db
|
||||
enum class webhook_type : std::uint8_t
|
||||
{
|
||||
tx_confirmation = 0, // cannot change values - stored in DB
|
||||
new_account
|
||||
new_account,
|
||||
tx_spend
|
||||
// unconfirmed_tx,
|
||||
// new_block
|
||||
// confirmed_tx,
|
||||
@@ -384,6 +385,19 @@ namespace db
|
||||
};
|
||||
void write_bytes(wire::writer&, const webhook_tx_confirmation&);
|
||||
|
||||
//! Returned by DB when a webhook event "tripped"
|
||||
struct webhook_tx_spend
|
||||
{
|
||||
webhook_key key;
|
||||
webhook_value value;
|
||||
struct tx_info_
|
||||
{
|
||||
spend input;
|
||||
output::spend_meta_ source;
|
||||
} tx_info;
|
||||
};
|
||||
void write_bytes(wire::writer&, const webhook_tx_spend&);
|
||||
|
||||
//! References a specific output that triggered a webhook
|
||||
struct webhook_output
|
||||
{
|
||||
|
||||
@@ -2573,18 +2573,69 @@ namespace db
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
expect<void> check_spends(std::vector<webhook_tx_spend>& out, MDB_cursor& webhooks_cur, MDB_cursor& outputs_cur, const lws::account& user)
|
||||
{
|
||||
const account_id user_id = user.id();
|
||||
const webhook_key hook_key{user_id, webhook_type::tx_spend};
|
||||
MDB_val key = lmdb::to_val(hook_key);
|
||||
MDB_val value{};
|
||||
|
||||
// Find a tx_spend for user id
|
||||
int err = mdb_cursor_get(&webhooks_cur, &key, &value, MDB_SET_KEY);
|
||||
for (;;)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
if (err != MDB_NOTFOUND)
|
||||
return {lmdb::error(err)};
|
||||
break;
|
||||
}
|
||||
|
||||
const auto hook = webhooks.get_value(value);
|
||||
if (hook)
|
||||
{
|
||||
out.reserve(user.spends().size());
|
||||
for (const spend& s : user.spends())
|
||||
{
|
||||
key = lmdb::to_val(user_id);
|
||||
value = lmdb::to_val(s.link.height);
|
||||
err = mdb_cursor_get(&outputs_cur, &key, &value, MDB_GET_BOTH_RANGE);
|
||||
|
||||
expect<output::spend_meta_> meta{common_error::kInvalidArgument};
|
||||
for (;;)
|
||||
{
|
||||
if (err)
|
||||
return {lmdb::error(err)};
|
||||
meta = outputs.get_value<MONERO_FIELD(output, spend_meta)>(value);
|
||||
if (!meta)
|
||||
return meta.error();
|
||||
if (meta->id == s.source)
|
||||
break;
|
||||
err = mdb_cursor_get(&outputs_cur, &key, &value, MDB_PREV_DUP);
|
||||
}
|
||||
|
||||
out.push_back(
|
||||
webhook_tx_spend{hook_key, *hook, {s, *meta}}
|
||||
);
|
||||
}
|
||||
}
|
||||
err = mdb_cursor_get(&webhooks_cur, &key, &value, MDB_NEXT_DUP);
|
||||
} // every hook_key
|
||||
return success();
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
expect<std::pair<std::size_t, std::vector<webhook_tx_confirmation>>> storage::update(block_id height, epee::span<const crypto::hash> chain, epee::span<const lws::account> users, epee::span<const pow_sync> pow)
|
||||
expect<storage::updated> storage::update(block_id height, epee::span<const crypto::hash> chain, epee::span<const lws::account> users, epee::span<const pow_sync> pow)
|
||||
{
|
||||
if (users.empty() && chain.empty())
|
||||
return {std::make_pair(0, std::vector<webhook_tx_confirmation>{})};
|
||||
return {updated{}};
|
||||
MONERO_PRECOND(!chain.empty());
|
||||
MONERO_PRECOND(db != nullptr);
|
||||
if (!pow.empty())
|
||||
MONERO_PRECOND(chain.size() == pow.size());
|
||||
|
||||
return db->try_write([this, height, chain, users, pow] (MDB_txn& txn) -> expect<std::pair<std::size_t, std::vector<webhook_tx_confirmation>>>
|
||||
return db->try_write([this, height, chain, users, pow] (MDB_txn& txn) -> expect<updated>
|
||||
{
|
||||
epee::span<const crypto::hash> chain_copy{chain};
|
||||
epee::span<const pow_sync> pow_copy{pow};
|
||||
@@ -2593,7 +2644,7 @@ namespace db
|
||||
const std::uint64_t first_new = lmdb::to_native(height) + 1;
|
||||
|
||||
// collect all .value() errors
|
||||
std::pair<std::size_t, std::vector<webhook_tx_confirmation>> updated;
|
||||
updated out{};
|
||||
if (get_checkpoints().get_max_height() <= last_update)
|
||||
{
|
||||
cursor::blocks blocks_cur;
|
||||
@@ -2652,7 +2703,7 @@ namespace db
|
||||
const auto cur_block = blocks.get_value<block_info>(value);
|
||||
if (!cur_block)
|
||||
return cur_block.error();
|
||||
// If a reorg past a checkpoint is being attempted
|
||||
// If a reorg past a checkpoint is being attempted
|
||||
if (chain[chain.size() - 1] != cur_block->hash)
|
||||
return {error::bad_blockchain};
|
||||
|
||||
@@ -2743,13 +2794,14 @@ namespace db
|
||||
MONERO_CHECK(check_hooks(*webhooks_cur, *events_cur, *user));
|
||||
MONERO_CHECK(
|
||||
add_ongoing_hooks(
|
||||
updated.second, *webhooks_cur, *outputs_cur, *events_cur, user->id(), block_id(first_new), block_id(last_update + 1)
|
||||
out.confirm_pubs, *webhooks_cur, *outputs_cur, *events_cur, user->id(), block_id(first_new), block_id(last_update + 1)
|
||||
)
|
||||
);
|
||||
MONERO_CHECK(check_spends(out.spend_pubs, *webhooks_cur, *outputs_cur, *user));
|
||||
|
||||
++updated.first;
|
||||
++out.accounts_updated;
|
||||
} // ... for every account being updated ...
|
||||
return {std::move(updated)};
|
||||
return {std::move(out)};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2954,7 +3006,7 @@ namespace db
|
||||
key.user = MONERO_UNWRAP(accounts_by_address.get_value<MONERO_FIELD(account_by_address, lookup.id)>(lmvalue));
|
||||
}
|
||||
|
||||
if (key.user == account_id::invalid && type == webhook_type::tx_confirmation)
|
||||
if (key.user == account_id::invalid && (type == webhook_type::tx_confirmation || type == webhook_type::tx_spend))
|
||||
return {error::bad_webhook};
|
||||
|
||||
lmkey = lmdb::to_val(key);
|
||||
|
||||
@@ -264,6 +264,13 @@ namespace db
|
||||
expect<std::vector<account_address>>
|
||||
reject_requests(request req, epee::span<const account_address> addresses);
|
||||
|
||||
//! Status of an `update` request
|
||||
struct updated
|
||||
{
|
||||
std::vector<webhook_tx_spend> spend_pubs;
|
||||
std::vector<webhook_tx_confirmation> confirm_pubs;
|
||||
std::size_t accounts_updated;
|
||||
};
|
||||
/*!
|
||||
Updates the status of user accounts, even if inactive or hidden. Duplicate
|
||||
receives or spends provided in `accts` are silently ignored. If a gap in
|
||||
@@ -274,15 +281,15 @@ namespace db
|
||||
\param chain List of block hashes that `accts` were scanned against.
|
||||
\param accts Updated to `height + chain.size()` scan height.
|
||||
|
||||
\return Number of updated accounts, and a list of webhooks that triggered.
|
||||
\return Status via `updated` object.
|
||||
*/
|
||||
expect<std::pair<std::size_t, std::vector<webhook_tx_confirmation>>>
|
||||
expect<updated>
|
||||
update(block_id height, epee::span<const crypto::hash> chain, epee::span<const lws::account> accts, epee::span<const pow_sync> pow);
|
||||
|
||||
/*!
|
||||
Adds subaddresses to an account. Upon success, an account will
|
||||
immediately begin tracking them in the scanner.
|
||||
|
||||
|
||||
\param id of the account to associate new indexes
|
||||
\param addresss of the account (needed to generate subaddress publc key)
|
||||
\param view_key of the account (needed to generate subaddress public key)
|
||||
@@ -300,7 +307,7 @@ namespace db
|
||||
/*!
|
||||
Add webhook to be tracked in the database. The webhook will "call"
|
||||
the specified URL with JSON/msgpack information when the event occurs.
|
||||
|
||||
|
||||
\param type The webhook event type to be tracked by the DB.
|
||||
\param address is required for `type == tx_confirmation`, and is not
|
||||
not needed for all other types.
|
||||
|
||||
@@ -320,6 +320,10 @@ namespace lws { namespace rpc
|
||||
if (req.address)
|
||||
return {error::bad_webhook};
|
||||
break;
|
||||
case db::webhook_type::tx_spend:
|
||||
if (!req.address)
|
||||
return {error::bad_webhook};
|
||||
break;
|
||||
default:
|
||||
return {error::bad_webhook};
|
||||
}
|
||||
|
||||
@@ -217,6 +217,11 @@ namespace lws
|
||||
vec.erase(vec.begin());
|
||||
};
|
||||
|
||||
void send_spend_hook(rpc::client& client, const epee::span<const db::webhook_tx_spend> events, net::ssl_verification_t verify_mode)
|
||||
{
|
||||
rpc::send_webhook(client, events, "json-full-spend_hook:", "msgpack-full-spend_hook:", std::chrono::seconds{5}, verify_mode);
|
||||
}
|
||||
|
||||
struct by_height
|
||||
{
|
||||
bool operator()(account const& left, account const& right) const noexcept
|
||||
@@ -880,11 +885,11 @@ namespace lws
|
||||
}
|
||||
|
||||
MINFO("Processed " << blocks.size() << " block(s) against " << users.size() << " account(s)");
|
||||
send_payment_hook(client, epee::to_span(updated->second), opts.webhook_verify);
|
||||
|
||||
if (updated->first != users.size())
|
||||
send_payment_hook(client, epee::to_span(updated->confirm_pubs), opts.webhook_verify);
|
||||
send_spend_hook(client, epee::to_span(updated->spend_pubs), opts.webhook_verify);
|
||||
if (updated->accounts_updated != users.size())
|
||||
{
|
||||
MWARNING("Only updated " << updated->first << " account(s) out of " << users.size() << ", resetting");
|
||||
MWARNING("Only updated " << updated->accounts_updated << " account(s) out of " << users.size() << ", resetting");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user