Skip to content

Commit ada9c77

Browse files
s1nafjl
andauthored
eth, les: update unclean shutdown markers regularly (#24077)
Fixes #22580 Co-authored-by: Felix Lange <[email protected]>
1 parent 3e47e38 commit ada9c77

File tree

4 files changed

+145
-39
lines changed

4 files changed

+145
-39
lines changed

core/rawdb/accessors_metadata.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,28 @@ func PopUncleanShutdownMarker(db ethdb.KeyValueStore) {
139139
}
140140
}
141141

142+
// UpdateUncleanShutdownMarker updates the last marker's timestamp to now.
143+
func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) {
144+
var uncleanShutdowns crashList
145+
// Read old data
146+
if data, err := db.Get(uncleanShutdownKey); err != nil {
147+
log.Warn("Error reading unclean shutdown markers", "error", err)
148+
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
149+
log.Warn("Error decoding unclean shutdown markers", "error", err)
150+
}
151+
// This shouldn't happen because we push a marker on Backend instantiation
152+
count := len(uncleanShutdowns.Recent)
153+
if count == 0 {
154+
log.Warn("No unclean shutdown marker to update")
155+
return
156+
}
157+
uncleanShutdowns.Recent[count-1] = uint64(time.Now().Unix())
158+
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
159+
if err := db.Put(uncleanShutdownKey, data); err != nil {
160+
log.Warn("Failed to write unclean-shutdown marker", "err", err)
161+
}
162+
}
163+
142164
// ReadTransitionStatus retrieves the eth2 transition status from the database
143165
func ReadTransitionStatus(db ethdb.KeyValueReader) []byte {
144166
data, _ := db.Get(transitionStatusKey)

eth/backend.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
"github.com/ethereum/go-ethereum/ethdb"
4848
"github.com/ethereum/go-ethereum/event"
4949
"github.com/ethereum/go-ethereum/internal/ethapi"
50+
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
5051
"github.com/ethereum/go-ethereum/log"
5152
"github.com/ethereum/go-ethereum/miner"
5253
"github.com/ethereum/go-ethereum/node"
@@ -97,6 +98,8 @@ type Ethereum struct {
9798
p2pServer *p2p.Server
9899

99100
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
101+
102+
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
100103
}
101104

102105
// New creates a new Ethereum object (including the
@@ -157,6 +160,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
157160
bloomRequests: make(chan chan *bloombits.Retrieval),
158161
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
159162
p2pServer: stack.Server(),
163+
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
160164
}
161165

162166
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
@@ -262,19 +266,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
262266
stack.RegisterProtocols(eth.Protocols())
263267
stack.RegisterLifecycle(eth)
264268

265-
// Check for unclean shutdown
266-
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
267-
log.Error("Could not update unclean-shutdown-marker list", "error", err)
268-
} else {
269-
if discards > 0 {
270-
log.Warn("Old unclean shutdowns found", "count", discards)
271-
}
272-
for _, tstamp := range uncleanShutdowns {
273-
t := time.Unix(int64(tstamp), 0)
274-
log.Warn("Unclean shutdown detected", "booted", t,
275-
"age", common.PrettyAge(t))
276-
}
277-
}
269+
// Successful startup; push a marker and check previous unclean shutdowns.
270+
eth.shutdownTracker.MarkStartup()
271+
278272
return eth, nil
279273
}
280274

