Skip to content

Commit 1e10489

Browse files
holimanmeowsbits
andauthored
miner: don't interrupt mining after successful sync (#21701)
* miner: exit loop when downloader Done or Failed Following the logic of the comment at the method, this fixes a regression introduced at 7cf56d6 , which would allow external parties to DoS with blocks, preventing mining progress. Signed-off-by: meows <[email protected]> * miner: remove ineff assign (lint) Signed-off-by: meows <[email protected]> * miner: update test re downloader events Signed-off-by: meows <[email protected]> * Revert "miner: remove ineff assign (lint)" This reverts commit eaefcd3. * Revert "miner: exit loop when downloader Done or Failed" This reverts commit 23abd34. * miner: add test showing imprecise TestMiner Signed-off-by: meows <[email protected]> * miner: fix waitForMiningState precision This helper function would return an affirmation on the first positive match on a desired bool. This was imprecise; it return false positives by not waiting initially for an 'updated' value. This fix causes TestMiner_2 to fail, which is expected. Signed-off-by: meows <[email protected]> * miner: remove TestMiner_2 demonstrating broken test This test demonstrated the imprecision of the test helper function waitForMiningState. This function has been fixed with 6d365c2, and this test test may now be removed. Signed-off-by: meows <[email protected]> * miner: fix test regarding downloader event/mining expectations See comment for logic. Signed-off-by: meows <[email protected]> * miner: add test describing expectations for downloader/mining events We expect that once the downloader emits a DoneEvent, signaling a successful sync, that subsequent StartEvents are not longer permitted to stop the miner. This prevents a security vulnerability where forced syncs via fake high blocks would stall mining operation. Signed-off-by: meows <[email protected]> * miner: use 'canStop' state to fix downloader event handling - Break downloader event handling into event separating Done and Failed events. We need to treat these cases differently since a DoneEvent should prevent the miner from being stopped on subsequent downloader Start events. - Use canStop state to handle the one-off case when a downloader first succeeds. Signed-off-by: meows <[email protected]> * miner: improve comment wording Signed-off-by: meows <[email protected]> * miner: start mining on downloader events iff not already mining Signed-off-by: meows <[email protected]> * miner: refactor miner update logic w/r/t downloader events This makes mining pause/start logic regarding downloader events more explicit. Instead of eternally handling downloader events after the first done event, the subscription is closed when downloader events are no longer actionable. Signed-off-by: meows <[email protected]> * miner: fix handling downloader events on subcription closed Signed-off-by: meows <[email protected]> * miner: (lint:gosimple) use range over chan instead of for/select Signed-off-by: meows <[email protected]> * miner: refactor update loop to remove race condition The go routine handling the downloader events handling vars in parallel with the parent routine, causing a race condition. This change, though ugly, remove the condition while still allowing the downloader event subscription to be closed when the miner has no further use for it (ie DoneEvent). * miner: alternate fix for miner-flaw Co-authored-by: meows <[email protected]>
1 parent 2a9ea6b commit 1e10489

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

miner/miner.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,22 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
8585
// and halt your mining operation for as long as the DOS continues.
8686
func (miner *Miner) update() {
8787
events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
88-
defer events.Unsubscribe()
88+
defer func() {
89+
if !events.Closed() {
90+
events.Unsubscribe()
91+
}
92+
}()
8993

9094
shouldStart := false
9195
canStart := true
96+
dlEventCh := events.Chan()
9297
for {
9398
select {
94-
case ev := <-events.Chan():
99+
case ev := <-dlEventCh:
95100
if ev == nil {
96-
return
101+
// Unsubscription done, stop listening
102+
dlEventCh = nil
103+
continue
97104
}
98105
switch ev.Data.(type) {
99106
case downloader.StartEvent:
@@ -105,12 +112,20 @@ func (miner *Miner) update() {
105112
shouldStart = true
106113
log.Info("Mining aborted due to sync")
107114
}
108-
case downloader.DoneEvent, downloader.FailedEvent:
115+
case downloader.FailedEvent:
116+
canStart = true
117+
if shouldStart {
118+
miner.SetEtherbase(miner.coinbase)
119+
miner.worker.start()
120+
}
121+
case downloader.DoneEvent:
109122
canStart = true
110123
if shouldStart {
111124
miner.SetEtherbase(miner.coinbase)
112125
miner.worker.start()
113126
}
127+
// Stop reacting to downloader events
128+
events.Unsubscribe()
114129
}
115130
case addr := <-miner.startCh:
116131
if canStart {

miner/miner_test.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,75 @@ func TestMiner(t *testing.T) {
8989
// Stop the downloader and wait for the update loop to run
9090
mux.Post(downloader.DoneEvent{})
9191
waitForMiningState(t, miner, true)
92-
// Start the downloader and wait for the update loop to run
92+
93+
// Subsequent downloader events after a successful DoneEvent should not cause the
94+
// miner to start or stop. This prevents a security vulnerability
95+
// that would allow entities to present fake high blocks that would
96+
// stop mining operations by causing a downloader sync
97+
// until it was discovered they were invalid, whereon mining would resume.
98+
mux.Post(downloader.StartEvent{})
99+
waitForMiningState(t, miner, true)
100+
101+
mux.Post(downloader.FailedEvent{})
102+
waitForMiningState(t, miner, true)
103+
}
104+
105+
// TestMinerDownloaderFirstFails tests that mining is only
106+
// permitted to run indefinitely once the downloader sees a DoneEvent (success).
107+
// An initial FailedEvent should allow mining to stop on a subsequent
108+
// downloader StartEvent.
109+
func TestMinerDownloaderFirstFails(t *testing.T) {
110+
miner, mux := createMiner(t)
111+
miner.Start(common.HexToAddress("0x12345"))
112+
waitForMiningState(t, miner, true)
113+
// Start the downloader
93114
mux.Post(downloader.StartEvent{})
94115
waitForMiningState(t, miner, false)
116+
95117
// Stop the downloader and wait for the update loop to run
96118
mux.Post(downloader.FailedEvent{})
97119
waitForMiningState(t, miner, true)
120+
121+
// Since the downloader hasn't yet emitted a successful DoneEvent,
122+
// we expect the miner to stop on next StartEvent.
123+
mux.Post(downloader.StartEvent{})
124+
waitForMiningState(t, miner, false)
125+
126+
// Downloader finally succeeds.
127+
mux.Post(downloader.DoneEvent{})
128+
waitForMiningState(t, miner, true)
129+
130+
// Downloader starts again.
131+
// Since it has achieved a DoneEvent once, we expect miner
132+
// state to be unchanged.
133+
mux.Post(downloader.StartEvent{})
134+
waitForMiningState(t, miner, true)
135+
136+
mux.Post(downloader.FailedEvent{})
137+
waitForMiningState(t, miner, true)
138+
}
139+
140+
func TestMinerStartStopAfterDownloaderEvents(t *testing.T) {
141+
miner, mux := createMiner(t)
142+
143+
miner.Start(common.HexToAddress("0x12345"))
144+
waitForMiningState(t, miner, true)
145+
// Start the downloader
146+
mux.Post(downloader.StartEvent{})
147+
waitForMiningState(t, miner, false)
148+
149+
// Downloader finally succeeds.
150+
mux.Post(downloader.DoneEvent{})
151+
waitForMiningState(t, miner, true)
152+
153+
miner.Stop()
154+
waitForMiningState(t, miner, false)
155+
156+
miner.Start(common.HexToAddress("0x678910"))
157+
waitForMiningState(t, miner, true)
158+
159+
miner.Stop()
160+
waitForMiningState(t, miner, false)
98161
}
99162

100163
func TestStartWhileDownload(t *testing.T) {
@@ -137,10 +200,10 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) {
137200

138201
var state bool
139202
for i := 0; i < 100; i++ {
203+
time.Sleep(10 * time.Millisecond)
140204
if state = m.Mining(); state == mining {
141205
return
142206
}
143-
time.Sleep(10 * time.Millisecond)
144207
}
145208
t.Fatalf("Mining() == %t, want %t", state, mining)
146209
}

0 commit comments

Comments
 (0)