Skip to content

feat: batch-fetch and cache address lookup tables for V0 transactions#565

Open
raushan728 wants to merge 3 commits into
solana-foundation:mainfrom
raushan728:feat/alt-batch-cache-resolution
Open

feat: batch-fetch and cache address lookup tables for V0 transactions#565
raushan728 wants to merge 3 commits into
solana-foundation:mainfrom
raushan728:feat/alt-batch-cache-resolution

Conversation

@raushan728

Copy link
Copy Markdown
Contributor

V0 transactions resolved ALTs one at a time via CacheUtil::get_account, with a TODO comment noting caching was never added. Bundles re-resolved the same ALT for every transaction that referenced it.

  • Added CacheUtil::get_multiple_accounts cache-aware batch fetch using Redis mget + RPC get_multiple_accounts for misses
  • resolve_lookup_table_addresses now batches all ALT pubkeys into one call instead of looping
  • Bundle processing shares one ALT cache (HashMap<Pubkey, Vec<Pubkey>>) across all transactions in the same bundle repeated ALT fetched once per bundle, not once per tx. Single-transaction RPC handlers unaffected (pass None)
  • Deduped miss pubkeys before batch fetch (same ALT referenced twice in one tx)

Note: dropped 2 tests for the Redis cache-hit path needed a live Redis instance, CI doesn't run one, and no existing test in cache.rs does either.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the per-ALT CacheUtil::get_account loop in V0 transaction resolution with a cache-aware get_multiple_accounts batch path (Redis mget + RPC get_multiple_accounts for misses), and adds a per-bundle HashMap<Pubkey, Vec<Pubkey>> so that repeated ALT lookups within the same bundle hit the in-process cache rather than going to RPC or Redis on every transaction.

  • CacheUtil::get_multiple_accounts — new cache-aware batch method: checks Redis mget first, fetches misses from RPC in one call, writes back via a pipelined set_ex, and falls through to RPC-only when the cache pool is unavailable.
  • resolve_lookup_table_addresses — deduplicates ALT pubkeys via HashSet in both the Some(cache) and None branches, batch-fetches with get_multiple_accounts, then uses ok_or_else (not unwrap) for the cache-map lookup after population.
  • Bundle path — introduces alt_cache shared across all transactions in a bundle; single-transaction RPC handlers pass None and are unaffected.

Confidence Score: 5/5

The change is safe to merge. All callers have been updated, both code paths deduplicate ALT pubkeys before batch-fetching, error propagation uses ok_or_else throughout, and the bundle-level alt_cache is correctly scoped to a single BundleProcessor::new call.

The core logic is well-structured: cache hits, RPC misses, and Redis write-back follow the same pattern as existing single-account caching. Deduplication and graceful error handling are present in both the Some(cache) and None branches. The one theoretical weakness — get_multiple_accounts returning fewer accounts than expected if RPC truncates — is caught downstream by ok_or_else and requires a Solana RPC protocol violation to trigger.

No files require special attention beyond crates/lib/src/cache.rs, where the implicit contract that get_multiple_accounts returns exactly N accounts for N input pubkeys could benefit from an explicit length assertion.

Important Files Changed

Filename Overview
crates/lib/src/cache.rs Adds get_multiple_accounts with Redis mget/RPC batch-miss flow; return-length invariant for the cache path relies on Solana RPC always returning N elements for N requested pubkeys (correct in practice, but not asserted).
crates/lib/src/transaction/versioned_transaction.rs Rewrites resolve_lookup_table_addresses to batch-fetch ALTs via get_multiple_accounts; both Some(cache) and None branches deduplicate via HashSet and use ok_or_else instead of unwrap; logic is correct for all normal RPC responses.
crates/lib/src/bundle/helper.rs Adds per-bundle alt_cache: HashMap<Pubkey, Vec<Pubkey>> and threads it through VersionedTransactionResolved::from_transaction; straightforward and correct.
crates/lib/src/tests/rpc_mock.rs Adds with_multiple_accounts_info mock helper to support the new GetMultipleAccounts RPC call path in tests; implementation is correct.
crates/lib/src/token/token.rs Test cleanup: three token tests now explicitly disable the cache to avoid flaky Redis interactions, matching the pattern established by other tests in the file.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller as resolve_lookup_table_addresses
    participant AltCache as alt_cache (HashMap)
    participant GetMulti as CacheUtil::get_multiple_accounts
    participant Redis as Redis (mget/set_ex)
    participant RPC as Solana RPC

    Caller->>AltCache: check for each ALT pubkey
    Note over AltCache: misses collected into HashSet
    Caller->>GetMulti: batch fetch misses[]
    GetMulti->>Redis: mget(keys[])
    Redis-->>GetMulti: "Vec<Option<String>> (hits / misses)"
    GetMulti->>RPC: get_multiple_accounts(rpc_misses[])
    RPC-->>GetMulti: "Vec<Option<Account>>"
    GetMulti->>Redis: pipe.set_ex() for each fetched account
    GetMulti-->>Caller: "Vec<Account> (in input order)"
    Caller->>AltCache: insert(pubkey, addresses) for each miss
    Caller-->>Caller: zip lookups with full_address_lists → resolved_addresses
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller as resolve_lookup_table_addresses
    participant AltCache as alt_cache (HashMap)
    participant GetMulti as CacheUtil::get_multiple_accounts
    participant Redis as Redis (mget/set_ex)
    participant RPC as Solana RPC

    Caller->>AltCache: check for each ALT pubkey
    Note over AltCache: misses collected into HashSet
    Caller->>GetMulti: batch fetch misses[]
    GetMulti->>Redis: mget(keys[])
    Redis-->>GetMulti: "Vec<Option<String>> (hits / misses)"
    GetMulti->>RPC: get_multiple_accounts(rpc_misses[])
    RPC-->>GetMulti: "Vec<Option<Account>>"
    GetMulti->>Redis: pipe.set_ex() for each fetched account
    GetMulti-->>Caller: "Vec<Account> (in input order)"
    Caller->>AltCache: insert(pubkey, addresses) for each miss
    Caller-->>Caller: zip lookups with full_address_lists → resolved_addresses
Loading

Reviews (2): Last reviewed commit: "feat(bundle): reuse resolved ALTs across..." | Re-trigger Greptile

Comment thread crates/lib/src/transaction/versioned_transaction.rs
Comment thread crates/lib/src/transaction/versioned_transaction.rs Outdated
@raushan728 raushan728 force-pushed the feat/alt-batch-cache-resolution branch from 30b5abf to faa046e Compare June 16, 2026 07:57
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