Skip to content

feat: add inscription event indexing, persistence, and query API endpoints#1

Open
markwu35 wants to merge 7 commits intomasterfrom
DX-4892
Open

feat: add inscription event indexing, persistence, and query API endpoints#1
markwu35 wants to merge 7 commits intomasterfrom
DX-4892

Conversation

@markwu35
Copy link
Copy Markdown

@markwu35 markwu35 commented Apr 7, 2026

https://linear.app/quicknode/issue/DX-4892/track-inscription-creation-and-transfer-events-ordinals-and-runes-api

https://github.com/quiknode-labs/ordinals-json-rpc/pull/7

Summary

  • Persist inscription creation and transfer events during block indexing into two new redb tables (INSCRIPTION_EVENT_TO_DATA,
    SEQUENCE_NUMBER_TO_INSCRIPTION_EVENTS)
  • Resolve from_address and to_address at index time from script_pubkeys so queries are self-contained
  • Expose two new paginated JSON API endpoints:
    • GET /r/events/block/{start}/{end}/{page} — query events by block range
    • GET /r/events/inscription/{id}/{page} — query full history of an inscription
  • Gate behind --index-inscription-events flag (requires --index-addresses); no behavior change without the flag

Event record fields

event_type, block_height, inscription_id, txid, new_satpoint, old_satpoint, to_address, from_address, charms

Files changed (9 files, +662 lines)

File Change
entry.rs InscriptionEventEntry struct with binary serialize/deserialize
options.rs / settings.rs --index-inscription-events CLI flag
index.rs Table definitions, Statistic variant, query methods, integration tests
updater.rs Open new tables, pass to InscriptionUpdater
inscription_updater.rs Capture addresses, persist events at creation and transfer sites
api.rs InscriptionEvents / InscriptionEvent response types
server.rs / r.rs Route registration and endpoint handlers

Test plan

  • 3 unit tests for serialization round-trips (created, transferred, unbound)
  • Integration test: creation event persisted and queryable by ID and block range
  • Integration test: transfer event persisted with from/to addresses
  • Integration test: no events persisted without --index-inscription-events flag
  • Manual test against running instance with --index-inscription-events --index-addresses

Deployment note

Requires full reindex to populate historical events. Recommended: run a parallel instance with the new flags, let it sync from scratch, swap in once caught up.


Note

Medium Risk
Adds new persisted event data and query paths in the indexer, including address derivation and reorg-sensitive writes; issues could affect index correctness and API results when the flag is enabled.

Overview
Adds an optional inscription event index (gated by --index-inscription-events, requiring --index-addresses) that persists created and transferred events during block indexing, including derived from_address/to_address and satpoint changes.

Introduces new redb tables and query helpers to fetch events by inscription ID or block-height range with pagination, and exposes them via new JSON endpoints GET /r/events/inscription/{inscription_id}/{page} and GET /r/events/block/{start}/{end}/{page}.

Extends the API model with InscriptionEvents/InscriptionEvent, updates schema/statistics wiring, and adds unit/integration tests covering persistence, flag gating, and reorg rollback behavior.

Reviewed by Cursor Bugbot for commit 20842cc. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/subcommand/server/r.rs
Comment thread src/index/entry.rs Outdated
Comment thread src/index.rs Outdated
Comment thread src/api.rs
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 43614d3. Configure here.

Comment thread src/index/entry.rs Outdated
Copy link
Copy Markdown

@theelderbeever theelderbeever left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really have a ton of context but, I added some comments.

Comment thread src/index/updater/inscription_updater.rs Outdated
Comment thread src/index/updater/inscription_updater.rs
Comment thread src/index/entry.rs
match v {
0 => Self::Created,
1 => Self::Transferred,
_ => panic!("invalid InscriptionEventType: {v}"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is panicking really what you want to do here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ord's Entry trait is infallible. Every existing load() returns Self, not Result, and the whole deserialize() fn here is full of unwraps on try_into() / from_utf8. This explicit panic! is actually the only one with a useful error message; replacing it in isolation doesn't improve safety, and converting to a Result-returning deserialize is a broader change that departs from the rest of the codebase.

Comment thread src/index/entry.rs
Comment on lines +577 to +581
pub fn deserialize(data: &[u8]) -> Self {
let mut pos = 0;
let event_type = InscriptionEventType::from_u8(data[pos]);
pos += 1;
let block_height = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the custom deserialization and storage format?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no strong reason. The ord-idiomatic approach is the Entry trait with a tuple-typed Value, and the PR could use it here. And we'd probably want to get this into the open-sourced repo in the future so we'd want fewer changes and just follow what's existing currently

Comment on lines +631 to +636
task::block_in_place(|| {
if !index.has_inscription_event_index() {
return Err(ServerError::NotFound(
"this server has no inscription event index".to_string(),
));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rationale behind block_in_place? Presumably this is blocking the whole worker thread. What async runtime is being used?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime is tokio multi-thread (features = ["rt-multi-thread"] in Cargo.toml:96, multi-thread builder at settings.rs:613-623). block_in_place is the established convention for every /r/ handler in this file — there are 22 uses already, going back to the first handler (r::blockhash at line 6). It's specifically designed to prevent worker starvation when calling sync code from an async handler on a multi-threaded runtime: tokio migrates other tasks off the worker before running the closure, so no other task is blocked. For short redb reads like these, it's cheaper than spawn_blocking (no extra thread pool hop). If redb lookups ever grew into genuinely long operations we'd move to spawn_blocking, but that's not today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants