(Technical): Why does `batch::try_compact_note_decryption` need a `SaplingDomain` for each Output?

The batched version of try_compact_note_decryption is faster, which is cool, but it needs a copy of the SaplingDomain for each CompactOutput in that batch.

It seems to be that the SaplingDomain is exactly the same for a block (Created with SaplingDomain::for_height), so we’re creating 1000s of identical copies of it, cloning the over and over again.

Why can’t I pass a single SaplingDomain for the whole batch?

@str4d, @nuttycom

This is because the try_compact_note_decryption API is generic over both the transactions being trial-decrypted, and the pool. It’s true that for Sapling the domain is the same within a single block, but the moment you want to batch trial-decrypt across more than one block, you need multiple domains. Additionally, for Orchard you need the nullifier of the note spent in the same action, so it’s not even the same within a single block.

I also doubt that the cost of this cloning is contributing to a significant performance loss (though I’d love to be proven wrong with benchmarks). A SaplingDomain stores:

  • a consensus::Parameters, which is a zero-sized type if you are using the consensus::{MainNetwork, TestNetwork} structs (e.g. via their MAIN_NETWORK and TEST_NETWORK constants), or 1 byte if you use the consensus::Network enum.
  • a BlockHeight, which is 4 bytes.

Meanwhile, each Sapling compact output is 116 bytes. IIRC the maximum number of Sapling outputs that can be in a block is around 2100, so the memory overhead of storing one SaplingDomain per output instead of one per block is around 4.3%.