Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

ref impl: eth utils - watch head, hash/height id type, RPC source interfaces #125

Merged
merged 2 commits into from
Jan 14, 2022

Conversation

protolambda
Copy link
Collaborator

@protolambda protolambda commented Jan 10, 2022

Part of #119: staging -> main migration

This:

  • Introduces different types of Source interfaces: abstract away the JSON RPC from the ref impl. And more granular than e.g. the ethereum.ChainReader
  • Simple combined-source: to support multiple L1 sources (can replace with smarter load-balancing later on)
  • Wrap a header subscription to provide a channel of HeadSignal updates. Used to track L1 and L2 chain (requires websocket RPC, note that staging does have a fallback)
  • BlockID: combine hash and height, makes the sync implementation more readable

Depends on #124

Review: any team. No specs affected other than defining BlockID. Sources are named based on Go-ethereum method naming.

func (fn BlockLinkByNumberFn) BlockLinkByNumber(ctx context.Context, num uint64) (self BlockID, parent BlockID, err error) {
return fn(ctx, num)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some non-intuitive Go hackery, could we

  • make BlockLinkByNumberFn private?
  • add a comment to the effect that we need to introduce blockLinkByNumberFn so that it can implement BlockLinkByNumber and let us use an anonymous function a an implementation of BlockLinkByNumber?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a common pattern to define a function type that implements single-function interfaces. Interfaces are more flexible to modify and extend, whereas functions make implementing it really easy.

With most of the RPC interfaces and functions I hope we get different implementations later on; more advanced caching, load-balancing and additional test implementations. E.g. the l2 package might use it during testing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the BlockLinkByNumberFn public, updated doc to describe the purpose better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necroing b/c I haven't seen this pattern. Interfaces are really nice when you truly don't care about the implementation (io.Reader or io.Writer), and also when you want a test vs real implementation.

I get splitting up the interface (particularly further down), but I'm curious to see how the type/anonymous function thing works out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://pkg.go.dev/net/http#HandlerFunc is an example of this pattern in the standard library, and you've probably used it somewhere without realizing already, it's everywhere :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw it was being used in another PR and thought maybe it was a common pattern. Does it have a name?
For sure explaining it everywhere is very impractical. If it has a well-known googlable name, e.g. "interaface adapter function type" we could tag uses of the pattern with something like // interface adapter function type.

Or we could just let people figure it out I suppose. We do assume a fair amount of Go understanding.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"anonymous function interface implementation" I guess? You can find it on google that way, not that well documented though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like function to interface adapter or something along those lines. Important to me is to keep adapter and I don't really care for anonymous as the function does not need to be anonymous.

I'm fine leaving this as is, but from looking at and playing with it, my gut feeling that it's a little overkill remains. I think the function adapter pattern is really useful when you have a pure function and it avoids having to create an empty struct and define the pure function over that struct, but once you start to rely on closures, I'd just create a struct to implement it.

opnode/eth/heads.go Outdated Show resolved Hide resolved
opnode/eth/heads.go Outdated Show resolved Hide resolved
opnode/eth/source.go Show resolved Hide resolved
opnode/eth/source.go Outdated Show resolved Hide resolved
type HeadSignalFn func(sig HeadSignal)

// WatchHeadChanges wraps a new-head subscription from NewHeadSource to feed the given Tracker
func WatchHeadChanges(ctx context.Context, src NewHeadSource, fn HeadSignalFn) (ethereum.Subscription, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little less familiar with the internals of the Eth subscription system, but is there a design where the subscription returns the HeadSignal events and we don't need a callback?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like channels better than callbacks too, but we're sending it to an event.Feed to fan out to multiple channels anyway.
See

l1HeadsFeed.Send(sig)


// CombinedL1Source implements round-robin between multiple L1 sources,
// to divide concurrent requests to multiple endpoints
type CombinedL1Source struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that I want to watch closely going forwards. I'm a little worried that L1 nodes are not in sync with each other which could cause issues with figuring out the canonical chain or followup requests about block state.

Base automatically changed from impl-go to main January 14, 2022 20:59
@protolambda protolambda merged commit c6c2708 into main Jan 14, 2022
@protolambda protolambda deleted the impl-eth-utils branch January 14, 2022 21:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants