Skip to content

Support multiple IndexOperation::Renew calls within a single extrinsic index in sc-client-db#11474

Open
rosarp wants to merge 3 commits intoparitytech:masterfrom
rosarp:rs-multi-data-renew-per-extrinsic
Open

Support multiple IndexOperation::Renew calls within a single extrinsic index in sc-client-db#11474
rosarp wants to merge 3 commits intoparitytech:masterfrom
rosarp:rs-multi-data-renew-per-extrinsic

Conversation

@rosarp
Copy link
Member

@rosarp rosarp commented Mar 24, 2026

Description

Currently, apply_index_ops uses renewed_map: HashMap<u32, DbHash> which means if multiple Renew operations target the same extrinsic index, only the last hash survives — earlier ones are silently overwritten. This blocks batch-renewal use cases where a single mandatory inherent renews multiple previously-stored data items (e.g. the Bulletin chain's process_auto_renewals inherent which calls sp_io::transaction_index::renew() N times within one extrinsic).

This PR changes renewed_map to HashMap<u32, Vec<DbHash>> and introduces a new DbExtrinsic::MultiRenew variant to correctly store, reconstruct, retrieve, and prune blocks containing multi-renewal extrinsics.

Integration

Downstream projects using sc-client-db that read BODY_INDEX data directly (rather than through the BlockchainDb API) will need to handle the new DbExtrinsic::MultiRenew variant. Projects using the standard blockchain.body(), blockchain.block_indexed_body(), or blockchain.indexed_transaction() APIs require no changes.

Single-renewal extrinsics continue to produce DbExtrinsic::Indexed (backwards-compatible). The MultiRenew variant is only emitted when 2+ Renew operations share the same extrinsic index.

Review Notes

All changes are in substrate/client/db/src/lib.rs. There are five logical changes:

1. New DbExtrinsic::MultiRenew variant

MultiRenew {
    hashes: Vec<DbHash>,  // all renewed data hashes
    header: Vec<u8>,       // full encoded extrinsic for body reconstruction
}

2. apply_index_ops — core fix

- let mut renewed_map = HashMap::new();
+ let mut renewed_map: HashMap<u32, Vec<DbHash>> = HashMap::new();

  IndexOperation::Renew { extrinsic, hash } => {
-     renewed_map.insert(extrinsic, DbHash::from_slice(hash.as_ref()));
+     renewed_map.entry(extrinsic).or_default().push(DbHash::from_slice(hash.as_ref()));
  }

When building extrinsic entries:

  • 1 hashDbExtrinsic::Indexed (backwards-compatible, same as before)
  • 2+ hashesDbExtrinsic::MultiRenew with ref count bumped for each hash

3. body_uncached — body reconstruction

MultiRenew's header contains the full encoded extrinsic (unlike Indexed where header is partial and joined with indexed data). Decoded directly via Block::Extrinsic::decode(&mut &header[..]).

4. block_indexed_body — indexed data retrieval

Returns transaction data for all hashes in MultiRenew, not just a single hash.

5. prune_block — ref count release

Releases all hashes in MultiRenew when pruning, instead of just the single hash from Indexed.

@rosarp rosarp added the T0-node This PR/Issue is related to the topic “node”. label Mar 24, 2026
@rosarp rosarp marked this pull request as ready for review March 24, 2026 18:24
@paritytech-review-bot paritytech-review-bot bot requested a review from a team March 25, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T0-node This PR/Issue is related to the topic “node”.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant