Skip to content

Commit 4d48980

Browse files
core, eth, les: implement unclean-shutdown marker (#21893)
This PR implements unclean shutdown marker. Every time geth boots, it adds a timestamp to a list of timestamps in the database. This list is capped at 10. At a clean shutdown, the timestamp is removed again. Thus, when geth exits unclean, the marker remains, and at boot up we show the most recent unclean shutdowns to the user, which makes it easier to diagnose root-causes to certain problems. Co-authored-by: Nagy Salem <[email protected]>
1 parent c49aae9 commit 4d48980

File tree

5 files changed

+95
-5
lines changed

5 files changed

+95
-5
lines changed

core/rawdb/accessors_metadata.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package rawdb
1818

1919
import (
2020
"encoding/json"
21+
"time"
2122

2223
"github.com/ethereum/go-ethereum/common"
2324
"github.com/ethereum/go-ethereum/ethdb"
@@ -30,7 +31,7 @@ import (
3031
func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 {
3132
var version uint64
3233

33-
enc, _ := db.Get(databaseVerisionKey)
34+
enc, _ := db.Get(databaseVersionKey)
3435
if len(enc) == 0 {
3536
return nil
3637
}
@@ -47,7 +48,7 @@ func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) {
4748
if err != nil {
4849
log.Crit("Failed to encode database version", "err", err)
4950
}
50-
if err = db.Put(databaseVerisionKey, enc); err != nil {
51+
if err = db.Put(databaseVersionKey, enc); err != nil {
5152
log.Crit("Failed to store the database version", "err", err)
5253
}
5354
}
@@ -79,3 +80,61 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
7980
log.Crit("Failed to store chain config", "err", err)
8081
}
8182
}
83+
84+
// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
85+
// database
86+
type crashList struct {
87+
Discarded uint64 // how many ucs have we deleted
88+
Recent []uint64 // unix timestamps of 10 latest unclean shutdowns
89+
}
90+
91+
const crashesToKeep = 10
92+
93+
// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns
94+
// the previous data
95+
// - a list of timestamps
96+
// - a count of how many old unclean-shutdowns have been discarded
97+
func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) {
98+
var uncleanShutdowns crashList
99+
// Read old data
100+
if data, err := db.Get(uncleanShutdownKey); err != nil {
101+
log.Warn("Error reading unclean shutdown markers", "error", err)
102+
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
103+
return nil, 0, err
104+
}
105+
var discarded = uncleanShutdowns.Discarded
106+
var previous = make([]uint64, len(uncleanShutdowns.Recent))
107+
copy(previous, uncleanShutdowns.Recent)
108+
// Add a new (but cap it)
109+
uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix()))
110+
if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 {
111+
numDel := count - (crashesToKeep + 1)
112+
uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:]
113+
uncleanShutdowns.Discarded += uint64(numDel)
114+
}
115+
// And save it again
116+
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
117+
if err := db.Put(uncleanShutdownKey, data); err != nil {
118+
log.Warn("Failed to write unclean-shutdown marker", "err", err)
119+
return nil, 0, err
120+
}
121+
return previous, discarded, nil
122+
}
123+
124+
// PopUncleanShutdownMarker removes the last unclean shutdown marker
125+
func PopUncleanShutdownMarker(db ethdb.KeyValueStore) {
126+
var uncleanShutdowns crashList
127+
// Read old data
128+
if data, err := db.Get(uncleanShutdownKey); err != nil {
129+
log.Warn("Error reading unclean shutdown markers", "error", err)
130+
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
131+
log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen
132+
}
133+
if l := len(uncleanShutdowns.Recent); l > 0 {
134+
uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1]
135+
}
136+
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
137+
if err := db.Put(uncleanShutdownKey, data); err != nil {
138+
log.Warn("Failed to clear unclean-shutdown marker", "err", err)
139+
}
140+
}

core/rawdb/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error {
355355
bloomTrieNodes.Add(size)
356356
default:
357357
var accounted bool
358-
for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} {
358+
for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} {
359359
if bytes.Equal(key, meta) {
360360
metadata.Add(size)
361361
accounted = true

core/rawdb/schema.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import (
2727

2828
// The fields below define the low level database schema prefixing.
2929
var (
30-
// databaseVerisionKey tracks the current database version.
31-
databaseVerisionKey = []byte("DatabaseVersion")
30+
// databaseVersionKey tracks the current database version.
31+
databaseVersionKey = []byte("DatabaseVersion")
3232

3333
// headHeaderKey tracks the latest known header's hash.
3434
headHeaderKey = []byte("LastHeader")
@@ -81,6 +81,8 @@ var (
8181
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
8282
configPrefix = []byte("ethereum-config-") // config prefix for the db
8383

84+
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
85+
8486
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
8587
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
8688

eth/backend.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"runtime"
2525
"sync"
2626
"sync/atomic"
27+
"time"
2728

2829
"github.com/ethereum/go-ethereum/accounts"
2930
"github.com/ethereum/go-ethereum/common"
@@ -220,6 +221,19 @@ func New(stack *node.Node, config *Config) (*Ethereum, error) {
220221
stack.RegisterAPIs(eth.APIs())
221222
stack.RegisterProtocols(eth.Protocols())
222223
stack.RegisterLifecycle(eth)
224+
// Check for unclean shutdown
225+
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
226+
log.Error("Could not update unclean-shutdown-marker list", "error", err)
227+
} else {
228+
if discards > 0 {
229+
log.Warn("Old unclean shutdowns found", "count", discards)
230+
}
231+
for _, tstamp := range uncleanShutdowns {
232+
t := time.Unix(int64(tstamp), 0)
233+
log.Warn("Unclean shutdown detected", "booted", t,
234+
"age", common.PrettyAge(t))
235+
}
236+
}
223237
return eth, nil
224238
}
225239

@@ -543,6 +557,7 @@ func (s *Ethereum) Stop() error {
543557
s.miner.Stop()
544558
s.blockchain.Stop()
545559
s.engine.Close()
560+
rawdb.PopUncleanShutdownMarker(s.chainDb)
546561
s.chainDb.Close()
547562
s.eventMux.Stop()
548563
return nil

les/client.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,19 @@ func New(stack *node.Node, config *eth.Config) (*LightEthereum, error) {
178178
stack.RegisterProtocols(leth.Protocols())
179179
stack.RegisterLifecycle(leth)
180180

181+
// Check for unclean shutdown
182+
if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
183+
log.Error("Could not update unclean-shutdown-marker list", "error", err)
184+
} else {
185+
if discards > 0 {
186+
log.Warn("Old unclean shutdowns found", "count", discards)
187+
}
188+
for _, tstamp := range uncleanShutdowns {
189+
t := time.Unix(int64(tstamp), 0)
190+
log.Warn("Unclean shutdown detected", "booted", t,
191+
"age", common.PrettyAge(t))
192+
}
193+
}
181194
return leth, nil
182195
}
183196

@@ -313,6 +326,7 @@ func (s *LightEthereum) Stop() error {
313326
s.engine.Close()
314327
s.pruner.close()
315328
s.eventMux.Stop()
329+
rawdb.PopUncleanShutdownMarker(s.chainDb)
316330
s.chainDb.Close()
317331
s.wg.Wait()
318332
log.Info("Light ethereum stopped")

0 commit comments

Comments
 (0)