From 85a48214086ba3b40f4af43c5c80b6e08881d6da Mon Sep 17 00:00:00 2001 From: 1bcMax Date: Mon, 3 Aug 2026 23:36:20 -0500 Subject: [PATCH] test(solana): pin zero client RPC on a paid GET with a server blockhash 0.19.3 ships the client half of x402#2693: when the 402 carries extra.recentBlockhash, signing skips getLatestBlockhash entirely. Nothing tested that end to end. solana_x402_test.go exercises extra.recentBlockhash at the signing-function level and never goes through a paid GET. paid_get_test.go goes through a paid GET but lets the blockhash come from the RPC fallback. The two files are disjoint, so the combination the release is named after was unguarded: the fast path could regress to a working-but-slow RPC fetch and every test would stay green. Assert the signed transaction carries the server's hash and that both getLatestBlockhash and getAccountInfo see zero calls. Mutation-checked: disabling serverProvidedBlockhash fails this test and only this test. Offline, like the rest of the file. --- paid_get_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/paid_get_test.go b/paid_get_test.go index cae0e50..7d8981f 100644 --- a/paid_get_test.go +++ b/paid_get_test.go @@ -144,3 +144,44 @@ func TestPaidGetWithoutWalletStillRejected(t *testing.T) { }) } } + +// TestPaidGetWithServerBlockhashMakesNoRPC pins the end-to-end claim of the +// 0.19.3 fast path: a paid GET whose 402 carries extra.recentBlockhash signs +// with that hash and makes zero client RPC calls. +// +// Neither existing test covers this. solana_x402_test.go exercises +// extra.recentBlockhash at the signing-function level and never goes through a +// paid GET; the tests above go through a paid GET but let the blockhash come +// from the RPC fallback. Dropping both round trips is the entire point of the +// fast path, so without this the optimisation can silently regress to a +// working-but-slow RPC fetch and every test stays green. +func TestPaidGetWithServerBlockhashMakesNoRPC(t *testing.T) { + resetSolanaBlockhashCacheForTest(t) + counter, rpcSrv := newRPCCounterServer(t, usdcSolanaDecimals) + + serverHash := makeBlockhash(t) + opt := *testPaymentOption(USDCSolanaMainnet) + opt.Extra["recentBlockhash"] = serverHash.String() + srv, sawSignature := paidGetServer(t, opt) + + bc := &baseClient{ + chain: chainSolana, + solanaKey: testSolanaKey(t), + solanaRPCURL: rpcSrv.URL, + apiURL: srv.URL, + httpClient: srv.Client(), + } + + if _, err := bc.doGetWithPayment(context.Background(), "/v1/paid", nil); err != nil { + t.Fatalf("paid GET failed: %v", err) + } + if got := decodePaymentTx(t, *sawSignature).Message.RecentBlockhash; !got.Equals(serverHash) { + t.Errorf("blockhash = %s, want the server-provided %s", got, serverHash) + } + if got := counter.blockhashCalls.Load(); got != 0 { + t.Errorf("getLatestBlockhash calls = %d, want 0 — the server supplied the blockhash", got) + } + if got := counter.mintCalls.Load(); got != 0 { + t.Errorf("getAccountInfo calls = %d, want 0 — USDC mint info is hardcoded", got) + } +}