Skip to content

chore(ntx-builder): store sponsorship fee notes in the database - #2494

Open
SantiagoPittella wants to merge 4 commits into
nextfrom
santiagopittella-sponsorship-note-introduction
Open

chore(ntx-builder): store sponsorship fee notes in the database#2494
SantiagoPittella wants to merge 4 commits into
nextfrom
santiagopittella-sponsorship-note-introduction

Conversation

@SantiagoPittella

Copy link
Copy Markdown
Collaborator

Summary

Part 1 of 3 for #2327 (network account fees).

With the new fee sponsorship model, the fee for a network note (the "feature note") travels in a separate FeeSponsorshipNote bound to it by note id. Sponsorship notes carry no attachments, so today the ntx-builder silently drops them during block ingestion.

This PR makes the ntx-builder detect them (by script root) and persist them in a new sponsorship_notes table, indexed by the feature note they pay for. Consumption is tracked by nullifier, and a sponsorship may arrive before or after its feature note since the binding is resolved at read time with a join. The index rebuilds automatically on restart via the existing
block replay.

No behavior change: transaction selection does not use the index yet. Follow-up PRs:

  • PR 2: attach sponsorships to the feature note's transaction during candidate selection.
  • PR 3: detect insufficient-fee execution failures and drop the note after one attempt.

Changelog

changelog = "none"
reason    = "Internal change only."

@SantiagoPittella
SantiagoPittella force-pushed the santiagopittella-sponsorship-note-introduction branch from 2b72f23 to 7196ac1 Compare August 19, 2026 15:22
Comment on lines +450 to +471
pub(crate) async fn insert_sponsorship_notes(
&self,
notes: Vec<SponsorshipNote>,
) -> Result<(), DatabaseError> {
self.writer
.write("insert_sponsorship_notes", move |tx| {
queries::insert_sponsorship_notes(tx, &notes)
})
.await
}

pub(crate) async fn mark_sponsorships_consumed(
&self,
nullifiers: Vec<Nullifier>,
block_num: BlockNumber,
) -> Result<(), DatabaseError> {
self.writer
.write("mark_sponsorships_consumed", move |tx| {
queries::mark_sponsorships_consumed(tx, &nullifiers, block_num)
})
.await
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would these not need to be part of the same db tx as the entire block update?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

In the apply_committed_block function it is part of the same tx, this are under cfg(test) and used only for testing purposes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think ideally we would use production APIs to setup tests where possible i.e. actually get proper blocks in to inject and consume these. But that's part of a larger problem I think, so I guess its fine.

Comment on lines +1 to +2
-- Inserts a FEE_SPONSORSHIP note from a committed block. Uses `INSERT OR IGNORE` so re-applying
-- the same block (e.g. on a redelivery from the subscription stream) is a no-op rather than a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would redelivery not be a pretty major bug?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yes, that's a bug and shouldn't happen. This is defensive, to avoid breaking the DB in that case. If you think that we should error out I can change it (and also change insert_network_note.sql because that does the same)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Up to you; I don't mind that much. The comment just made me concerned.

Comment thread bin/ntx-builder/src/db/queries/mark_sponsorships_consumed/mod.rs Outdated
Comment thread bin/ntx-builder/src/db/queries/mark_sponsorships_consumed/mod.rs Outdated
Comment thread bin/ntx-builder/src/db/queries/insert_sponsorship_notes/mod.rs Outdated
-- are kept around (not deleted), mirroring the `notes` table lifecycle.
UPDATE sponsorship_notes
SET committed_at = ?2
WHERE nullifier IN (SELECT value FROM rarray(?1)) AND committed_at IS NULL

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We could technically avoid the IS NULL check or perhaps it speeds things up with a smaller set?

Comment on lines +12 to +15
/// Returns the unconsumed `FEE_SPONSORSHIP` notes bound to the given account's unconsumed feature
/// notes, grouped by feature note id.
pub fn pending_sponsorships(
tx: &ReadTx<'_>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Name feels a bit weird; but perhaps I need to see how it gets used in the next PR :)

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.

2 participants