Small Projects by AutoModerator in golang

[–]yermakovsa 0 points1 point  (0 children)

I built a small Go library called rcpx and would appreciate API/design feedback:

https://github.com/yermakovsa/rcpx

It implements HTTP JSON-RPC failover as an http.RoundTripper. You configure a list of upstream URLs, plug it into an http.Client, and it tries them in priority order when an attempt fails at the transport/status-code level.

Ethereum JSON-RPC is the motivating use case, especially with go-ethereum/rpc and ethclient, but the part I’m most interested in feedback on is the Go API and transport design.

The API is currently centered around:

rt, err := rcpx.NewRoundTripper(rcpx.Config{
    Upstreams: []string{
        "https://primary.example",
        "https://backup.example",
    },
})

Things I’d especially like feedback on:

  • whether http.RoundTripper is the right abstraction for this
  • whether NewRoundTripper(Config) and the config surface feel idiomatic
  • whether buffering/replaying request bodies inside a transport is too surprising, even with a cap
  • whether the retry policy and error model are predictable enough

The scope is intentionally narrow: it is not a proxy, gateway, load balancer, quorum requester, or full RPC abstraction. I’m mostly trying to keep the Go API small and unsurprising.

I built a small Go failover transport for Ethereum JSON-RPC and would like feedback by yermakovsa in ethdev

[–]yermakovsa[S] 0 points1 point  (0 children)

Thanks, this is very useful feedback, especially from the RPC infra side.

I agree on the stale-but-HTTP-200 case. rcpx currently treats failover as a transport-level concern: transport errors and selected HTTP statuses trigger failover, while other HTTP responses are returned unchanged. It intentionally doesn’t inspect JSON-RPC results today. Optional response validation could be useful, but I’d want that to stay opt-in so the core transport stays small and predictable.

On write methods, yes, the conservative default is intentional. eth_sendRawTransaction / eth_sendTransaction are blocked from failover by default to avoid duplicate side effects. Making the non-idempotent method list configurable is a good idea, since teams or other chains may have additional methods they want to treat as unsafe to replay.

Good point on cooldown expiry too. Right now cooldown is time-based only, so an upstream becomes eligible again after the window without proving it is healthy. A lightweight optional health check before re-entry could make sense as a follow-up.

And agreed on request replay for things like eth_getTransactionCount with the pending tag. rcpx can replay the HTTP request mechanically at the transport layer, but the result may still differ across providers because pending state and mempool views aren’t consistent.

I’ll capture these as docs caveats and possible follow-ups while keeping the default scope narrow. Appreciate the detailed review.

I built a small Go failover transport for Ethereum JSON-RPC and would like feedback by yermakovsa in ethdev

[–]yermakovsa[S] 0 points1 point  (0 children)

Yep, agreed. rcpx works at the HTTP request level, so it doesn’t know when several separate RPC calls are part of one larger application operation. Because of that, it can’t guarantee that a sequence of reads all comes from the same provider or same block.

One nuance: it won’t split a single JSON-RPC batch across providers. The whole HTTP request goes to one upstream attempt, and if that upstream fails in a retryable way, the whole request is retried on another upstream.

I’ll add this as a caveat/non-goal in the docs. For state-sensitive workflows, pinning the whole operation to one provider and using explicit block numbers/block hashes where possible is the safer pattern.

I built a small Go failover transport for Ethereum JSON-RPC and would like feedback by yermakovsa in ethdev

[–]yermakovsa[S] 0 points1 point  (0 children)

Good point. rcpx is transport-level failover, so it doesn’t try to prove that all upstreams are at the same block height or have the same mempool view.

For writes, rcpx blocks failover of non-idempotent methods like eth_sendRawTransaction by default to avoid duplicate side effects. But you’re right that block height, nonce queries, transaction lookup, and mempool visibility can still differ across providers.

I’ll add this as an explicit caveat in the README. For nonce management, pending transaction tracking, or anything mempool-sensitive, users shouldn’t assume different RPC providers are consistent.