-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Conversation
I see that you haven't updated any CHANGELOG files. Would it make sense to do so? |
core/chains/evm/txmgr_wrapper.go
Outdated
var _ httypes.HeadTrackable = &txmWrapper{} | ||
|
||
// EVM specific wrapper to hold the core TxMgr object underneath | ||
type txmWrapper struct { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for explaining
There was a problem hiding this comment.
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
There was a problem hiding this 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 :)
core/chains/evm/txmgr_wrapper.go
Outdated
var _ httypes.HeadTrackable = &txmWrapper{} | ||
|
||
// EVM specific wrapper to hold the core TxMgr object underneath | ||
type txmWrapper struct { |
There was a problem hiding this comment.
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_trackable.go
Outdated
// | ||
//go:generate mockery --quiet --name HeadTrackable --output ../mocks/ --case=underscore | ||
type HeadTrackable[HEAD any] interface { | ||
OnNewLongestChain(ctx context.Context, head HeadView[HEAD]) |
There was a problem hiding this comment.
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?
OnNewLongestChain(ctx context.Context, head HeadView[HEAD]) | |
NewHead(ctx context.Context, head HeadView[HEAD]) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
chainlink/core/chains/evm/headtracker/head_tracker.go
Lines 203 to 211 in e7527e6
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) |
There was a problem hiding this comment.
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?
OnNewLongestChain(ctx context.Context, head HeadView[HEAD]) | |
NewHighestHead(ctx context.Context, head HeadView[HEAD]) |
There was a problem hiding this comment.
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.
common/txmgr/types/head_view.go
Outdated
|
||
// GetNativeHead returns the head in the chain's native type | ||
// Chain specific code can retrieve the native type via this function. | ||
GetNativeHead() HEAD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetNativeHead() HEAD | |
Native() HEAD |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🤷
There was a problem hiding this comment.
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:
type EthBroadcaster[HEAD any] struct { | |
type EthBroadcaster[H Head] struct { |
With type Head any
defined and documented elsewhere.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
common/txmgr/types/head_view.go
Outdated
ChainLength() uint32 | ||
|
||
// EarliestInChain traverses through parents until it finds the earliest one | ||
EarliestInChain() HeadView[HEAD] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Does this PR cover all changes in the ticket? |
EarliestInChain() Head[NATIVE] | ||
|
||
// Hash is the head's block hash | ||
Hash() common.Hash |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 :)
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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]
?
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. |
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. |
This PR is now obsolete in favor of #8674