github.com/ethereum-optimism/optimism@v1.7.2/op-node/node/safedb/safedb_test.go (about) 1 package safedb 2 3 import ( 4 "context" 5 "math" 6 "slices" 7 "testing" 8 9 "github.com/ethereum-optimism/optimism/op-service/eth" 10 "github.com/ethereum-optimism/optimism/op-service/testlog" 11 "github.com/ethereum/go-ethereum/common" 12 "github.com/ethereum/go-ethereum/log" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestStoreSafeHeads(t *testing.T) { 17 logger := testlog.Logger(t, log.LvlInfo) 18 dir := t.TempDir() 19 db, err := NewSafeDB(logger, dir) 20 require.NoError(t, err) 21 defer db.Close() 22 l2a := eth.L2BlockRef{ 23 Hash: common.Hash{0x02, 0xaa}, 24 Number: 20, 25 } 26 l2b := eth.L2BlockRef{ 27 Hash: common.Hash{0x02, 0xbb}, 28 Number: 25, 29 } 30 l1a := eth.BlockID{ 31 Hash: common.Hash{0x01, 0xaa}, 32 Number: 100, 33 } 34 l1b := eth.BlockID{ 35 Hash: common.Hash{0x01, 0xbb}, 36 Number: 150, 37 } 38 require.NoError(t, db.SafeHeadUpdated(l2a, l1a)) 39 require.NoError(t, db.SafeHeadUpdated(l2b, l1b)) 40 41 verifySafeHeads := func(db *SafeDB) { 42 _, _, err = db.SafeHeadAtL1(context.Background(), l1a.Number-1) 43 require.ErrorIs(t, err, ErrNotFound) 44 45 actualL1, actualL2, err := db.SafeHeadAtL1(context.Background(), l1a.Number) 46 require.NoError(t, err) 47 require.Equal(t, l1a, actualL1) 48 require.Equal(t, l2a.ID(), actualL2) 49 50 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1a.Number+1) 51 require.NoError(t, err) 52 require.Equal(t, l1a, actualL1) 53 require.Equal(t, l2a.ID(), actualL2) 54 55 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1b.Number) 56 require.NoError(t, err) 57 require.Equal(t, l1b, actualL1) 58 require.Equal(t, l2b.ID(), actualL2) 59 60 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1b.Number+1) 61 require.NoError(t, err) 62 require.Equal(t, l1b, actualL1) 63 require.Equal(t, l2b.ID(), actualL2) 64 } 65 // Verify loading the safe heads with the already open DB 66 verifySafeHeads(db) 67 68 // Close the DB and open a new instance 69 require.NoError(t, db.Close()) 70 newDB, err := NewSafeDB(logger, dir) 71 require.NoError(t, err) 72 // Verify the data is reloaded correctly 73 verifySafeHeads(newDB) 74 } 75 76 func TestSafeHeadAtL1_EmptyDatabase(t *testing.T) { 77 logger := testlog.Logger(t, log.LvlInfo) 78 dir := t.TempDir() 79 db, err := NewSafeDB(logger, dir) 80 require.NoError(t, err) 81 defer db.Close() 82 _, _, err = db.SafeHeadAtL1(context.Background(), 100) 83 require.ErrorIs(t, err, ErrNotFound) 84 } 85 86 func TestTruncateOnSafeHeadReset(t *testing.T) { 87 logger := testlog.Logger(t, log.LvlInfo) 88 dir := t.TempDir() 89 db, err := NewSafeDB(logger, dir) 90 require.NoError(t, err) 91 defer db.Close() 92 93 l2a := eth.L2BlockRef{ 94 Hash: common.Hash{0x02, 0xaa}, 95 Number: 20, 96 L1Origin: eth.BlockID{ 97 Number: 60, 98 }, 99 } 100 l2b := eth.L2BlockRef{ 101 Hash: common.Hash{0x02, 0xbb}, 102 Number: 22, 103 L1Origin: eth.BlockID{ 104 Number: 90, 105 }, 106 } 107 l2c := eth.L2BlockRef{ 108 Hash: common.Hash{0x02, 0xcc}, 109 Number: 25, 110 L1Origin: eth.BlockID{ 111 Number: 110, 112 }, 113 } 114 l2d := eth.L2BlockRef{ 115 Hash: common.Hash{0x02, 0xcc}, 116 Number: 30, 117 L1Origin: eth.BlockID{ 118 Number: 120, 119 }, 120 } 121 l1a := eth.BlockID{ 122 Hash: common.Hash{0x01, 0xaa}, 123 Number: 100, 124 } 125 l1b := eth.BlockID{ 126 Hash: common.Hash{0x01, 0xbb}, 127 Number: 150, 128 } 129 l1c := eth.BlockID{ 130 Hash: common.Hash{0x01, 0xcc}, 131 Number: 160, 132 } 133 134 // Add some entries 135 require.NoError(t, db.SafeHeadUpdated(l2a, l1a)) 136 require.NoError(t, db.SafeHeadUpdated(l2c, l1b)) 137 require.NoError(t, db.SafeHeadUpdated(l2d, l1c)) 138 139 // Then reset to between the two existing entries 140 require.NoError(t, db.SafeHeadReset(l2b)) 141 142 // Only the reset safe head is now safe at the previous L1 block number 143 actualL1, actualL2, err := db.SafeHeadAtL1(context.Background(), l1b.Number) 144 require.NoError(t, err) 145 require.Equal(t, l1b, actualL1) 146 require.Equal(t, l2b.ID(), actualL2) 147 148 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1c.Number) 149 require.NoError(t, err) 150 require.Equal(t, l1b, actualL1) 151 require.Equal(t, l2b.ID(), actualL2) 152 153 // l2a is still safe from its original update 154 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1a.Number) 155 require.NoError(t, err) 156 require.Equal(t, l1a, actualL1) 157 require.Equal(t, l2a.ID(), actualL2) 158 } 159 160 func TestTruncateOnSafeHeadReset_BeforeFirstEntry(t *testing.T) { 161 logger := testlog.Logger(t, log.LvlInfo) 162 dir := t.TempDir() 163 db, err := NewSafeDB(logger, dir) 164 require.NoError(t, err) 165 defer db.Close() 166 167 l2b := eth.L2BlockRef{ 168 Hash: common.Hash{0x02, 0xbb}, 169 Number: 22, 170 L1Origin: eth.BlockID{ 171 Number: 90, 172 }, 173 } 174 l2c := eth.L2BlockRef{ 175 Hash: common.Hash{0x02, 0xcc}, 176 Number: 25, 177 L1Origin: eth.BlockID{ 178 Number: 110, 179 }, 180 } 181 l2d := eth.L2BlockRef{ 182 Hash: common.Hash{0x02, 0xcc}, 183 Number: 30, 184 L1Origin: eth.BlockID{ 185 Number: 120, 186 }, 187 } 188 l1a := eth.BlockID{ 189 Hash: common.Hash{0x01, 0xaa}, 190 Number: 100, 191 } 192 l1b := eth.BlockID{ 193 Hash: common.Hash{0x01, 0xbb}, 194 Number: 150, 195 } 196 l1c := eth.BlockID{ 197 Hash: common.Hash{0x01, 0xcc}, 198 Number: 160, 199 } 200 201 // Add some entries 202 require.NoError(t, db.SafeHeadUpdated(l2c, l1b)) 203 require.NoError(t, db.SafeHeadUpdated(l2d, l1c)) 204 205 // Then reset to between the two existing entries 206 require.NoError(t, db.SafeHeadReset(l2b)) 207 208 // All entries got removed 209 _, _, err = db.SafeHeadAtL1(context.Background(), l1a.Number) 210 require.ErrorIs(t, err, ErrNotFound) 211 _, _, err = db.SafeHeadAtL1(context.Background(), l1b.Number) 212 require.ErrorIs(t, err, ErrNotFound) 213 _, _, err = db.SafeHeadAtL1(context.Background(), l1c.Number) 214 require.ErrorIs(t, err, ErrNotFound) 215 } 216 217 func TestTruncateOnSafeHeadReset_AfterLastEntry(t *testing.T) { 218 logger := testlog.Logger(t, log.LvlInfo) 219 dir := t.TempDir() 220 db, err := NewSafeDB(logger, dir) 221 require.NoError(t, err) 222 defer db.Close() 223 224 l2a := eth.L2BlockRef{ 225 Hash: common.Hash{0x02, 0xaa}, 226 Number: 20, 227 L1Origin: eth.BlockID{ 228 Number: 60, 229 }, 230 } 231 l2b := eth.L2BlockRef{ 232 Hash: common.Hash{0x02, 0xbb}, 233 Number: 22, 234 L1Origin: eth.BlockID{ 235 Number: 90, 236 }, 237 } 238 l2c := eth.L2BlockRef{ 239 Hash: common.Hash{0x02, 0xcc}, 240 Number: 25, 241 L1Origin: eth.BlockID{ 242 Number: 110, 243 }, 244 } 245 l1a := eth.BlockID{ 246 Hash: common.Hash{0x01, 0xaa}, 247 Number: 100, 248 } 249 l1b := eth.BlockID{ 250 Hash: common.Hash{0x01, 0xbb}, 251 Number: 150, 252 } 253 l1c := eth.BlockID{ 254 Hash: common.Hash{0x01, 0xcc}, 255 Number: 160, 256 } 257 258 // Add some entries 259 require.NoError(t, db.SafeHeadUpdated(l2a, l1a)) 260 require.NoError(t, db.SafeHeadUpdated(l2b, l1b)) 261 require.NoError(t, db.SafeHeadUpdated(l2c, l1c)) 262 263 verifySafeHeads := func() { 264 // Everything is still safe 265 actualL1, actualL2, err := db.SafeHeadAtL1(context.Background(), l1a.Number) 266 require.NoError(t, err) 267 require.Equal(t, l1a, actualL1) 268 require.Equal(t, l2a.ID(), actualL2) 269 270 // Everything is still safe 271 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1b.Number) 272 require.NoError(t, err) 273 require.Equal(t, l1b, actualL1) 274 require.Equal(t, l2b.ID(), actualL2) 275 276 // Everything is still safe 277 actualL1, actualL2, err = db.SafeHeadAtL1(context.Background(), l1c.Number) 278 require.NoError(t, err) 279 require.Equal(t, l1c, actualL1) 280 require.Equal(t, l2c.ID(), actualL2) 281 } 282 verifySafeHeads() 283 284 // Then reset to an L2 block after all entries with an origin after all L1 entries 285 require.NoError(t, db.SafeHeadReset(eth.L2BlockRef{ 286 Hash: common.Hash{0x02, 0xdd}, 287 Number: 30, 288 L1Origin: eth.BlockID{ 289 Number: l1c.Number + 1, 290 }, 291 })) 292 verifySafeHeads() 293 294 // Then reset to an L2 block after all entries with an origin before some L1 entries 295 require.NoError(t, db.SafeHeadReset(eth.L2BlockRef{ 296 Hash: common.Hash{0x02, 0xdd}, 297 Number: 30, 298 L1Origin: eth.BlockID{ 299 Number: l1b.Number - 1, 300 }, 301 })) 302 verifySafeHeads() 303 } 304 305 func TestKeysFollowNaturalByteOrdering(t *testing.T) { 306 vals := []uint64{0, 1, math.MaxUint32 - 1, math.MaxUint32, math.MaxUint32 + 1, math.MaxUint64 - 1, math.MaxUint64} 307 for i := 1; i < len(vals); i++ { 308 prev := safeByL1BlockNumKey.Of(vals[i-1]) 309 cur := safeByL1BlockNumKey.Of(vals[i]) 310 require.True(t, slices.Compare(prev, cur) < 0, "Expected %v key %x to be less than %v key %x", vals[i-1], prev, vals[i], cur) 311 } 312 } 313 314 func TestDecodeSafeByL1BlockNum(t *testing.T) { 315 l1 := eth.BlockID{ 316 Hash: common.Hash{0x01}, 317 Number: 84298, 318 } 319 l2 := eth.BlockID{ 320 Hash: common.Hash{0x02}, 321 Number: 3224, 322 } 323 validKey := safeByL1BlockNumKey.Of(l1.Number) 324 validValue := safeByL1BlockNumValue(l1, l2) 325 326 t.Run("Roundtrip", func(t *testing.T) { 327 actualL1, actualL2, err := decodeSafeByL1BlockNum(validKey, validValue) 328 require.NoError(t, err) 329 require.Equal(t, l1, actualL1) 330 require.Equal(t, l2, actualL2) 331 }) 332 333 t.Run("ErrorOnEmptyKey", func(t *testing.T) { 334 _, _, err := decodeSafeByL1BlockNum([]byte{}, validValue) 335 require.ErrorIs(t, err, ErrInvalidEntry) 336 }) 337 338 t.Run("ErrorOnTooShortKey", func(t *testing.T) { 339 _, _, err := decodeSafeByL1BlockNum([]byte{1, 2, 3, 4}, validValue) 340 require.ErrorIs(t, err, ErrInvalidEntry) 341 }) 342 343 t.Run("ErrorOnTooLongKey", func(t *testing.T) { 344 _, _, err := decodeSafeByL1BlockNum(append(validKey, 2), validValue) 345 require.ErrorIs(t, err, ErrInvalidEntry) 346 }) 347 348 t.Run("ErrorOnWrongKeyPrefix", func(t *testing.T) { 349 invalidKey := slices.Clone(validKey) 350 invalidKey[0] = 49 351 _, _, err := decodeSafeByL1BlockNum(invalidKey, validValue) 352 require.ErrorIs(t, err, ErrInvalidEntry) 353 }) 354 355 t.Run("ErrorOnEmptyValue", func(t *testing.T) { 356 _, _, err := decodeSafeByL1BlockNum(validKey, []byte{}) 357 require.ErrorIs(t, err, ErrInvalidEntry) 358 }) 359 360 t.Run("ErrorOnTooShortValue", func(t *testing.T) { 361 _, _, err := decodeSafeByL1BlockNum(validKey, []byte{1, 2, 3, 4}) 362 require.ErrorIs(t, err, ErrInvalidEntry) 363 }) 364 365 t.Run("ErrorOnTooLongValue", func(t *testing.T) { 366 _, _, err := decodeSafeByL1BlockNum(validKey, append(validKey, 2)) 367 require.ErrorIs(t, err, ErrInvalidEntry) 368 }) 369 }