> ## Documentation Index
> Fetch the complete documentation index at: https://www.helius.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Масштабная фильтрация аккаунтов с помощью сжатых фильтров

> Подпишитесь на сотни тысяч аккаунтов Solana в одном gRPC-потоке LaserStream с помощью сжатых cuckoo-фильтров — запросы на подписку будут примерно в 8 раз меньше.

## Обзор

LaserStream поддерживает **сжатую фильтрацию аккаунтов с помощью cuckoo-фильтров**. Вместо отправки явного списка публичных ключей в запросе на подписку (32 байта на аккаунт) вы отправляете компактный вероятностный фильтр, который занимает примерно **3–4 байта на аккаунт** при передаче.

Это позволяет подписаться на **сотни тысяч аккаунтов в одном потоке** — без сегментирования между подключениями и без чрезмерно больших запросов на подписку.

Например, сериализованный фильтр для отслеживания 500 000 аккаунтов занимает около 2,1 МБ, тогда как исходный список публичных ключей — 16 МБ. Это примерно **в 7,6 раза меньше**. Точная экономия зависит от степени заполнения фильтра: чем ближе он к предельной емкости, тем меньше байтов требуется на один аккаунт.

### Доступность

| Клиент | Минимальная версия | Поддержка cuckoo-фильтров |
| - | - | - |
| LaserStream SDK — Rust (`helius-laserstream`) | 0.2.0 | ✅ |
| LaserStream SDK — JavaScript/TypeScript (`helius-laserstream`) | 0.4.0 | ✅ |
| LaserStream SDK — Go | — | ❌ Пока нет |
| Yellowstone gRPC — Rust (`yellowstone-grpc-client`) | 13.1.0 | ✅ |

## Когда использовать cuckoo-фильтры

| Отслеживаемые аккаунты | Рекомендуемый подход |
| - | - |
| До \~10 000 | Явные списки публичных ключей (`account: [...]`) — простой и точный вариант |
| \~10 000 и более | Cuckoo-фильтр через `CompressedAccountFilterSet` |

Типичные сценарии использования: мониторинг всех держателей токена, отслеживание всех позиций в протоколе кредитования или наблюдение за большими наборами кошельков в торговой или аналитической системе.

## Принцип работы

1. **Создайте фильтр на стороне клиента.** Добавьте каждый отслеживаемый публичный ключ в `CompressedAccountFilterSet`. Начальное значение хеша выбирается случайным образом для каждого фильтра и сериализуется вместе с ним, поэтому сервер хеширует входящие аккаунты с тем же начальным значением, которое использовал ваш клиент.
2. **Прикрепите его к запросу на подписку.** `insert_into_subscribe_request()` помещает сериализованный фильтр в поток аккаунтов стандартного `SubscribeRequest`.
3. **Сервер выполняет вероятностное сопоставление.** Поскольку фильтр вероятностный, сервер может отправлять обновления для аккаунтов, которые вы не отслеживаете. Доля ложноположительных результатов составляет **менее 1% при полной загрузке**. Ложноотрицательных результатов **не бывает**: каждое обновление отслеживаемого аккаунта будет доставлено.
4. **Повторно проверяйте каждое обновление локально — этот шаг обязателен.** Вызывайте `set.contains(pubkey)` для каждого входящего аккаунта перед обработкой. Эта проверка точна, поскольку использует внутреннее множество хешей, поэтому после локальной фильтрации ложноположительных результатов не остается.

## Быстрый старт (Rust)

Добавьте SDK в свой проект:

```toml Cargo.toml theme={"system"}
[dependencies]
helius-laserstream = "0.2"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
```

Создайте фильтр, прикрепите его к подписке и удаляйте ложноположительные результаты локально:

```rust main.rs [expandable] theme={"system"}
use {
    futures::StreamExt,
    helius_laserstream::{
        cuckoo::{CompressedAccountFilterSet, Pubkey},
        grpc::{subscribe_update::UpdateOneof, SubscribeRequest},
        subscribe, LaserstreamConfig,
    },
    std::str::FromStr,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The exact set of accounts you care about. In production this is
    // typically loaded from your database — hundreds of thousands of keys.
    let tracked: Vec<Pubkey> = [
        "So11111111111111111111111111111111111111112", // Wrapped SOL
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
        "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT
    ]
    .iter()
    .map(|s| Pubkey::from_str(s).unwrap())
    .collect();

    // Build the cuckoo filter. Size it for your peak tracked-set size.
    let mut set = CompressedAccountFilterSet::with_capacity(500_000)?;
    for pk in &tracked {
        set.insert(*pk)?;
    }
    println!(
        "Tracking {} accounts via cuckoo filter ({} bytes on the wire)",
        set.len(),
        set.to_proto().data.len()
    );

    // Attach the compressed filter to the accounts stream.
    let mut request = SubscribeRequest::default();
    set.insert_into_subscribe_request(&mut request, "tracked_accounts");

    let config = LaserstreamConfig::new(
        "https://laserstream-mainnet-ewr.helius-rpc.com".to_string(), // Choose your closest region
        "YOUR_API_KEY".to_string(), // Replace with your key from https://dashboard.helius.dev/
    );

    let (stream, _handle) = subscribe(config, request);
    tokio::pin!(stream);
    while let Some(message) = stream.next().await {
        match message {
            Ok(update) => {
                if let Some(UpdateOneof::Account(account_update)) = update.update_oneof {
                    if let Some(info) = account_update.account {
                        let pk = Pubkey::try_from(info.pubkey.as_slice()).ok();
                        // Re-check locally: drop server-side false positives.
                        match pk {
                            Some(pk) if set.contains(pk) => {
                                println!(
                                    "tracked account update: {pk} (slot {})",
                                    account_update.slot
                                );
                            }
                            Some(pk) => {
                                println!("(false positive, ignored): {pk}");
                            }
                            None => {}
                        }
                    }
                }
            }
            Err(e) => eprintln!("stream error: {e}"),
        }
    }

    Ok(())
}
```

Полная готовая к запуску версия входит в SDK: [`rust/examples/cuckoo_account_filter.rs`](https://github.com/helius-labs/laserstream-sdk/blob/main/rust/examples/cuckoo_account_filter.rs).

## Быстрый старт (JavaScript/TypeScript)

Установите SDK (для поддержки cuckoo-фильтров требуется `helius-laserstream` версии 0.4.0 или новее):

```bash theme={"system"}
npm install helius-laserstream
```

Создайте и прикрепите фильтр, а затем повторно проверяйте каждое обновление локально:

```typescript [expandable] theme={"system"}
import {
  subscribe,
  CommitmentLevel,
  CompressedAccountFilterSet,
  SubscribeUpdate,
  LaserstreamConfig,
} from 'helius-laserstream';

async function main() {
  const config: LaserstreamConfig = {
    apiKey: 'YOUR_API_KEY', // Replace with your key from https://dashboard.helius.dev/
    endpoint: 'https://laserstream-mainnet-ewr.helius-rpc.com', // Choose your closest region
  };

  // The accounts you want to track. In production this is typically loaded
  // from your database — hundreds of thousands of keys.
  const addresses = [
    'So11111111111111111111111111111111111111112', // Wrapped SOL
    'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
    'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // USDT
  ];

  // Build a compact cuckoo filter instead of sending the full pubkey list.
  // Size capacity for your peak tracked-set size.
  const tracked = new CompressedAccountFilterSet(500_000);
  for (const address of addresses) {
    tracked.insert(address);
  }

  // Attach the filter to the request (no explicit account list needed).
  const request: any = { accounts: {}, commitment: CommitmentLevel.CONFIRMED };
  tracked.insertIntoSubscribeRequest(request, 'tracked-accounts');

  const stream = await subscribe(
    config,
    request,
    async (update: SubscribeUpdate) => {
      const pubkey = update.account?.account?.pubkey;
      if (!pubkey) return;
      // Re-check locally: drop server-side false positives. This is exact.
      if (tracked.contains(pubkey)) {
        console.log('tracked account update:', update.account);
      }
    },
    (error: Error) => {
      console.error('Stream error:', error);
    }
  );

  process.on('SIGINT', () => {
    stream.cancel();
    process.exit(0);
  });
}

main().catch(console.error);
```

Полная готовая к запуску версия входит в SDK: [`javascript/examples/cuckoo-account-sub.ts`](https://github.com/helius-labs/laserstream-sdk/blob/main/javascript/examples/cuckoo-account-sub.ts).

## Справочник API

`CompressedAccountFilterSet` объединяет исходный cuckoo-фильтр с точным множеством хешей, поэтому изменения и проверки принадлежности всегда безопасны и точны:

| Метод | Поведение |
| - | - |
| `with_capacity(n)` | Создает фильтр с размером, рассчитанным на `n` отслеживаемых аккаунтов. Рассчитывайте размер на **пиковое** количество отслеживаемых аккаунтов. |
| `insert(pubkey)` | Возвращает `Ok(true)` для нового элемента, `Ok(false)` для дубликата и `Err(TableFullError)`, если фильтр достиг предельной емкости. |
| `remove(pubkey)` | Удаляет аккаунт. Операция безопасна и точна. |
| `contains(pubkey)` | Точная проверка принадлежности — используйте ее для удаления ложноположительных результатов, полученных от сервера. |
| `insert_into_subscribe_request(&mut request, "label")` | Прикрепляет фильтр к потоку аккаунтов `SubscribeRequest`. |
| `to_account_filter()` / `to_proto()` | Низкоуровневые преобразования для формирования пользовательских запросов. |
| `is_dirty()` / `take_dirty()` | Сообщают, изменилось ли множество с момента его последнего добавления в запрос. Это полезно при повторных подписках. |

Приведенные выше имена методов соответствуют соглашениям Rust. SDK для JavaScript/TypeScript предоставляет тот же интерфейс с именами в camelCase: `new CompressedAccountFilterSet(capacity)` вместо `with_capacity`, `insertIntoSubscribeRequest`, `isDirty`, `takeDirty`, `toProto` и так далее. В JavaScript метод `insert` возвращает логическое значение (`true`, если элемент добавлен впервые) и выдает исключение `TableFullError`, когда фильтр переполнен. Публичный ключ можно передать в виде строки base58, исходной последовательности из 32 байтов или любого объекта с методом `toBytes()`.

Всегда используйте `CompressedAccountFilterSet`, а не обернутый в него исходный `CuckooFilter`. Метод `remove()` исходного фильтра может незаметно удалить не тот элемент — это известная и документированная проблема cuckoo-фильтров. Обертка объединяет фильтр с точным множеством хешей, поэтому операции вставки, удаления и проверки наличия всегда выполняются корректно.

## Расчет емкости

* Рассчитывайте размер фильтра через `with_capacity(n)` на **пиковое** количество аккаунтов, которое вы планируете отслеживать.
* При вставке сверх предельной емкости корректно возвращается `TableFullError` — фильтр никогда не повреждается. На практике таблица допускает небольшое превышение емкости, прежде чем начнет отклонять вставки, но не полагайтесь на этот запас.
* Размер сериализованных данных определяется емкостью, а не количеством добавленных аккаунтов, поэтому слишком большой фильтр напрасно увеличивает объем передаваемых данных. Выбирайте емкость, близкую к реальному пиковому значению.

## Обновление отслеживаемого множества

Когда отслеживаемое множество изменяется — появляются новые аккаунты или удаляются старые:

1. Вызовите `insert()` / `remove()` для `CompressedAccountFilterSet`.
2. Проверьте `is_dirty()` или получите и сбросьте флаг с помощью `take_dirty()`, чтобы определить, изменился ли фильтр с момента последней отправки.
3. Если фильтр изменен, заново сформируйте запрос с помощью `insert_into_subscribe_request()`. В JavaScript его можно повторно отправить в том же потоке с помощью `stream.write(request)`. В Rust оформите повторную подписку с заново сформированным запросом.

## Часто задаваемые вопросы

<Accordion title="Can I miss updates for accounts in my filter?">
  Нет. Cuckoo-фильтры дают ложноположительные результаты — дополнительные обновления для неотслеживаемых аккаунтов, — но **никогда не дают ложноотрицательных результатов**. Каждое обновление отслеживаемого аккаунта будет доставлено.
</Accordion>

<Accordion title="How many extra (false-positive) updates will I receive?">
  Менее 1% при полной загрузке и, как правило, еще меньше, если фильтр заполнен не до предельной емкости. Один локальный вызов `contains()` для каждого обновления точно отфильтрует такие результаты.
</Accordion>

<Accordion title="Which clients support cuckoo filters?">
  SDK для Rust (`helius-laserstream` версии 0.2.0 или новее), SDK для JavaScript/TypeScript (`helius-laserstream` версии 0.4.0 или новее) и клиент Yellowstone для Rust (`yellowstone-grpc-client` версии 13.1.0 или новее). SDK для Go пока не поддерживает эту возможность. См. [таблицу доступности](#доступность) выше.
</Accordion>

<Accordion title="Can I still use explicit pubkey lists?">
  Да. Стандартные фильтры `account: [...]` работают без изменений и остаются оптимальным выбором для небольших множеств аккаунтов — примерно до 10 000. См. [руководство по подписке на аккаунты](/docs/ru/laserstream/guides/account-subscription).
</Accordion>

<Accordion title="Do compressed filters work with matchMints?">
  Да. Когда сжатый фильтр прикреплен к подписке на транзакции и установлен `matchMints: true`, сервер проверяет по фильтру не только ключи аккаунтов транзакции, но и адреса выпуска токенов из балансов до и после транзакции. См. [Фильтрация по адресу выпуска токена](/docs/ru/laserstream/mint-filtering).
</Accordion>

## Связанные материалы

<CardGroup cols={2}>
  <Card title="Account Subscriptions" icon="user" href="/docs/ru/laserstream/guides/account-subscription">
    Стандартная фильтрация аккаунтов с помощью фильтров по владельцу, размеру данных и memcmp.
  </Card>

  <Card title="Clients & SDKs" icon="code" href="/docs/ru/laserstream/clients">
    SDK для TypeScript, Rust и Go с автоматическим повторным воспроизведением и переподключением.
  </Card>

  <Card title="Token Mint Filtering" icon="coins" href="/docs/ru/laserstream/mint-filtering">
    Сопоставление транзакций по адресу выпуска токена с помощью `matchMints`, в том числе внутри сжатых фильтров.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.