@@ -549,6 +543,9 @@ func (s *Ethereum) Start() error {
549543
// Start the bloom bits servicing goroutines
550544
s.startBloomHandlers(params.BloomBitsBlocks)
551545

546+
// Regularly update shutdown marker
547+
s.shutdownTracker.Start()
548+
552549
// Figure out a max peers count based on the server limits
553550
maxPeers := s.p2pServer.MaxPeers
554551
if s.config.LightServ > 0 {
@@ -577,7 +574,10 @@ func (s *Ethereum) Stop() error {
577574
s.miner.Close()
578575
s.blockchain.Stop()
579576
s.engine.Close()
580-
rawdb.PopUncleanShutdownMarker(s.chainDb)
577+
578+
// Clean shutdown marker as the last thing before closing db
579+
s.shutdownTracker.Stop()
580+
581581
s.chainDb.Close()
582582
s.eventMux.Stop()
583583

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package shutdowncheck
18+
19+
import (
20+
"time"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/core/rawdb"
24+
"github.com/ethereum/go-ethereum/ethdb"
25+
"github.com/ethereum/go-ethereum/log"
26+
)
27+
28+
// ShutdownTracker is a service that reports previous unclean shutdowns
29+
// upon start. It needs to be started after a successful start-up and stopped
30+
// after a successful shutdown, just before the db is closed.
31+
type ShutdownTracker struct {
32+
db ethdb.Database
33+
stopCh chan struct{}
34+
}
35+
36+
// NewShutdownTracker creates a new ShutdownTracker instance and has
37+
// no other side-effect.
38+
func NewShutdownTracker(db ethdb.Database) *ShutdownTracker {
39+
return &ShutdownTracker{
40+
db: db,
41+
stopCh: make(chan struct{}),
42+
}
43+
}
44+
45+
// MarkStartup is to be called in the beginning when the node starts. It will:
46+
// - Push a new startup marker to the db
47+
// - Report previous unclean shutdowns
48+
func (t *ShutdownTracker) MarkStartup() {
49+
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(t.db); err != nil {
50+
log.Error("Could not update unclean-shutdown-marker list", "error", err)
51+
} else {
52+
if discards > 0 {
53+
log.Warn("Old unclean shutdowns found", "count", discards)
54+
}
55+
for _, tstamp := range uncleanShutdowns {
56+
t := time.Unix(int64(tstamp), 0)
57+
log.Warn("Unclean shutdown detected", "booted", t,
58+
"age", common.PrettyAge(t))
59+
}
60+
}
61+
}
62+
63+
// Start runs an event loop that updates the current marker's timestamp every 5 minutes.
64+
func (t *ShutdownTracker) Start() {
65+
go func() {
66+
ticker := time.NewTicker(5 * time.Minute)
67+
defer ticker.Stop()
68+
for {
69+
select {
70+
case <-ticker.C:
71+
rawdb.UpdateUncleanShutdownMarker(t.db)
72+
case <-t.stopCh:
73+
return
74+
}
75+
}
76+
}()
77+
}
78+
79+
// Stop will stop the update loop and clear the current marker.
80+
func (t *ShutdownTracker) Stop() {
81+
// Stop update loop.
82+
t.stopCh <- struct{}{}
83+
// Clear last marker.
84+
rawdb.PopUncleanShutdownMarker(t.db)
85+
}

les/client.go

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/ethereum/go-ethereum/eth/gasprice"
3636
"github.com/ethereum/go-ethereum/event"
3737
"github.com/ethereum/go-ethereum/internal/ethapi"
38+
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
3839
"github.com/ethereum/go-ethereum/les/downloader"
3940
"github.com/ethereum/go-ethereum/les/vflux"
4041
vfc "github.com/ethereum/go-ethereum/les/vflux/client"
@@ -77,6 +78,8 @@ type LightEthereum struct {
7778
p2pServer *p2p.Server
7879
p2pConfig *p2p.Config
7980
udpEnabled bool
81+
82+
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
8083
}
8184

8285
// New creates an instance of the light client.
@@ -107,17 +110,18 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
107110
lesDb: lesDb,
108111
closeCh: make(chan struct{}),
109112
},
110-
peers: peers,
111-
eventMux: stack.EventMux(),
112-
reqDist: newRequestDistributor(peers, &mclock.System{}),
113-
accountManager: stack.AccountManager(),
114-
merger: merger,
115-
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
116-
bloomRequests: make(chan chan *bloombits.Retrieval),
117-
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
118-
p2pServer: stack.Server(),
119-
p2pConfig: &stack.Config().P2P,
120-
udpEnabled: stack.Config().P2P.DiscoveryV5,
113+
peers: peers,
114+
eventMux: stack.EventMux(),
115+
reqDist: newRequestDistributor(peers, &mclock.System{}),
116+
accountManager: stack.AccountManager(),
117+
merger: merger,
118+
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
119+
bloomRequests: make(chan chan *bloombits.Retrieval),
120+
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
121+
p2pServer: stack.Server(),
122+
p2pConfig: &stack.Config().P2P,
123+
udpEnabled: stack.Config().P2P.DiscoveryV5,
124+
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
121125
}
122126

123127
var prenegQuery vfc.QueryFunc
@@ -185,19 +189,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
185189
stack.RegisterProtocols(leth.Protocols())
186190
stack.RegisterLifecycle(leth)
187191

188-
// Check for unclean shutdown
189-
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
190-
log.Error("Could not update unclean-shutdown-marker list", "error", err)
191-
} else {
192-
if discards > 0 {
193-
log.Warn("Old unclean shutdowns found", "count", discards)
194-
}
195-
for _, tstamp := range uncleanShutdowns {
196-
t := time.Unix(int64(tstamp), 0)
197-
log.Warn("Unclean shutdown detected", "booted", t,
198-
"age", common.PrettyAge(t))
199-
}
200-
}
192+
// Successful startup; push a marker and check previous unclean shutdowns.
193+
leth.shutdownTracker.MarkStartup()
194+
201195
return leth, nil
202196
}
203197

@@ -352,6 +346,9 @@ func (s *LightEthereum) Protocols() []p2p.Protocol {
352346
func (s *LightEthereum) Start() error {
353347
log.Warn("Light client mode is an experimental feature")
354348

349+
// Regularly update shutdown marker
350+
s.shutdownTracker.Start()
351+
355352
if s.udpEnabled && s.p2pServer.DiscV5 == nil {
356353
s.udpEnabled = false
357354
log.Error("Discovery v5 is not initialized")
@@ -387,7 +384,9 @@ func (s *LightEthereum) Stop() error {
387384
s.engine.Close()
388385
s.pruner.close()
389386
s.eventMux.Stop()
390-
rawdb.PopUncleanShutdownMarker(s.chainDb)
387+
// Clean shutdown marker as the last thing before closing db
388+
s.shutdownTracker.Stop()
389+
391390
s.chainDb.Close()
392391
s.lesDb.Close()
393392
s.wg.Wait()

0 commit comments

Comments
 (0)