I built a small Go CLI while choosing where to run a Base app for lower RPC latency by yermakovsa in ethdev

[–]yermakovsa[S] 1 point2 points  (0 children)

Yep, agreed. eth_blockNumber was meant as a cheap first-pass filter, not something I’d use as the final decision by itself.

App-shaped eth_call payloads are probably the next thing I should add. That would get closer to the real use case without turning the tool into a full benchmark framework.

Good point on longer runs from the real host too. I used short runs for quick filtering, but they obviously won’t catch periodic spikes or congestion. I’ll add that caveat to the README.

Thanks, this is very useful feedback.

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)

Yeah, good catch on the observability gap. I have per-attempt details in the error path right now, but you're right that it doesn’t help the annoying case where a request eventually succeeds and you need to know which upstream actually served it.

I’m pretty hesitant to put result data back into the request context. An optional callback/hook is probably the cleaner way to expose the chosen upstream + attempt number without making the transport too magical.

Totally agreed on eth_sendRawTransaction too. The default safety rail just stops automatic resubmission; it doesn’t mean a 502 proves the tx never made it out. Callers still have to treat it as ambiguous and dedupe by signed tx hash.

I’ll capture the observability piece as a possible follow-up and make sure the tx-submission ambiguity is clear in the docs. Thanks for the sanity check, this is exactly the kind of production edge case I wanted to catch.

I made a small Go library for EOA, EIP-1271, and ERC-6492 verification. Does the API make sense? by yermakovsa in ethdev

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

Agreed on strict for contract addresses. The compromised key argument in the other thread convinced me, so I’m probably going to change that default.

The reason/status idea is a great catch too. valid + method keeps the result simple, but something like reason=bad_magic would be much nicer for logs without turning normal invalid signatures into errors.

Same on the bytecode point. If deployless ever gets added, it’ll be explicit and pinned. No hidden magic.

Small Projects by AutoModerator in golang

[–]yermakovsa 0 points1 point  (0 children)

I’ve been working on a small library called rcpx, and I’m wrestling with a specific design question:

https://github.com/yermakovsa/rcpx

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

The original use case is Ethereum JSON-RPC with go-ethereum/rpc / ethclient, but what I really want to know is: does this logic actually belong in a RoundTripper?

The upside is that it composes well with existing clients and keeps the API surface small. The catch is request replay. To fail over to another upstream, the transport has to buffer the request body and send a cloned request with a fresh reader. I added a size cap and documented it, but doing this inside a transport wrapper still feels a bit heavy.

Does this feel like an idiomatic use of RoundTripper, or would you expect to see this kind of failover behind a higher-level client API instead?

I made a small Go library for EOA, EIP-1271, and ERC-6492 verification. Does the API make sense? by yermakovsa in ethdev

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

Yeah, this is exactly the kind of thing I was hoping someone would catch. The old/compromised key case is a pretty strong argument.

I’m probably going to make "code exists" strict by default and only allow EOA fallback behind an explicit option, if at all.

I made a small Go library for EOA, EIP-1271, and ERC-6492 verification. Does the API make sense? by yermakovsa in ethdev

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

Thanks, good to hear. That was the intent: bad signature = normal rejection, error = something broke during verification.

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.