Skip to content

Commit 6bee208

Browse files
authored
rpc fix streaming trace methods returning result:null in case of error (#21922)
Remove stream.WriteNil() from early error paths in traceBlock, TraceTransaction, and traceCallMany. With the resultFieldStream wrapper introduced in #19267, writing nil to the stream before returning an error marks rs.written=true and forces "result":null into the response alongside the error — violating JSON-RPC 2.0 section 5 (result MUST NOT exist when error is present). Add TestTraceErrorPathsWriteNoStream to verify the stream buffer stays empty on early error returns. To fix hive rpc-compact: * debug_traceBlockByHash/trace-genesis * debug_traceBlockByNumber/trace-genesis
1 parent 66197f5 commit 6bee208

3 files changed

Lines changed: 43 additions & 10 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
RPC_VERSION=v2.16.0
1+
RPC_VERSION=v2.17.0
22

rpc/jsonrpc/debug_api_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,48 @@ func TestTraceTransactionNotFound(t *testing.T) {
210210
require.ErrorContains(t, err, "transaction not found")
211211
}
212212

213+
// TestTraceErrorPathsWriteNoStream verifies that streaming trace methods write nothing to the
214+
// stream on early error paths. This is required for JSON-RPC 2.0 compliance: the handler omits
215+
// the "result" field when the stream is untouched, producing {error:...} without result:null.
216+
func TestTraceErrorPathsWriteNoStream(t *testing.T) {
217+
m, _, _ := rpcdaemontest.CreateTestExecModule(t)
218+
api := NewPrivateDebugAPI(newBaseApiForTest(m), m.DB, nil, 0, false)
219+
220+
newStream := func() (*bytes.Buffer, jsonstream.Stream) {
221+
var buf bytes.Buffer
222+
return &buf, jsonstream.New(jsoniter.NewStream(jsoniter.ConfigDefault, &buf, 4096))
223+
}
224+
225+
t.Run("TraceBlockByNumber_genesis", func(t *testing.T) {
226+
buf, s := newStream()
227+
err := api.TraceBlockByNumber(m.Ctx, rpc.BlockNumber(0), &tracersConfig.TraceConfig{}, s)
228+
require.ErrorContains(t, err, "genesis is not traceable")
229+
require.NoError(t, s.Flush())
230+
require.Empty(t, buf.Bytes(), "stream must be empty on early error")
231+
})
232+
233+
t.Run("TraceBlockByHash_genesis", func(t *testing.T) {
234+
var genesisHash common.Hash
235+
require.NoError(t, m.DB.View(m.Ctx, func(tx kv.Tx) error {
236+
genesisHash, _, _ = m.BlockReader.CanonicalHash(m.Ctx, tx, 0)
237+
return nil
238+
}))
239+
buf, s := newStream()
240+
err := api.TraceBlockByHash(m.Ctx, genesisHash, &tracersConfig.TraceConfig{}, s)
241+
require.ErrorContains(t, err, "genesis is not traceable")
242+
require.NoError(t, s.Flush())
243+
require.Empty(t, buf.Bytes(), "stream must be empty on early error")
244+
})
245+
246+
t.Run("TraceTransaction_genesis", func(t *testing.T) {
247+
buf, s := newStream()
248+
err := api.TraceTransaction(m.Ctx, common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), &tracersConfig.TraceConfig{}, s)
249+
require.ErrorContains(t, err, "transaction not found")
250+
require.NoError(t, s.Flush())
251+
require.Empty(t, buf.Bytes(), "stream must be empty on early error")
252+
})
253+
}
254+
213255
func TestTraceTransactionNoRefund(t *testing.T) {
214256
m, _, _ := rpcdaemontest.CreateTestExecModule(t)
215257
api := NewPrivateDebugAPI(newBaseApiForTest(m), m.DB, nil, 0, false)

rpc/jsonrpc/tracing.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ func (api *DebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rpc.Block
6767
}
6868

6969
if (blockNrOrHash.BlockHash == nil && hash == common.Hash{}) {
70-
stream.WriteNil()
7170
return fmt.Errorf("block #%d not found", *blockNrOrHash.BlockNumber)
7271
}
7372

7473
if blockNumber == 0 {
75-
stream.WriteNil()
7674
return fmt.Errorf("genesis is not traceable")
7775
}
7876

@@ -88,7 +86,6 @@ func (api *DebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rpc.Block
8886
return err
8987
}
9088
if block == nil {
91-
stream.WriteNil()
9289
return fmt.Errorf("invalid arguments; block with hash %x not found", hash)
9390
}
9491

@@ -290,7 +287,6 @@ func (api *DebugAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash,
290287
}
291288

292289
if blockNum == 0 {
293-
stream.WriteNil()
294290
return fmt.Errorf("genesis is not traceable")
295291
}
296292

@@ -319,12 +315,10 @@ func (api *DebugAPIImpl) TraceTransaction(ctx context.Context, hash common.Hash,
319315
return err
320316
}
321317
if txNumMin+1 > txNum {
322-
stream.WriteNil()
323318
return fmt.Errorf("uint underflow txnums error txNum: %d, txNumMin: %d, blockNum: %d", txNum, txNumMin, blockNum)
324319
}
325320
txnIndex = int(txNum - txNumMin - 1)
326321
if txnIndex >= block.Transactions().Len() {
327-
stream.WriteNil()
328322
return fmt.Errorf("transaction %#x not found", hash)
329323
}
330324
}
@@ -511,7 +505,6 @@ func (api *DebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, si
511505
return err
512506
}
513507
if len(bundles) == 0 {
514-
stream.WriteNil()
515508
return errors.New("empty bundles")
516509
}
517510
empty := true
@@ -522,7 +515,6 @@ func (api *DebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, si
522515
}
523516

524517
if empty {
525-
stream.WriteNil()
526518
return errors.New("empty bundles")
527519
}
528520

@@ -544,7 +536,6 @@ func (api *DebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bundle, si
544536
return err
545537
}
546538
if header == nil {
547-
stream.WriteNil()
548539
return fmt.Errorf("block %d(%x) not found", blockNum, hash)
549540
}
550541

0 commit comments

Comments
 (0)