Skip to content

Test maximum number of solvers that may exist #125

Description

@kaze-cow

I didn't write this in the PR description but I did a bit of research on this and it's indeed more difficult than it seems right now! Indeed there's the 10M byte limit per account (327680 words as you mention). But another bigger problem is the maximum amount of data you can allocate in a single transaction for an account (10k) which is the main limit if we want to do something fancy (so max 320 words per transaction).
All of this is irrelevant here. It's possible to send ~327680 txs to fill up the account.
I tried to add an ignored test but it's extremely slow, I interrupted it well before it reached completion. I guess it would take, I'd say, a few hours to complete. I waited something between 20k and 30k transactions before aborting.
Let's revisit this if we ever extend this function to add multiple solvers per transaction (we should).

Test (trigger warning: vibes!)

/// Adds solvers one 32-byte entry per transaction through the real instruction
/// until the state PDA can grow no further, confirming the list fills the account
/// right up to the runtime's account-size cap and the add that would cross it
/// reverts the same way [`rejects_growing_beyond_the_max_account_size`] does.
///
/// Ignored by default: filling a 10 MiB account one solver per transaction is on
/// the order of 327k transactions, far too slow for the normal suite. Run it with
/// `cargo test -p settlement --test add_solvers -- --ignored --exact --nocapture \
/// fills_state_pda_to_capacity` to watch the completion percentage tick up.
#[test]
#[ignore = "grows the state PDA to the full 10 MiB, one solver per tx (~327k txs)"]
fn fills_state_pda_to_capacity() {
    let (mut svm, params) = setup_init();

    // Filling the account to the cap needs the payer to fund the rent for a 10 MiB
    // account (~73 SOL) plus a transaction fee per add; the default 1 SOL from
    // `setup` isn't nearly enough, so top the payer up generously.
    svm.airdrop(&params.payer.pubkey(), 250 * LAMPORTS_PER_SOL)
        .expect("topping up the payer should succeed");

    /// A deterministic solver address holding `index` big-endian in its leading
    /// four bytes, so the addresses ascend with `index` and every add appends at
    /// the end of the sorted list without shifting the existing entries.
    fn indexed_solver(index: u32) -> Pubkey {
        let mut bytes = [0u8; 32];
        bytes[..4].copy_from_slice(&index.to_be_bytes());
        Pubkey::new_from_array(bytes)
    }

    // The most solvers that can ever fit: the entries that follow the header once
    // the account is grown to the runtime's cap. Used only to report progress.
    let capacity = (MAX_PERMITTED_DATA_LENGTH as usize - WIDTH_HEADER) / WIDTH_PUBKEY;

    // Keep adding ascending solvers until one won't fit. The failing add is the
    // runtime refusing to grow the account past its size limit, which surfaces as
    // a plain `InvalidArgument` rather than one of our `Custom` settlement errors.
    let mut count: u32 = 0;
    loop {
        match add_solver(&mut svm, &params, &indexed_solver(count)) {
            Ok(()) => {
                count += 1;
                // Progress ticker (visible under `--nocapture`): this loop runs for
                // hundreds of thousands of transactions, so report how full the
                // account is every so often.
                if count % 10_000 == 0 {
                    let percent = 100.0 * f64::from(count) / capacity as f64;
                    println!("filling state PDA: {count}/{capacity} solvers ({percent:.1}%)");
                }
            }
            Err(err) => {
                assert_eq!(
                    err,
                    TransactionError::InstructionError(0, InstructionError::InvalidArgument),
                );
                break;
            }
        }
    }

    // The account holds exactly `count` solvers and sits within one entry of the
    // cap: the next solver would cross it, and the leftover slack is smaller than a
    // full entry.
    let data_len = svm
        .get_account(&params.state_pda)
        .expect("state PDA exists")
        .data
        .len();
    assert_eq!(data_len, WIDTH_HEADER + (count as usize) * WIDTH_PUBKEY);
    assert!(data_len <= MAX_PERMITTED_DATA_LENGTH as usize);
    assert!(data_len + WIDTH_PUBKEY > MAX_PERMITTED_DATA_LENGTH as usize);
}

Originally posted by @fedgiac in #119 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions