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 at7cf56d6f06
, which would allow external parties to DoS with blocks, preventing mining progress. Signed-off-by: meows <b5c6@protonmail.com> * miner: remove ineff assign (lint) Signed-off-by: meows <b5c6@protonmail.com> * miner: update test re downloader events Signed-off-by: meows <b5c6@protonmail.com> * Revert "miner: remove ineff assign (lint)" This reverts commit eaefcd34ab4862ebc936fb8a07578aa2744bc058. * Revert "miner: exit loop when downloader Done or Failed" This reverts commit 23abd34265aa246c38fc390bb72572ad6ae9fe3b. * miner: add test showing imprecise TestMiner Signed-off-by: meows <b5c6@protonmail.com> * 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 <b5c6@protonmail.com> * miner: remove TestMiner_2 demonstrating broken test This test demonstrated the imprecision of the test helper function waitForMiningState. This function has been fixed with6d365c2851
, and this test test may now be removed. Signed-off-by: meows <b5c6@protonmail.com> * miner: fix test regarding downloader event/mining expectations See comment for logic. Signed-off-by: meows <b5c6@protonmail.com> * 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 <b5c6@protonmail.com> * 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 <b5c6@protonmail.com> * miner: improve comment wording Signed-off-by: meows <b5c6@protonmail.com> * miner: start mining on downloader events iff not already mining Signed-off-by: meows <b5c6@protonmail.com> * 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 <b5c6@protonmail.com> * miner: fix handling downloader events on subcription closed Signed-off-by: meows <b5c6@protonmail.com> * miner: (lint:gosimple) use range over chan instead of for/select Signed-off-by: meows <b5c6@protonmail.com> * 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 <b5c6@protonmail.com>
This commit is contained in:
parent
2a9ea6be87
commit
1e10489196
|
@ -85,15 +85,22 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
|
|||
// and halt your mining operation for as long as the DOS continues.
|
||||
func (miner *Miner) update() {
|
||||
events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
|
||||
defer events.Unsubscribe()
|
||||
defer func() {
|
||||
if !events.Closed() {
|
||||
events.Unsubscribe()
|
||||
}
|
||||
}()
|
||||
|
||||
shouldStart := false
|
||||
canStart := true
|
||||
dlEventCh := events.Chan()
|
||||
for {
|
||||
select {
|
||||
case ev := <-events.Chan():
|
||||
case ev := <-dlEventCh:
|
||||
if ev == nil {
|
||||
return
|
||||
// Unsubscription done, stop listening
|
||||
dlEventCh = nil
|
||||
continue
|
||||
}
|
||||
switch ev.Data.(type) {
|
||||
case downloader.StartEvent:
|
||||
|
@ -105,12 +112,20 @@ func (miner *Miner) update() {
|
|||
shouldStart = true
|
||||
log.Info("Mining aborted due to sync")
|
||||
}
|
||||
case downloader.DoneEvent, downloader.FailedEvent:
|
||||
case downloader.FailedEvent:
|
||||
canStart = true
|
||||
if shouldStart {
|
||||
miner.SetEtherbase(miner.coinbase)
|
||||
miner.worker.start()
|
||||
}
|
||||
case downloader.DoneEvent:
|
||||
canStart = true
|
||||
if shouldStart {
|
||||
miner.SetEtherbase(miner.coinbase)
|
||||
miner.worker.start()
|
||||
}
|
||||
// Stop reacting to downloader events
|
||||
events.Unsubscribe()
|
||||
}
|
||||
case addr := <-miner.startCh:
|
||||
if canStart {
|
||||
|
|
|
@ -89,12 +89,75 @@ func TestMiner(t *testing.T) {
|
|||
// Stop the downloader and wait for the update loop to run
|
||||
mux.Post(downloader.DoneEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
// Start the downloader and wait for the update loop to run
|
||||
|
||||
// Subsequent downloader events after a successful DoneEvent should not cause the
|
||||
// miner to start or stop. This prevents a security vulnerability
|
||||
// that would allow entities to present fake high blocks that would
|
||||
// stop mining operations by causing a downloader sync
|
||||
// until it was discovered they were invalid, whereon mining would resume.
|
||||
mux.Post(downloader.StartEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
mux.Post(downloader.FailedEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
}
|
||||
|
||||
// TestMinerDownloaderFirstFails tests that mining is only
|
||||
// permitted to run indefinitely once the downloader sees a DoneEvent (success).
|
||||
// An initial FailedEvent should allow mining to stop on a subsequent
|
||||
// downloader StartEvent.
|
||||
func TestMinerDownloaderFirstFails(t *testing.T) {
|
||||
miner, mux := createMiner(t)
|
||||
miner.Start(common.HexToAddress("0x12345"))
|
||||
waitForMiningState(t, miner, true)
|
||||
// Start the downloader
|
||||
mux.Post(downloader.StartEvent{})
|
||||
waitForMiningState(t, miner, false)
|
||||
|
||||
// Stop the downloader and wait for the update loop to run
|
||||
mux.Post(downloader.FailedEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
// Since the downloader hasn't yet emitted a successful DoneEvent,
|
||||
// we expect the miner to stop on next StartEvent.
|
||||
mux.Post(downloader.StartEvent{})
|
||||
waitForMiningState(t, miner, false)
|
||||
|
||||
// Downloader finally succeeds.
|
||||
mux.Post(downloader.DoneEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
// Downloader starts again.
|
||||
// Since it has achieved a DoneEvent once, we expect miner
|
||||
// state to be unchanged.
|
||||
mux.Post(downloader.StartEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
mux.Post(downloader.FailedEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
}
|
||||
|
||||
func TestMinerStartStopAfterDownloaderEvents(t *testing.T) {
|
||||
miner, mux := createMiner(t)
|
||||
|
||||
miner.Start(common.HexToAddress("0x12345"))
|
||||
waitForMiningState(t, miner, true)
|
||||
// Start the downloader
|
||||
mux.Post(downloader.StartEvent{})
|
||||
waitForMiningState(t, miner, false)
|
||||
|
||||
// Downloader finally succeeds.
|
||||
mux.Post(downloader.DoneEvent{})
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
miner.Stop()
|
||||
waitForMiningState(t, miner, false)
|
||||
|
||||
miner.Start(common.HexToAddress("0x678910"))
|
||||
waitForMiningState(t, miner, true)
|
||||
|
||||
miner.Stop()
|
||||
waitForMiningState(t, miner, false)
|
||||
}
|
||||
|
||||
func TestStartWhileDownload(t *testing.T) {
|
||||
|
@ -137,10 +200,10 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) {
|
|||
|
||||
var state bool
|
||||
for i := 0; i < 100; i++ {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
if state = m.Mining(); state == mining {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("Mining() == %t, want %t", state, mining)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue