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  }