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 }