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

htlcswitch: randomize link fee updates #1229

Merged
merged 2 commits into from
Jun 14, 2018

Conversation

wpaulino
Copy link
Contributor

In this PR, we modify the behavior of links updating their commitment fees. Rather than attempting to update the commitment fee for each link every time a new block comes in, we'll use a timer with a random interval between 10 and 60 minutes for each link to determine when to update their corresponding commitment fee. This prevents us from oscillating the fee rate for our various commitment transactions.

We also move the block height dependency from the links in the switch to the switch itself. This is possible due to a recent change on the links no longer depending on the block height to update their commitment fees. We'll now only have the switch be alerted of new blocks coming in and
links will retrieve the height from it atomically.

Fixes #1129.

@wpaulino wpaulino added this to the 0.5 milestone May 10, 2018
// UpdateFeeInterval is the interval at which the update fee timer will
// fire at in order to determine if the link should update its
// commitment fee.
UpdateFeeInterval func() time.Duration
Copy link
Member

Choose a reason for hiding this comment

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

Why does this need to be a method? It can simply be an attribute.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, alternatively, it can be a min/max. Don't have a strong opinion in either direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

}
// For the sake of this test, we'll reset the timer to fire in a second
// so that Alice's link queries for a new network fee.
n.aliceChannelLink.updateFeeTimer.Reset(time.Second)
Copy link
Member

Choose a reason for hiding this comment

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

Why not lower it even further to instantaneously trigger a fee update

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

peer.go Outdated
@@ -438,6 +441,9 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) error {
time.NewTicker(time.Minute)),
BatchSize: 10,
UnsafeReplay: cfg.UnsafeReplay,
UpdateFeeInterval: func() time.Duration {
return time.Duration(prand.Intn(60)+11) * time.Minute
Copy link
Member

Choose a reason for hiding this comment

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

Magic numbers should be lifted up into constants.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

peer.go Outdated
@@ -1416,6 +1422,9 @@ out:
time.NewTicker(time.Minute)),
BatchSize: 10,
UnsafeReplay: cfg.UnsafeReplay,
UpdateFeeInterval: func() time.Duration {
return time.Duration(prand.Intn(60)+11) * time.Minute
Copy link
Member

Choose a reason for hiding this comment

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

Same here w.r.t magic numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -2012,7 +1996,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
continue
}

heightNow := l.bestHeight
heightNow := atomic.LoadUint32(&l.cfg.Switch.bestHeight)
Copy link
Member

Choose a reason for hiding this comment

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

Rather than accessing a private member, the links should access a public method of the switch. This way, the details (the atomic load), don't need to be leaked to the lower layers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make retrieve it using a new method BestHeight() set in the channel link config.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed to use the a BestHeight method, although I'm not sure we need to set it in the config since we already have a reference to the switch itself in there.


// Notifier is an instance of a chain notifier that we'll use to signal
// the switch when a new block has arrived.
Notifier chainntnfs.ChainNotifier
Copy link
Member

Choose a reason for hiding this comment

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

Why pass the full notifier, when we can pass an already instantiated BlockEpoch struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By the time the switch is declared in server.go, the ChainNotifier instance hasn't been initialized yet.


// bestHeight is the best known height of the main chain. The links will
// use this information to govern decisions based on HTLC timeouts.
bestHeight uint32
Copy link
Member

Choose a reason for hiding this comment

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

Comment should note that this must be used atomically.

Copy link
Member

Choose a reason for hiding this comment

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

Also this should be moved to the top of the struct in order to not run into alignment issues with 32-bit systems.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -1712,6 +1744,8 @@ func (s *Switch) Stop() error {
// accessed and modified.
s.mailOrchestrator.Stop()

s.blockEpochs.Cancel()
Copy link
Member

Choose a reason for hiding this comment

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

This should be placed in the main goroutine as a defer instead. As otherwise, it's possible for the switch to exit, but the Stop method to not have been called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

break out
}

atomic.StoreUint32(&s.bestHeight, uint32(blockEpoch.Height))
Copy link
Member

Choose a reason for hiding this comment

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

Carrying over a comment here to recommend a method is added so the link don't need to directly access this attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

@@ -438,6 +449,7 @@ func (l *channelLink) Stop() {
l.cfg.ChainEvents.Cancel()
}

l.updateFeeTimer.Stop()
Copy link
Contributor

Choose a reason for hiding this comment

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

can this just be started and stopped within the htlcManager?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't due to needing to reset the timer withinTestChannelLinkUpdateCommitFee. Otherwise, we'll run into a race where we might update the timer without it being initialized first.

// Make the BatchSize and UpdateFeeInterval large enough to not
// trigger commit updates automatically during tests.
BatchSize: 10000,
UpdateFeeInterval: func() time.Duration { return time.Hour },
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use the BatchTicker pattern to control the ticks instead?

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 BatchTicker seems to be of better use when wanting continuous ticks within the same interval, rather than a different interval after every tick.

@@ -2012,7 +1996,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
continue
}

heightNow := l.bestHeight
heightNow := atomic.LoadUint32(&l.cfg.Switch.bestHeight)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make retrieve it using a new method BestHeight() set in the channel link config.

@wpaulino wpaulino force-pushed the randomize-link-fee-updates branch from 53e7824 to c9a84ae Compare June 1, 2018 04:28
@@ -2074,7 +2064,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
continue
}

heightNow := l.bestHeight
heightNow := l.cfg.Switch.BestHeight()
Copy link
Contributor

Choose a reason for hiding this comment

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

the l.bestHeight can be removed after this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

for {
select {
case blockEpoch, ok := <-s.blockEpochStream.Epochs:
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the reason we are not usingGetBestBlock() performance?

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 believe so. In the Neutrino case, we end up going to disk. This also notifies us as soon as we know about the new block, rather than having to do some kind of long-polling with GetBestBlock.

func (l *channelLink) randomFeeUpdateTimeout() time.Duration {
lower := int64(l.cfg.MinFeeUpdateTimeout)
upper := int64(l.cfg.MaxFeeUpdateTimeout)
rand := prand.Int63n(upper) + lower + 1
Copy link
Member

Choose a reason for hiding this comment

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

This can cause is to accept (in the worst case), a value of lower+upper+1, rather than [lower, upper). Instead we can do something like the following:

lower := int64(l.cfg.MinFeeUpdateTimeout)
upper := int64(l.cfg.MaxFeeUpdateTimeout)
rand := prand.Int63n(upper)
if rand < lower {
   rand = lower
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

In this commit, we modify the behavior of links updating their
commitment fees. Rather than attempting to update the commitment fee for
each link every time a new block comes in, we'll use a timer with a
random interval between 10 and 60 minutes for each link to determine
when to update their corresponding commitment fee. This prevents us from
oscillating the fee rate for our various commitment transactions.
In this commit, we move the block height dependency from the links in
the switch to the switch itself. This is possible due to a recent change
on the links no longer depending on the block height to update their
commitment fees.

We'll now only have the switch be alerted of new blocks coming in and
links will retrieve the height from it atomically.
@Roasbeef Roasbeef merged commit 39f2739 into lightningnetwork:master Jun 14, 2018
@wpaulino wpaulino deleted the randomize-link-fee-updates branch June 14, 2018 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants