github.com/ethereum-optimism/optimism@v1.7.2/op-node/node/config_persistence.go (about) 1 package node 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "os" 9 "path/filepath" 10 "sync" 11 ) 12 13 type RunningState int 14 15 const ( 16 StateUnset RunningState = iota 17 StateStarted 18 StateStopped 19 ) 20 21 type persistedState struct { 22 SequencerStarted *bool `json:"sequencerStarted,omitempty"` 23 } 24 25 type ConfigPersistence interface { 26 SequencerStarted() error 27 SequencerStopped() error 28 SequencerState() (RunningState, error) 29 } 30 31 var _ ConfigPersistence = (*ActiveConfigPersistence)(nil) 32 var _ ConfigPersistence = DisabledConfigPersistence{} 33 34 type ActiveConfigPersistence struct { 35 lock sync.Mutex 36 file string 37 } 38 39 func NewConfigPersistence(file string) *ActiveConfigPersistence { 40 return &ActiveConfigPersistence{file: file} 41 } 42 43 func (p *ActiveConfigPersistence) SequencerStarted() error { 44 return p.persist(true) 45 } 46 47 func (p *ActiveConfigPersistence) SequencerStopped() error { 48 return p.persist(false) 49 } 50 51 // persist writes the new config state to the file as safely as possible. 52 // It uses sync to ensure the data is actually persisted to disk and initially writes to a temp file 53 // before renaming it into place. On UNIX systems this rename is typically atomic, ensuring the 54 // actual file isn't corrupted if IO errors occur during writing. 55 func (p *ActiveConfigPersistence) persist(sequencerStarted bool) error { 56 p.lock.Lock() 57 defer p.lock.Unlock() 58 data, err := json.Marshal(persistedState{SequencerStarted: &sequencerStarted}) 59 if err != nil { 60 return fmt.Errorf("marshall new config: %w", err) 61 } 62 dir := filepath.Dir(p.file) 63 if err := os.MkdirAll(dir, 0755); err != nil { 64 return fmt.Errorf("create config dir (%v): %w", p.file, err) 65 } 66 // Write the new content to a temp file first, then rename into place 67 // Avoids corrupting the content if the disk is full or there are IO errors 68 tmpFile := p.file + ".tmp" 69 file, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 70 if err != nil { 71 return fmt.Errorf("open file (%v) for writing: %w", tmpFile, err) 72 } 73 defer file.Close() // Ensure file is closed even if write or sync fails 74 if _, err = file.Write(data); err != nil { 75 return fmt.Errorf("write new config to temp file (%v): %w", tmpFile, err) 76 } 77 if err := file.Sync(); err != nil { 78 return fmt.Errorf("sync new config temp file (%v): %w", tmpFile, err) 79 } 80 if err := file.Close(); err != nil { 81 return fmt.Errorf("close new config temp file (%v): %w", tmpFile, err) 82 } 83 // Rename to replace the previous file 84 if err := os.Rename(tmpFile, p.file); err != nil { 85 return fmt.Errorf("rename temp config file to final destination: %w", err) 86 } 87 return nil 88 } 89 90 func (p *ActiveConfigPersistence) SequencerState() (RunningState, error) { 91 config, err := p.read() 92 if err != nil { 93 return StateUnset, err 94 } 95 96 if config.SequencerStarted == nil { 97 return StateUnset, nil 98 } else if *config.SequencerStarted { 99 return StateStarted, nil 100 } else { 101 return StateStopped, nil 102 } 103 } 104 105 func (p *ActiveConfigPersistence) read() (persistedState, error) { 106 p.lock.Lock() 107 defer p.lock.Unlock() 108 data, err := os.ReadFile(p.file) 109 if errors.Is(err, os.ErrNotExist) { 110 // persistedState.SequencerStarted == nil: SequencerState() will return StateUnset if no state is found 111 return persistedState{}, nil 112 } else if err != nil { 113 return persistedState{}, fmt.Errorf("read config file (%v): %w", p.file, err) 114 } 115 var config persistedState 116 dec := json.NewDecoder(bytes.NewReader(data)) 117 dec.DisallowUnknownFields() 118 if err = dec.Decode(&config); err != nil { 119 return persistedState{}, fmt.Errorf("invalid config file (%v): %w", p.file, err) 120 } 121 if config.SequencerStarted == nil { 122 return persistedState{}, fmt.Errorf("missing sequencerStarted value in config file (%v)", p.file) 123 } 124 return config, nil 125 } 126 127 // DisabledConfigPersistence provides an implementation of config persistence 128 // that does not persist anything and reports unset for all values 129 type DisabledConfigPersistence struct { 130 } 131 132 func (d DisabledConfigPersistence) SequencerState() (RunningState, error) { 133 return StateUnset, nil 134 } 135 136 func (d DisabledConfigPersistence) SequencerStarted() error { 137 return nil 138 } 139 140 func (d DisabledConfigPersistence) SequencerStopped() error { 141 return nil 142 }