Skip to content

fix(ws): pubsub parity#407

Merged
HealthyBuilder merged 3 commits into
solana-foundation:v2from
sonicfromnewyoke:sonic/ws-pubsub-parity
May 8, 2026
Merged

fix(ws): pubsub parity#407
HealthyBuilder merged 3 commits into
solana-foundation:v2from
sonicfromnewyoke:sonic/ws-pubsub-parity

Conversation

@sonicfromnewyoke
Copy link
Copy Markdown
Contributor

@sonicfromnewyoke sonicfromnewyoke commented Apr 19, 2026

Warning

Breaking Change SHould be merged into 2.0 branch

Problem

Agave's pubsub-client has evolved since this repo's ws package was first written, and the two drifted across several dimensions that made our client either silently wrong or missing features users expect

Summary of Changes

  • actualized "shape" of requests and responses
  • implemented all the supported encodings
  • ported tests from the original crate + added some mainnet fixtures

closes #291
closes #286
closes #208
closes #196

@sonicfromnewyoke sonicfromnewyoke changed the base branch from main to v2 April 23, 2026 10:40
Cross-checked every response and request shape in rpc/ws against
agave/pubsub-client (anza-xyz/agave) and filled the gaps. Method-level
parity was already complete, but option sets, response fields, and the
BlockSubscribe/ParsedBlockSubscribe split had drifted in ways that
produced the "unable to decode client response" error on jsonParsed
block notifications (issue solana-foundation#291) and left several subscriptions without
the options Agave exposes.

BlockSubscribe unification (closes solana-foundation#291)
----------------------------------------
BlockSubscribe now accepts every UiTransactionEncoding the RPC
supports — base58, base64, base64+zstd, jsonParsed — through a single
method, matching Agave's shape. Go can't express
BlockResult.Value is a Go-style union: BlockResultValue carries both
Block (*rpc.GetBlockResult) and ParsedBlock (*rpc.GetParsedBlockResult).
Exactly one is populated per frame, chosen by the Encoding captured at
subscribe time and routed inside decodeBlockNotification(isParsed).

  base58 / base64 / base64+zstd  -> Block
  jsonParsed                     -> ParsedBlock

ParsedBlockSubscribe is marked Deprecated but continues to return
*ParsedBlockResult so nothing breaks for existing callers.

Request / config parity
-----------------------
- AccountSubscribeConfig: new struct mirroring RpcAccountInfoConfig
  (commitment, encoding, dataSlice, minContextSlot). The existing
  AccountSubscribe / AccountSubscribeWithOpts are preserved as thin
  wrappers; AccountSubscribeWithConfig exposes the full surface.
- ProgramSubscribeConfig: new struct mirroring RpcProgramAccountsConfig
  (commitment, encoding, dataSlice, filters, minContextSlot,
  withContext, sortResults).
- SignatureSubscribeConfig: new struct mirroring
  RpcSignatureSubscribeConfig, adds enableReceivedNotification.
- Each Config has an unexported (c *Config).params() helper that
  matches Agave's serde(skip_serializing_if="Option::is_none") behavior.

Response parity
---------------
- SignatureResult.Value now models the untagged
  ProcessedSignature | ReceivedSignature union via
  SignatureResultValue{IsReceived, Processed}. Custom UnmarshalJSON
  handles the object form ({"err":...}), the string form
  "receivedSignature", and null. The old Value.Err path is replaced
  by Value.Processed.Err.
- VoteResult gains VotePubkey and Signature to match RpcVote.
- SlotsUpdatesResult adds Err (Dead variant), documents which fields
  are populated per discriminator.
- All Context fields across the subscription result types moved from
  anonymous struct{Slot uint64} to a shared RPCResponseContext
  (slot + apiVersion), matching RpcResponseContext.

Consumer adaptation
-------------------
sendAndConfirmTransaction.WaitForConfirmation adapted to the new
SignatureResult shape and loops on IsReceived until a processed
notification arrives — previously retried only once, so a second
consecutive received-only frame would have passed a nil Processed
through and wrongly reported (confirmed=true, err=nil).

Examples
--------
- rpc/ws/examples/blockSubscribe: demonstrates the Block/ParsedBlock
  union via a switch on which field is populated.
- rpc/ws/examples/parsedBlockSubscribe: removed (redundant now).

Tests (new subscriptions_test.go + parsedBlockSubscribe_test.go)
----------------------------------------------------------------
Fixture JSON mirrors the wire format produced by Agave's
rpc_subscriptions.rs integration tests:
- accountNotification: base64 and jsonParsed payloads
- programNotification, logsNotification, voteNotification,
  slotNotification, rootNotification
- signatureNotification: processed (null err), processed (with err),
  and the "receivedSignature" string variant
- slotsUpdatesNotification: all seven SlotUpdate variants
- blockNotification: separate ParsedRoute / BinaryRoute tests that
  exercise decodeBlockNotification for both branches; the binary
  fixture includes a real [blob, "base64"] 2-array transaction +
  meta so drift between the two shapes would fail the test
- TestConfigParams_Wire verifies per-field emission and omit behavior
  on every *SubscribeConfig.
@HealthyBuilder HealthyBuilder merged commit b1b7d28 into solana-foundation:v2 May 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants