Skip to content

sdk(batchDistribution): createBatches infinite-loops on batchSize <= 0; maxRecipientsPerBatch is never validated #257

@pragmaticAweds

Description

@pragmaticAweds

Summary

createBatches(array, batchSize) increments the loop counter by batchSize. If a caller (directly or via BatchDistributionConfig.maxRecipientsPerBatch) passes 0, the loop is infinite (i += 0 never advances) and the process hangs. If they pass a negative number, the loop body never executes and the function silently returns [] — i.e. zero transactions, no error — even though the caller asked for a real distribution.

prepareBatchEqualDistribution and prepareBatchWeightedDistribution accept maxRecipientsPerBatch straight from user-supplied config with ?? 100 and pass it through without any sanity check.

Where

packages/sdk/src/utils/batchDistribution.ts:343-351

export function createBatches<T>(array: T[], batchSize: number): T[][] {
  const batches: T[][] = [];
  for (let i = 0; i < array.length; i += batchSize) {  // batchSize=0 → infinite loop
    batches.push(array.slice(i, i + batchSize));
  }
  return batches;
}

packages/sdk/src/utils/batchDistribution.ts:199, 282

const maxRecipientsPerBatch = config.maxRecipientsPerBatch ?? 100;
// ... no validation that maxRecipientsPerBatch > 0 ...
const recipientBatches = createBatches(recipients, maxRecipientsPerBatch);

Impact

  • Infinite loop on 0: the entire process (Node script, browser tab) hangs. No way to recover without a kill. Easy to hit by accident with Number(formInput) where the form was empty.
  • Silent no-op on negative: prepareBatch* returns { transactions: [], batchCount: 0 }. Callers who don't check batchCount will believe the distribution succeeded with no on-chain effect.
  • Non-integer values (e.g. 100.5) produce overlapping/short slices and double-charge recipients in adjacent batches.

Fix

Validate at the public entry points and in createBatches:

function assertPositiveInt(n: number, name: string): void {
  if (!Number.isInteger(n) || n <= 0) {
    throw new Error(\`\${name} must be a positive integer (got \${n})\`);
  }
}
  • Call assertPositiveInt(maxRecipientsPerBatch, 'config.maxRecipientsPerBatch') at the top of prepareBatchEqualDistribution and prepareBatchWeightedDistribution.
  • Add the same check in createBatches for defense-in-depth (since it's exported).

Acceptance criteria

  • createBatches([1,2,3], 0) throws synchronously with a clear message.
  • createBatches([1,2,3], -1) throws.
  • createBatches([1,2,3], 1.5) throws.
  • prepareBatchEqualDistribution/prepareBatchWeightedDistribution reject invalid maxRecipientsPerBatch before any RPC calls.
  • Tests cover each rejection case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programbugSomething isn't workingdifficulty: easySmall, bounded changessdkIssues related to the SDK package

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions