github.com/ethereum-optimism/optimism@v1.7.2/op-node/node/safedb/safedb.go (about)

     1  package safedb
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"slices"
    10  	"sync"
    11  
    12  	"github.com/cockroachdb/pebble"
    13  	"github.com/ethereum-optimism/optimism/op-service/eth"
    14  	"github.com/ethereum/go-ethereum/log"
    15  )
    16  
    17  var (
    18  	ErrNotFound     = errors.New("not found")
    19  	ErrInvalidEntry = errors.New("invalid db entry")
    20  )
    21  
    22  const (
    23  	// Keys are prefixed with a constant byte to allow us to differentiate different "columns" within the data
    24  	keyPrefixSafeByL1BlockNum byte = 0
    25  )
    26  
    27  var (
    28  	safeByL1BlockNumKey = uint64Key{prefix: keyPrefixSafeByL1BlockNum}
    29  )
    30  
    31  type uint64Key struct {
    32  	prefix byte
    33  }
    34  
    35  func (c uint64Key) Of(num uint64) []byte {
    36  	key := make([]byte, 0, 9)
    37  	key = append(key, c.prefix)
    38  	key = binary.BigEndian.AppendUint64(key, num)
    39  	return key
    40  }
    41  func (c uint64Key) Max() []byte {
    42  	return c.Of(math.MaxUint64)
    43  }
    44  
    45  func (c uint64Key) IterRange() *pebble.IterOptions {
    46  	return &pebble.IterOptions{
    47  		LowerBound: c.Of(0),
    48  		UpperBound: c.Max(),
    49  	}
    50  }
    51  
    52  type SafeDB struct {
    53  	// m ensures all read iterators are closed before closing the database by preventing concurrent read and write
    54  	// operations (with close considered a write operation).
    55  	m   sync.RWMutex
    56  	log log.Logger
    57  	db  *pebble.DB
    58  
    59  	writeOpts *pebble.WriteOptions
    60  
    61  	closed bool
    62  }
    63  
    64  func safeByL1BlockNumValue(l1 eth.BlockID, l2 eth.BlockID) []byte {
    65  	val := make([]byte, 0, 72)
    66  	val = append(val, l1.Hash.Bytes()...)
    67  	val = append(val, l2.Hash.Bytes()...)
    68  	val = binary.BigEndian.AppendUint64(val, l2.Number)
    69  	return val
    70  }
    71  
    72  func decodeSafeByL1BlockNum(key []byte, val []byte) (l1 eth.BlockID, l2 eth.BlockID, err error) {
    73  	if len(key) != 9 || len(val) != 72 || key[0] != keyPrefixSafeByL1BlockNum {
    74  		err = ErrInvalidEntry
    75  		return
    76  	}
    77  	copy(l1.Hash[:], val[:32])
    78  	l1.Number = binary.BigEndian.Uint64(key[1:])
    79  	copy(l2.Hash[:], val[32:64])
    80  	l2.Number = binary.BigEndian.Uint64(val[64:])
    81  	return
    82  }
    83  
    84  func NewSafeDB(logger log.Logger, path string) (*SafeDB, error) {
    85  	db, err := pebble.Open(path, &pebble.Options{})
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	return &SafeDB{
    90  		log:       logger,
    91  		db:        db,
    92  		writeOpts: &pebble.WriteOptions{Sync: true},
    93  	}, nil
    94  }
    95  
    96  func (d *SafeDB) Enabled() bool {
    97  	return true
    98  }
    99  
   100  func (d *SafeDB) SafeHeadUpdated(safeHead eth.L2BlockRef, l1Head eth.BlockID) error {
   101  	d.m.Lock()
   102  	defer d.m.Unlock()
   103  	d.log.Info("Record safe head", "l2", safeHead.ID(), "l1", l1Head)
   104  	batch := d.db.NewBatch()
   105  	defer batch.Close()
   106  	if err := batch.Set(safeByL1BlockNumKey.Of(l1Head.Number), safeByL1BlockNumValue(l1Head, safeHead.ID()), d.writeOpts); err != nil {
   107  		return fmt.Errorf("failed to record safe head update: %w", err)
   108  	}
   109  	if err := batch.Commit(d.writeOpts); err != nil {
   110  		return fmt.Errorf("failed to commit safe head update: %w", err)
   111  	}
   112  	return nil
   113  }
   114  
   115  func (d *SafeDB) SafeHeadReset(safeHead eth.L2BlockRef) error {
   116  	d.m.Lock()
   117  	defer d.m.Unlock()
   118  	iter, err := d.db.NewIter(safeByL1BlockNumKey.IterRange())
   119  	if err != nil {
   120  		return fmt.Errorf("reset failed to create iterator: %w", err)
   121  	}
   122  	defer iter.Close()
   123  	if valid := iter.SeekGE(safeByL1BlockNumKey.Of(safeHead.L1Origin.Number)); !valid {
   124  		// Reached end of column without finding any entries to delete
   125  		return nil
   126  	}
   127  	for {
   128  		val, err := iter.ValueAndErr()
   129  		if err != nil {
   130  			return fmt.Errorf("reset failed to read entry: %w", err)
   131  		}
   132  		l1Block, l2Block, err := decodeSafeByL1BlockNum(iter.Key(), val)
   133  		if err != nil {
   134  			return fmt.Errorf("reset encountered invalid entry: %w", err)
   135  		}
   136  		if l2Block.Number >= safeHead.Number {
   137  			// Keep a copy of this key - it may be modified when calling Prev()
   138  			l1HeadKey := slices.Clone(iter.Key())
   139  			hasPrevEntry := iter.Prev()
   140  			// Found the first entry that made the new safe head safe.
   141  			batch := d.db.NewBatch()
   142  			if err := batch.DeleteRange(l1HeadKey, safeByL1BlockNumKey.Max(), d.writeOpts); err != nil {
   143  				return fmt.Errorf("reset failed to delete entries after %v: %w", l1HeadKey, err)
   144  			}
   145  
   146  			// If we reset to a safe head before the first entry, we don't know if the new safe head actually became
   147  			// safe in that L1 block or if it was just before our records start, so don't record it as safe at the
   148  			// specified L1 block.
   149  			if hasPrevEntry {
   150  				if err := batch.Set(l1HeadKey, safeByL1BlockNumValue(l1Block, safeHead.ID()), d.writeOpts); err != nil {
   151  					return fmt.Errorf("reset failed to record safe head update: %w", err)
   152  				}
   153  			}
   154  			if err := batch.Commit(d.writeOpts); err != nil {
   155  				return fmt.Errorf("reset failed to commit batch: %w", err)
   156  			}
   157  			return nil
   158  		}
   159  		if valid := iter.Next(); !valid {
   160  			// Reached end of column
   161  			return nil
   162  		}
   163  	}
   164  }
   165  
   166  func (d *SafeDB) SafeHeadAtL1(ctx context.Context, l1BlockNum uint64) (l1Block eth.BlockID, safeHead eth.BlockID, err error) {
   167  	d.m.RLock()
   168  	defer d.m.RUnlock()
   169  	iter, err := d.db.NewIterWithContext(ctx, safeByL1BlockNumKey.IterRange())
   170  	if err != nil {
   171  		return
   172  	}
   173  	defer iter.Close()
   174  	if valid := iter.SeekLT(safeByL1BlockNumKey.Of(l1BlockNum + 1)); !valid {
   175  		err = ErrNotFound
   176  		return
   177  	}
   178  	// Found an entry at or before the requested L1 block
   179  	val, err := iter.ValueAndErr()
   180  	if err != nil {
   181  		return
   182  	}
   183  	l1Block, safeHead, err = decodeSafeByL1BlockNum(iter.Key(), val)
   184  	return
   185  }
   186  
   187  func (d *SafeDB) Close() error {
   188  	d.m.Lock()
   189  	defer d.m.Unlock()
   190  	if d.closed {
   191  		// Already closed
   192  		return nil
   193  	}
   194  	d.closed = true
   195  	return d.db.Close()
   196  }