Skip to content

Commit ed7003d

Browse files
bitwiseguyclaude
andauthored
feat(proxyd): log sendRawTransaction forwarding with tx_hash and duration (#569)
* feat(proxyd): log sendRawTransaction forwarding with tx_hash and duration When enable_tx_hash_logging is enabled, proxyd now emits a "sendRawTransaction forwarded" log after the backend responds. This correlates req_id with tx_hash and includes backend name, backend group, and forwarding duration in milliseconds. This complements the existing "processing sendRawTransaction" log (which fires at request time) by adding a response-time log that measures proxyd's internal processing time — useful for flashblocks latency analysis with op-benchmark. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(proxyd): verify forwarding log fields match request log Parse the JSON log lines to assert that the "sendRawTransaction forwarded" log has the correct tx_hash value and that its req_id matches the corresponding "processing sendRawTransaction" log from the same request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(proxyd): only log sendRawTransaction forwarded on success Move the forwarding log after the error handling block so it only emits when Forward() succeeds. Previously it fired before the err check, producing misleading latency data on failed forwards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 10ba4dd commit ed7003d

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

proxyd/integration_tests/tx_hash_logging_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,38 @@ package integration_tests
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"log/slog"
67
"net/http"
78
"os"
9+
"strings"
810
"testing"
911

1012
"github.com/ethereum-optimism/infra/proxyd"
1113
"github.com/ethereum/go-ethereum/log"
1214
"github.com/stretchr/testify/require"
1315
)
1416

17+
// findJSONLogLine searches JSON log output for a line with the given message
18+
// and returns the parsed fields, or nil if not found.
19+
func findJSONLogLine(t *testing.T, logOutput string, msg string) map[string]any {
20+
t.Helper()
21+
for _, line := range strings.Split(logOutput, "\n") {
22+
line = strings.TrimSpace(line)
23+
if line == "" {
24+
continue
25+
}
26+
var entry map[string]any
27+
if err := json.Unmarshal([]byte(line), &entry); err != nil {
28+
continue
29+
}
30+
if entry["msg"] == msg {
31+
return entry
32+
}
33+
}
34+
return nil
35+
}
36+
1537
func TestTxHashLogging(t *testing.T) {
1638
// Capture log output
1739
var logBuf bytes.Buffer
@@ -49,6 +71,17 @@ func TestTxHashLogging(t *testing.T) {
4971
require.Contains(t, logOutput, "req_id")
5072
require.Contains(t, logOutput, "chain_id")
5173
require.Contains(t, logOutput, "nonce")
74+
75+
// Verify forwarding log has correct tx_hash and matching req_id
76+
recvLog := findJSONLogLine(t, logOutput, "processing sendRawTransaction")
77+
require.NotNil(t, recvLog, "expected processing sendRawTransaction log line")
78+
79+
fwdLog := findJSONLogLine(t, logOutput, "sendRawTransaction forwarded")
80+
require.NotNil(t, fwdLog, "expected sendRawTransaction forwarded log line")
81+
require.Equal(t, "0x0cdd497ce38727606708946cd07b83b101b92a29dea7f090f1f09ae849efd1a3", fwdLog["tx_hash"])
82+
require.Equal(t, recvLog["req_id"], fwdLog["req_id"], "req_id should match between processing and forwarded logs")
83+
require.NotEmpty(t, fwdLog["backend"], "backend should be present")
84+
require.NotNil(t, fwdLog["duration_ms"], "duration_ms should be present")
5285
})
5386

5487
t.Run("batch sendRawTransaction should log tx hash", func(t *testing.T) {
@@ -121,6 +154,7 @@ func TestTxHashLoggingDisabled(t *testing.T) {
121154
// Verify log does NOT contain transaction hash logging
122155
logOutput := logBuf.String()
123156
require.NotContains(t, logOutput, "processing sendRawTransaction")
157+
require.NotContains(t, logOutput, "sendRawTransaction forwarded")
124158
require.NotContains(t, logOutput, "0x0cdd497ce38727606708946cd07b83b101b92a29dea7f090f1f09ae849efd1a3")
125159
})
126160
}

proxyd/server.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL
568568
responses := make([]*RPCRes, len(reqs))
569569
batches := make(map[batchGroup][]batchElem)
570570
ids := make(map[string]int, len(reqs))
571+
txHashes := make(map[int]common.Hash) // tracks tx hashes by request index for forwarding logs
571572

572573
for i := range reqs {
573574
parsedReq, err := ParseRPCReq(reqs[i])
@@ -653,6 +654,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL
653654
responses[i] = NewRPCErrorRes(parsedReq.ID, err)
654655
continue
655656
}
657+
txHashes[i] = tx.Hash()
656658
if err := s.rateLimitSender(ctx, tx, bypassLimit); err != nil {
657659
RecordRPCError(ctx, BackendProxyd, parsedReq.Method, err)
658660
responses[i] = NewRPCErrorRes(parsedReq.ID, err)
@@ -713,8 +715,11 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL
713715
start := i * s.maxUpstreamBatchSize
714716
end := int(math.Min(float64(start+s.maxUpstreamBatchSize), float64(len(cacheMisses))))
715717
elems := cacheMisses[start:end]
718+
forwardStart := time.Now()
716719
res, sb, err := s.BackendGroups[group.backendGroup].Forward(ctx, createBatchRequest(elems), isBatch)
720+
forwardDuration := time.Since(forwardStart)
717721
servedBy[sb] = true
722+
718723
if err != nil {
719724
if errors.Is(err, ErrConsensusGetReceiptsCantBeBatched) ||
720725
errors.Is(err, ErrConsensusGetReceiptsInvalidTarget) {
@@ -733,6 +738,21 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL
733738
}
734739
}
735740

741+
// Log forwarding for sendRawTransaction requests (success path only)
742+
if err == nil && s.enableTxHashLogging {
743+
for _, elem := range elems {
744+
if txHash, ok := txHashes[elem.Index]; ok {
745+
log.Info("sendRawTransaction forwarded",
746+
"tx_hash", txHash,
747+
"req_id", GetReqID(ctx),
748+
"backend", sb,
749+
"backend_group", group.backendGroup,
750+
"duration_ms", forwardDuration.Milliseconds(),
751+
)
752+
}
753+
}
754+
}
755+
736756
for i := range elems {
737757
responses[elems[i].Index] = res[i]
738758

0 commit comments

Comments
 (0)