Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OBSOLETE] Introduce a chain agnostic generic HEAD type in the core Transaction Manager #8597

Closed
wants to merge 38 commits into from

Conversation

prashantkumar1982
Copy link
Contributor

@prashantkumar1982 prashantkumar1982 commented Mar 1, 2023

This PR is now obsolete in favor of #8674

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2023

I see that you haven't updated any CHANGELOG files. Would it make sense to do so?

@aalu1418 aalu1418 self-requested a review March 1, 2023 15:30
var _ httypes.HeadTrackable = &txmWrapper{}

// EVM specific wrapper to hold the core TxMgr object underneath
type txmWrapper struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning behind introducing this wrapper in this PR? Is it necessary to make Head Tracker generic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The TXM, after the generic HeadTracking changes implements the following trackable:
OnNewLongestChain(ctx context.Context, head txmgrtypes.HeadView)

Earlier, the last parameter was *evmtypes.Head. It was evm specific, but now is generic.
However, the HeadBroadcaster, which calls this method, still expects the evmtypes.Head param. Rather than change the broadcaster, and thus all subscribers across the codebase, I created a txmWrapper, which accepts evmtypes.Head, and then converts that to HeadView, and then calls the Txm's implementation.
Thus an easy hack to make the TXM generic

Copy link
Contributor

Choose a reason for hiding this comment

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

Awesome, thanks for explaining

Copy link
Contributor

Choose a reason for hiding this comment

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

can we call this struct EvmTxm since it will become EVM specific?

and then it would be NewEvmTxManager. we will need to use it to construct the chain-specific gas estimators, etc before passing it to the chain-agnostic TXM

and then the struct would be something like

type EvmTxm struct {
  txmgr.TxManager
  httypes.HeadTrackable
}

func (txm *EvmTxm) OnNewLongestChain(ctx context.Context, evmHead *evmtypes.Head) { ... }

this way you don't need to redefine a bunch of interfaces because the TxManager interface already has them, and all you need is to wrap a function

Copy link
Contributor

@aalu1418 aalu1418 left a comment

Choose a reason for hiding this comment

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

looking good! there's a few changes that would be useful to see and would setup nicely for extracting other components :)

var _ httypes.HeadTrackable = &txmWrapper{}

// EVM specific wrapper to hold the core TxMgr object underneath
type txmWrapper struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we call this struct EvmTxm since it will become EVM specific?

and then it would be NewEvmTxManager. we will need to use it to construct the chain-specific gas estimators, etc before passing it to the chain-agnostic TXM

and then the struct would be something like

type EvmTxm struct {
  txmgr.TxManager
  httypes.HeadTrackable
}

func (txm *EvmTxm) OnNewLongestChain(ctx context.Context, evmHead *evmtypes.Head) { ... }

this way you don't need to redefine a bunch of interfaces because the TxManager interface already has them, and all you need is to wrap a function

common/txmgr/types/head_view.go Outdated Show resolved Hide resolved
@prashantkumar1982 prashantkumar1982 marked this pull request as ready for review March 9, 2023 23:40
//
//go:generate mockery --quiet --name HeadTrackable --output ../mocks/ --case=underscore
type HeadTrackable[HEAD any] interface {
OnNewLongestChain(ctx context.Context, head HeadView[HEAD])
Copy link
Contributor

Choose a reason for hiding this comment

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

WDTY about simplifying this irregular name while we're changing things here?

Suggested change
OnNewLongestChain(ctx context.Context, head HeadView[HEAD])
NewHead(ctx context.Context, head HeadView[HEAD])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is indicating that we found a head which creates a new longest chain, not just any head.
For example, if we had 2 forks, A and B, like this:
A: 4,5,6,7
B:4,5

Now if we see a new head #6 on B fork, it won't trigger this callback. Only trigger on A8, or if we reach B8 before A8.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See this for more context:

if prevHead == nil || head.Number > prevHead.Number {
promCurrentHead.WithLabelValues(ht.chainID.String()).Set(float64(head.Number))
headWithChain := ht.headSaver.Chain(head.Hash)
if headWithChain == nil {
return errors.Errorf("HeadTracker#handleNewHighestHead headWithChain was unexpectedly nil")
}
ht.backfillMB.Deliver(headWithChain)
ht.broadcastMB.Deliver(headWithChain)

Copy link
Contributor

Choose a reason for hiding this comment

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

How about NewHighestHead like in that error message?

Suggested change
OnNewLongestChain(ctx context.Context, head HeadView[HEAD])
NewHighestHead(ctx context.Context, head HeadView[HEAD])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I would prefer that name. However note that OnLongestChain name is used in all places that subscribe to HeadBroadcaster, which is way more places than just TxMgr.
If we want to do that, I would prefer to do a repo wide rename in a dedicated PR.


// GetNativeHead returns the head in the chain's native type
// Chain specific code can retrieve the native type via this function.
GetNativeHead() HEAD
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
GetNativeHead() HEAD
Native() HEAD

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually don't fully grok how this is meant to work. If a caller knows the type is HEAD, then why aren't they just using that directly in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good

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 also not sure about this call. The chain-specific txm implements the interface to comply with the minimum required functionality based on the HEAD type which is known to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The HeadView was a generic interface with a few functions that all chains implement. But this Native() will return the complete Head object in chain's native type. This is being passed to GasEstimators, which need the whole chain specific head object.

Copy link
Contributor

Choose a reason for hiding this comment

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

The HeadView was a generic interface with a few functions that all chains implement. But this Native() will return the complete Head object in chain's native type. This is being passed to GasEstimators, which need the whole chain specific head object.

What problem does this abstract wrapper solve? All the callers still have to import and reference the native type anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Components inside the core Txm don't need to know or import native types.
For example, look at the eth_confirmer.go file.
It uses the methods in the Head interface, without knowing the native type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Components inside the core Txm don't need to know or import native types.

Then they should use a less complex abstraction that does not impose knowing the native type - i.e. only an interface.

It uses the methods in the Head interface, without knowing the native type.

It does know the type. It is leaking everywhere. If the generic type is not being used, then it should be eliminated from that code scope with a stronger abstraction that assumes less.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think there may be another way, which you might prefer.

I create a generic Head interface, similar to the one I currently have here.
Then, the native evmtypes.Head should implement it, and extend it with its own native head fields. Similarly, all chains will implement it, and extend with their own fields.

The txmgr will only use the generic Head interface, and pass it around. Chain-specific code can keep using native Head type.

I didn't do it this way earlier, as I didn't want to change the existing evmtypes.Head, as its being used in a lot of places.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense to me if it can be done.

type EthBroadcaster struct {
//
// The type HEAD represents the chain's native head datatype.
type EthBroadcaster[HEAD any] struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this generic type HEAD used? Is it only to propagate to the estimator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Is there a better way to handle that without using this generic on the EthBroadcaster level?

Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't see one 🤷

Copy link
Contributor

Choose a reason for hiding this comment

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

One stylistic thing that could help limit some of the noise might be to use:

Suggested change
type EthBroadcaster[HEAD any] struct {
type EthBroadcaster[H Head] struct {

With type Head any defined and documented elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Does that help?
"Head any" or "H Head" are equally puzzling for the puzzled, no?

Generics are always hard to read and understand, imo. Can't really solve that i think.

Copy link
Contributor

Choose a reason for hiding this comment

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

It objectively reduces the spam elsewhere through the files, and using single letters is standard, idiomatic Go. We absolutely can and should be working our hardest to make these APIs readable for users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you saying generally, wherever we use generics, instead of saying [XYZ any], we should say [X XYZ]?
I'm not against using idiomatic go, if this is an established best practice.

Copy link
Contributor

Choose a reason for hiding this comment

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

any is perfectly fine to use given the right circumstances. I still don't fully understand what we are trying to capture in our case, but aliasing something in for any was just meant as a way to document your expectations clearly on a type.

Single letter generic types are the only examples I have seen.

ChainLength() uint32

// EarliestInChain traverses through parents until it finds the earliest one
EarliestInChain() HeadView[HEAD]
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this return HEAD type as well? Same is for Parent() down below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No these functions are called from within the EthConfirmer, which doesn't understand the native Head type. It only can understand HeadView.

Copy link
Contributor

Choose a reason for hiding this comment

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

But HeadView does not hide the head type, it just parameterizes it so it can be swapped out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does hide the native type for EthConfirmer right?
The EthConfirmer needs to be generic, without importing any native types. But it also needs to call EarliestInChain().

Copy link
Contributor

Choose a reason for hiding this comment

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

Generic types are not hidden in any way.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interfaces and generics serve two separate purposes, and when they are mixed together like this they undermine one another.

core/chains/evm/chain.go Outdated Show resolved Hide resolved
@cl-sonarqube-production
Copy link

SonarQube Quality Gate

Quality Gate failed

Failed condition 56.9% 56.9% Coverage on New Code (is less than 90%)

See analysis details on SonarQube

@augustbleeds
Copy link
Contributor

Does this PR cover all changes in the ticket?

EarliestInChain() Head[NATIVE]

// Hash is the head's block hash
Hash() common.Hash
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be generic right (especially since this is geth's version of a hash)

@@ -93,7 +96,7 @@ type reset struct {
done chan error
}

type Txm struct {
type Txm[HEAD any] 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 a huge step. first generic introduced into the txm service :)

Copy link
Collaborator

@samsondav samsondav left a comment

Choose a reason for hiding this comment

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

What problem does this solve?


// evmTxm is an evm wrapper over the generic TxManager interface
type evmTxm struct {
httypes.HeadTrackable
Copy link
Contributor

@augustbleeds augustbleeds Mar 10, 2023

Choose a reason for hiding this comment

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

let's use txmgrtypes.Head[*evmtypes.Head] here instead of httypes.HeadTracakable. (just tried locally and it works) so new structs use generic interfaces

Copy link
Contributor

@augustbleeds augustbleeds Mar 10, 2023

Choose a reason for hiding this comment

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

Actually, txmgrtypes.TxManager already requires txmgrtypes.HeadTrackable[HEAD] so I think httypes.HeadTracakable is redundant?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nvm, disregard above. I see what you're trying to accomplish by making the evmTxm compatible with the eth-specific code

Copy link
Contributor

Choose a reason for hiding this comment

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

Any reasons we don't just change functions that expect httypes.HeadTrackable to expect txmgrtypes.Head[*evmtypes.Head]?

@augustbleeds
Copy link
Contributor

What problem does this solve?

It's part of the larger initiative to generalize the Transaction manager to be chain-agnostic so that the bulk of logic needed to integrate with new chains is already written. We have a bunch of tickets to generalize different components, starting with their types first.

@jmank88
Copy link
Contributor

jmank88 commented Mar 10, 2023

What problem does this solve?

It's part of the larger initiative to generalize the Transaction manager to be chain-agnostic so that the bulk of logic needed to integrate with new chains is already written. We have a bunch of tickets to generalize different components, starting with their types first.

But generic types are not the only way to generalize and should not be taken as a maxim. Each case needs to be considered separately, and many cases here look like they could simply use an interface.

@prashantkumar1982 prashantkumar1982 changed the title Introduce a chain agnostic generic HEAD type in the core Transaction Manager [OBSOLETE] Introduce a chain agnostic generic HEAD type in the core Transaction Manager Mar 10, 2023
@github-actions
Copy link
Contributor

This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale label May 21, 2023
@github-actions github-actions bot closed this May 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants