Solana Geyser gRPC - Filters

Solana Stream SDK
Solana Stream SDK распространяется как open-source software. Подробнее см. в репозитории GitHub ниже.

Обзор gRPC Filters

Solana Geyser gRPC использует filters, чтобы эффективно получать только интересующие вас данные: конкретные accounts, programs, transactions, slots и blocks.
Ниже приведены TypeScript-примеры на базе Solana Stream SDK с пояснением конкретной роли каждого filter. При использовании Rust структура и смысл filters полностью совпадают.

Назначение и примеры каждого filter

Подписка на account

Подписка на обновления конкретного account в реальном времени. Следующий пример подписывается на SOL-USDC OpenBook account на уровне commitment Confirmed:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: { slots: {} },
  accounts: {
    'wsol/usdc': {
      account: ['8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.CONFIRMED,
}
  • "wsol/usdc" - это label, задаваемый клиентом.
  • Несколько filters для accounts, programs, blocks и slots можно объединить в одном JSON request.

Подписка на account с account_data_slice

Этот пример показывает, как получать только определенную часть account data. Вместо загрузки всех данных USDC token account (165 bytes) здесь извлекаются 40 bytes, начиная с offset 32. Этот диапазон включает информацию, такую как owner и balance в lamports.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {
    usdc: {
      owner: ['TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'],
      filters: [
        {
          tokenAccountState: true,
        },
        {
          memcmp: {
            offset: 0,
            data: {
              base58: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
            },
          },
        },
      ],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  commitment: CommitmentLevel.CONFIRMED,
  accountsDataSlice: [{ offset: 32, length: 40 }],
}

Подписка на program

Этот пример показывает, как подписаться на обновления accounts, связанных с конкретным program.
Ниже мы подписываемся на обновления accounts, принадлежащих program Solend, на уровне commitment Processed.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {
    solend: {
      owner: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}
  • "solend" - это пользовательский label, который клиент может задать произвольно.
  • Чтобы подписаться сразу на несколько programs, см. следующий раздел "Подписка на несколько programs".

Подписка на несколько programs

Этот пример показывает, как одновременно подписаться на обновления accounts, связанных с несколькими programs.
В примере ниже оформлена подписка на обновления accounts, принадлежащих и Solend, и Serum.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {
    programs: {
      owner: [
        'So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo',
        '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin',
      ],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}
Если вам удобнее назначить отдельный label каждому program, используйте такой подход:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {
    solend: {
      owner: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
    },
    serum: {
      owner: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
    },
  },
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на все finalized non-vote и non-failed transactions

Этот пример показывает подписку на все transactions на уровне commitment Finalized, исключая vote transactions и failed transactions.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {},
  transactions: {
    alltxs: {
      vote: false,
      failed: false,
    },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.FINALIZED,
}
  • vote: false исключает vote transactions.
  • failed: false исключает failed transactions.
  • Если поля оставить пустыми, будут получены все transactions.
  • При указании нескольких полей между ними действует условие AND.

Подписка на non-vote transactions, упоминающие account

Этот пример показывает подписку на transactions, в которых участвует конкретный account, но при этом исключаются vote transactions.
В примере ниже оформлена подписка на non-vote transactions, упоминающие accounts, связанные с program Serum.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {},
  transactions: {
    serum: {
      vote: false,
      accountInclude: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
    },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на transactions с исключением accounts

Этот пример показывает подписку на transactions с исключением тех, которые затрагивают конкретные accounts.
Пример ниже получает transactions, исключая любые accounts, принадлежащие programs Serum и Tokenkeg.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {},
  transactions: {
    serum: {
      accountExclude: [
        '9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin',
        'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
      ],
    },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на transactions, упоминающие accounts, с одновременным исключением отдельных accounts

Этот пример показывает, как подписаться на transactions, затрагивающие определенные accounts, и при этом явно исключить другие указанные accounts.
В примере ниже оформлена подписка на transactions, упоминающие account Serum, но исключается один конкретный account:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    slots: {},
  },
  accounts: {},
  transactions: {
    serum: {
      accountInclude: ['9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin'],
      accountExclude: ['9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT'],
    },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на signature transaction

Этот пример показывает подписку на обновления конкретной transaction signature в реальном времени, пока она не достигнет состояния Confirmed или Finalized.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {},
  transactions: {
    sign: {
      signature:
        '5rp2hL9b6kexex11Mugfs3vfU9GhieKruj4CkFFSnu52WLxiGn4VcLLwsB62XURhMmT1j4CZiXT6FFtYbXsLq2Zs',
    },
  },
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на slots

Этот пример показывает подписку на уведомления о входящих slots. Никакие дополнительные параметры не нужны, кроме пользовательского tag name:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {
    incoming_slots: {},
  },
  accounts: {},
  transactions: {},
  blocks: {},
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на blocks

Подписка на обновления всех генерируемых blocks в реальном времени. По умолчанию будут получены все transactions внутри block:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {},
  transactions: {},
  blocks: {
    blocks: {},
  },
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Чтобы исключить transactions и получать только обновленную информацию об accounts:

typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {},
  transactions: {},
  blocks: {
    blocks: {
      includeTransactions: false,
      includeAccounts: true,
    },
  },
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Чтобы получать только transactions/accounts, затрагивающие конкретные accounts:

typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {},
  transactions: {},
  blocks: {
    blocks: {
      accountInclude: ['So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'],
    },
  },
  blocksMeta: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Подписка на metadata block

Подписка только на уведомления о metadata block по мере обработки blocks. Подробные transaction data не включаются.
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

const request = {
  slots: {},
  accounts: {},
  transactions: {},
  blocks: {},
  blocksMeta: {
    blockmetadata: {},
  },
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
}

Управление уровнями commitment

По умолчанию stream Solana Geyser gRPC использует уровень commitment Processed.
Вы можете указать более высокий уровень commitment, например Confirmed или Finalized. В этих случаях Geyser буферизует данные и отправляет уведомления, когда достигается указанный уровень commitment.
Для максимальной производительности рекомендуется управлять уровнями commitment на стороне клиента.
Вот как задаются уровни commitment:
typescript
import { CommitmentLevel } from '@validators-dao/solana-stream-sdk'

enum CommitmentLevel {
  PROCESSED = 0,
  CONFIRMED = 1,
  FINALIZED = 2,
}
  • PROCESSED: данные сразу после обработки. Получение максимально быстрое, но данные еще не подтверждены.
  • CONFIRMED: данные подтверждены кластером, что дает большую определенность.
  • FINALIZED: полностью finalized data без риска reorganization.

Преимущества работы на уровне Processed

Главное преимущество уровня commitment Processed - немедленное получение transactions, что позволяет очень быстро обрабатывать их на стороне клиента. Затем клиент может отслеживать переход в Confirmed или Finalized, сохраняя высокую отзывчивость.

Как управлять Confirmed и Finalized

При использовании уровней Confirmed или Finalized events (transactions или account updates) следует буферизовать по slot.
Буферизуйте events по slot, подпишитесь на slot notifications и выпускайте events из buffer, когда конкретный slot достигает нужного commitment (Confirmed или Finalized).
Изначально events приходят до того, как slot достигает Confirmed или Finalized.

Особенность Finalized

Из-за особенностей спецификации Solana Geyser не все slots получают явные finalized notifications. Поэтому, когда вы получаете finalized notification для slot, вы должны считать finalized также все ancestor slots, даже если для них не пришло явных notifications.
Иными словами, при получении finalized notification нужно ретроактивно считать finalized все ancestor slots.