From a2003f650f30201869acb68d0dc6523b35185e27 Mon Sep 17 00:00:00 2001 From: ozpool Date: Wed, 13 May 2026 12:16:45 +0530 Subject: [PATCH] fix(ws): use spec "showRewards" key in blockSubscribe params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blockSubscribe / parsedBlockSubscribe params object was emitting `rewards` for the rewards-toggle field, but the Solana spec uses `showRewards`. The validator silently ignored the unknown `rewards` key, which meant Rewards=false (or true) had no effect — rewards were always populated. Change both BlockSubscribe and ParsedBlockSubscribe to emit `showRewards`, matching the spec at: https://solana.com/docs/rpc/websocket/blocksubscribe Note: the HTTP getBlock RPC continues to use `rewards` — that name is correct per its own spec, only the websocket variant differs. Closes #208. --- rpc/ws/blockSubscribe.go | 2 +- rpc/ws/blockSubscribe_rewards_test.go | 107 ++++++++++++++++++++++++++ rpc/ws/parsedBlockSubscribe.go | 2 +- 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 rpc/ws/blockSubscribe_rewards_test.go diff --git a/rpc/ws/blockSubscribe.go b/rpc/ws/blockSubscribe.go index 6886a64d0..9ba732a96 100644 --- a/rpc/ws/blockSubscribe.go +++ b/rpc/ws/blockSubscribe.go @@ -117,7 +117,7 @@ func (cl *Client) BlockSubscribe( obj["transactionDetails"] = opts.TransactionDetails } if opts.Rewards != nil { - obj["rewards"] = opts.Rewards + obj["showRewards"] = opts.Rewards } if opts.MaxSupportedTransactionVersion != nil { obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion diff --git a/rpc/ws/blockSubscribe_rewards_test.go b/rpc/ws/blockSubscribe_rewards_test.go new file mode 100644 index 000000000..82c7de489 --- /dev/null +++ b/rpc/ws/blockSubscribe_rewards_test.go @@ -0,0 +1,107 @@ +package ws + +import ( + "strconv" + "testing" + "time" + + "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/require" +) + +// readSubscribeParams reads one subscribe request from the mock server and +// returns the params map of the second element (the options object), responding +// to the client so the subscribe call can return. +func readSubscribeParams(t *testing.T, m *mockWSServer, wsSubID uint64) map[string]any { + t.Helper() + select { + case msg := <-m.incoming: + var req struct { + ID uint64 `json:"id"` + Method string `json:"method"` + Params []any `json:"params"` + } + require.NoError(t, json.Unmarshal(msg, &req)) + resp := `{"jsonrpc":"2.0","result":` + strconv.FormatUint(wsSubID, 10) + `,"id":` + strconv.FormatUint(req.ID, 10) + `}` + m.send(t, resp) + // params[0] is the filter ("all"), params[1] is the opts object + require.GreaterOrEqual(t, len(req.Params), 2, "expected opts object in params[1]: %s", string(msg)) + opts, ok := req.Params[1].(map[string]any) + require.True(t, ok, "expected map opts in params[1], got %T", req.Params[1]) + return opts + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for subscribe request") + return nil + } +} + +// TestBlockSubscribeUsesShowRewardsField pins that BlockSubscribe sends the +// Solana-spec field "showRewards" (not "rewards") in its params object. +// +// See https://solana.com/docs/rpc/websocket/blocksubscribe for the spec, and +// issue #208 for the bug report. +func TestBlockSubscribeUsesShowRewardsField(t *testing.T) { + m := newMockWSServer(t) + defer m.stop() + c := connectClient(t, m) + defer c.Close() + + rewards := false + opts := &BlockSubscribeOpts{ + Commitment: rpc.CommitmentConfirmed, + Rewards: &rewards, + } + + type subResult struct { + err error + } + ch := make(chan subResult, 1) + go func() { + _, err := c.BlockSubscribe(NewBlockSubscribeFilterAll(), opts) + ch <- subResult{err} + }() + + params := readSubscribeParams(t, m, 1) + require.NoError(t, (<-ch).err) + + _, hasOldKey := params["rewards"] + require.False(t, hasOldKey, "params must not include legacy 'rewards' key: %v", params) + + v, ok := params["showRewards"] + require.True(t, ok, "params must include 'showRewards' key: %v", params) + require.Equal(t, false, v) +} + +// TestParsedBlockSubscribeUsesShowRewardsField pins the same fix for the +// parsed-block subscription path. +func TestParsedBlockSubscribeUsesShowRewardsField(t *testing.T) { + m := newMockWSServer(t) + defer m.stop() + c := connectClient(t, m) + defer c.Close() + + rewards := true + opts := &BlockSubscribeOpts{ + Commitment: rpc.CommitmentConfirmed, + Rewards: &rewards, + } + + type subResult struct { + err error + } + ch := make(chan subResult, 1) + go func() { + _, err := c.ParsedBlockSubscribe(NewBlockSubscribeFilterAll(), opts) + ch <- subResult{err} + }() + + params := readSubscribeParams(t, m, 2) + require.NoError(t, (<-ch).err) + + _, hasOldKey := params["rewards"] + require.False(t, hasOldKey, "params must not include legacy 'rewards' key: %v", params) + + v, ok := params["showRewards"] + require.True(t, ok, "params must include 'showRewards' key: %v", params) + require.Equal(t, true, v) +} diff --git a/rpc/ws/parsedBlockSubscribe.go b/rpc/ws/parsedBlockSubscribe.go index e3397bb57..34e5e64ae 100644 --- a/rpc/ws/parsedBlockSubscribe.go +++ b/rpc/ws/parsedBlockSubscribe.go @@ -62,7 +62,7 @@ func (cl *Client) ParsedBlockSubscribe( obj["transactionDetails"] = opts.TransactionDetails } if opts.Rewards != nil { - obj["rewards"] = opts.Rewards + obj["showRewards"] = opts.Rewards } if opts.MaxSupportedTransactionVersion != nil { obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion