Skip to content

Dedupe NFT mint URI fetches across editions - #866

Open
judeallred wants to merge 5 commits into
xch-dev:mainfrom
judeallred:worktree-nft-mint-url-fetch-dedupe
Open

Dedupe NFT mint URI fetches across editions#866
judeallred wants to merge 5 commits into
xch-dev:mainfrom
judeallred:worktree-nft-mint-url-fetch-dedupe

Conversation

@judeallred

@judeallred judeallred commented Sep 9, 2026

Copy link
Copy Markdown

Summary

  • Avoid re-fetching data/metadata/license URIs for NFT mints that share the same URI list across editions (e.g. bulk-minting many editions of the same NFT).
  • Cache the fetched content hash per URI list (keyed by Vec<String> -> Bytes32) instead of caching the full Data blob, so cache hits don't clone the raw bytes/thumbnail. ConfirmationInfo::nft_data remains the single place the fetched blob is stored.
  • Skip the 1s backoff sleep in fetch_minter_hash's parent-walking loop when the next hop is already known locally (i.e. it's part of the same pending transaction being submitted), instead of unconditionally sleeping after every failed parse attempt. This was the dominant cost in the "submitting" phase of a bulk mint, since most of a freshly-minted NFT's ancestor chain (launcher → intermediate coin → DID re-spend) is part of the transaction being submitted and needs no propagation-delay backoff.
  • Folded uri_cache into ConfirmationInfo (uri_hashes field) instead of threading it as a second parallel mutable parameter next to info through convert_nft_mint and both of its call sites — they share the same per-transaction lifetime, so they're now one struct instead of two.

Profiling

Real-world test (50 NFT mints):

  • Before any of these changes: 2:17 total.
  • After the URI dedup fix: 1:24 total.
  • After the minter-hash sleep-skip fix (current state of this PR): 35s total — roughly split evenly between the pre-confirmation (build/mint) phase and the post-confirmation (submit) phase.

Isolated benchmarks for each change against the test PeerSimulator/a local mock HTTP server:

Change Before After
URI fetch dedup (20 editions sharing one URI, 150ms simulated fetch latency) 9.53s 364ms
Skip minter-hash backoff sleep (50-NFT bulk mint submit time) 67.6s 13.8s

Both changes are independent and additive — one targets the mint/build phase (redundant network fetches of NFT content), the other targets the submit phase (wasted backoff sleeps while walking coin ancestry).

Test plan

  • cargo check -p sage
  • cargo clippy -p sage --all-targets
  • cargo test -p sage-wallet (48 tests)
  • Manual test: bulk-mint several NFT editions sharing data/metadata/license URIs and confirm only one fetch occurs per unique URI list.

Comment thread crates/sage/src/endpoints/transactions.rs Outdated
@judeallred

Copy link
Copy Markdown
Author

This version decreases the time to mine 50 NFTs from 2:17 to 1:24 in my testbed. Trying to optimize further...

Cache the fetched content hash per URI list instead of the full Data
blob, so repeated editions sharing data/metadata/license URIs skip
the network fetch without cloning the raw bytes on every cache hit.
info.nft_data remains the single place the blob is stored.
fetch_minter_hash walks parent coin spends looking for the minting
singleton, sleeping 1s between failed hops as a backoff against a full
node not having caught up yet. But for a freshly-minted NFT, most of
that ancestor chain is part of the very same transaction being
submitted and is already resolvable from the local pending-spend
cache, so the sleep just adds dead time for data we already have.

Only back off when the previous hop actually required a real network
round-trip. Benchmarked with a 50-NFT bulk mint against the test
PeerSimulator: submit time drops from 67.6s to 13.8s.

Also pins current minter_hash behavior with a regression test, since
nothing previously asserted on it.
@judeallred
judeallred force-pushed the worktree-nft-mint-url-fetch-dedupe branch from 0e3628a to eb3085c Compare September 10, 2026 03:38
uri_cache and info were always created together and passed together
through convert_nft_mint and both of its call sites. Since they share
the same per-transaction lifetime, keep them as one struct instead of
two parallel mutable parameters.
It doesn't protect the sleep-skip fix: is_pending only gates the
backoff sleep and never affects which branch runs or what
fetch_minter_hash returns, so this assertion would pass identically
regardless of whether is_pending were computed correctly. It was
written for a different, unshipped approach (skipping the network
fetch entirely for self-minted NFTs), where getting that logic wrong
really could have changed the result.
@judeallred
judeallred marked this pull request as ready for review September 10, 2026 03:59
@judeallred

Copy link
Copy Markdown
Author

@Rigidity for your consideration

tl;dr: minting 50 NFTs 2:17s -> 35s

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.

1 participant