db_test.go 43.5 KB
Newer Older
1 2 3
package logs

import (
4
	"encoding/binary"
5 6 7 8 9 10
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"testing"

11 12 13 14 15 16
	"github.com/stretchr/testify/require"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/log"

17 18 19
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"github.com/ethereum-optimism/optimism/op-service/testlog"
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/db/entrydb"
20
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
21 22 23
)

func createHash(i int) common.Hash {
24 25 26 27 28 29 30
	if i == -1 { // parent-hash of genesis is zero
		return common.Hash{}
	}
	var data [9]byte
	data[0] = 0xff
	binary.BigEndian.PutUint64(data[1:], uint64(i))
	return crypto.Keccak256Hash(data[:])
31 32 33 34
}

func TestErrorOpeningDatabase(t *testing.T) {
	dir := t.TempDir()
35
	_, err := NewFromFile(testlog.Logger(t, log.LvlInfo), &stubMetrics{}, filepath.Join(dir, "missing-dir", "file.db"), false)
36 37 38 39 40
	require.ErrorIs(t, err, os.ErrNotExist)
}

func runDBTest(t *testing.T, setup func(t *testing.T, db *DB, m *stubMetrics), assert func(t *testing.T, db *DB, m *stubMetrics)) {
	createDb := func(t *testing.T, dir string) (*DB, *stubMetrics, string) {
41
		logger := testlog.Logger(t, log.LvlTrace)
42 43
		path := filepath.Join(dir, "test.db")
		m := &stubMetrics{}
44
		db, err := NewFromFile(logger, m, path, false)
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
		require.NoError(t, err, "Failed to create database")
		t.Cleanup(func() {
			err := db.Close()
			if err != nil {
				require.ErrorIs(t, err, fs.ErrClosed)
			}
		})
		return db, m, path
	}

	t.Run("New", func(t *testing.T) {
		db, m, _ := createDb(t, t.TempDir())
		setup(t, db, m)
		assert(t, db, m)
	})

	t.Run("Existing", func(t *testing.T) {
		dir := t.TempDir()
		db, m, path := createDb(t, dir)
		setup(t, db, m)
		// Close and recreate the database
		require.NoError(t, db.Close())
		checkDBInvariants(t, path, m)

		db2, m, path := createDb(t, dir)
		assert(t, db2, m)
		checkDBInvariants(t, path, m)
	})
}

func TestEmptyDbDoesNotFindEntry(t *testing.T) {
	runDBTest(t,
		func(t *testing.T, db *DB, m *stubMetrics) {},
		func(t *testing.T, db *DB, m *stubMetrics) {
79 80
			requireFuture(t, db, 1, 0, createHash(1))
			requireFuture(t, db, 1, 0, common.Hash{})
81 82 83
		})
}

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
func TestLatestSealedBlockNum(t *testing.T) {
	t.Run("Empty case", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {},
			func(t *testing.T, db *DB, m *stubMetrics) {
				n, ok := db.LatestSealedBlockNum()
				require.False(t, ok, "empty db expected")
				require.Zero(t, n)
				idx, err := db.searchCheckpoint(0, 0)
				require.ErrorIs(t, err, ErrFuture, "no checkpoint in empty db")
				require.Zero(t, idx)
			})
	})
	t.Run("Zero case", func(t *testing.T) {
		genesis := eth.BlockID{Hash: createHash(0), Number: 0}
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
				require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
				n, ok := db.LatestSealedBlockNum()
				require.True(t, ok, "genesis block expected")
				require.Equal(t, genesis.Number, n)
				idx, err := db.searchCheckpoint(0, 0)
				require.NoError(t, err)
				require.Zero(t, idx, "genesis block as checkpoint 0")
			})
	})
	t.Run("Later genesis case", func(t *testing.T) {
		genesis := eth.BlockID{Hash: createHash(10), Number: 10}
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
				require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
				n, ok := db.LatestSealedBlockNum()
				require.True(t, ok, "genesis block expected")
				require.Equal(t, genesis.Number, n)
				idx, err := db.searchCheckpoint(genesis.Number, 0)
				require.NoError(t, err)
				require.Zero(t, idx, "anchor block as checkpoint 0")
				_, err = db.searchCheckpoint(0, 0)
				require.ErrorIs(t, err, ErrSkipped, "no checkpoint before genesis")
			})
	})
	t.Run("Block 1 case", func(t *testing.T) {
		genesis := eth.BlockID{Hash: createHash(0), Number: 0}
		block1 := eth.BlockID{Hash: createHash(1), Number: 1}
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
				require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
				require.NoError(t, db.SealBlock(genesis.Hash, block1, 5001), "seal block 1")
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
				n, ok := db.LatestSealedBlockNum()
				require.True(t, ok, "block 1 expected")
				require.Equal(t, block1.Number, n)
				idx, err := db.searchCheckpoint(block1.Number, 0)
				require.NoError(t, err)
				require.Equal(t, entrydb.EntryIdx(0), idx, "checkpoint 0 still for block 1")
			})
	})
	t.Run("Using checkpoint case", func(t *testing.T) {
		genesis := eth.BlockID{Hash: createHash(0), Number: 0}
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
				require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
				for i := 1; i <= 260; i++ {
					id := eth.BlockID{Hash: createHash(i), Number: uint64(i)}
					require.NoError(t, db.SealBlock(createHash(i-1), id, 5001), "seal block %d", i)
				}
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
				n, ok := db.LatestSealedBlockNum()
				require.True(t, ok, "latest block expected")
				expected := uint64(260)
				require.Equal(t, expected, n)
				idx, err := db.searchCheckpoint(expected, 0)
				require.NoError(t, err)
				// It costs 2 entries per block, so if we add more than 1 checkpoint worth of blocks,
				// then we get to checkpoint 2
				require.Equal(t, entrydb.EntryIdx(searchCheckpointFrequency*2), idx, "checkpoint 1 reached")
			})
	})
}

170 171 172 173 174 175
func TestAddLog(t *testing.T) {
	t.Run("BlockZero", func(t *testing.T) {
		// There are no logs in the genesis block so recording an entry for block 0 should be rejected.
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {},
			func(t *testing.T, db *DB, m *stubMetrics) {
176
				genesis := eth.BlockID{Hash: createHash(15), Number: 0}
177
				err := db.AddLog(createHash(1), genesis, 0, nil)
178 179 180 181
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

182
	t.Run("FirstEntries", func(t *testing.T) {
183 184
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
185 186
				genesis := eth.BlockID{Hash: createHash(15), Number: 15}
				require.NoError(t, db.SealBlock(common.Hash{}, genesis, 5000), "seal genesis")
187
				err := db.AddLog(createHash(1), genesis, 0, nil)
188 189
				require.NoError(t, err, "first log after genesis")
				require.NoError(t, db.SealBlock(genesis.Hash, eth.BlockID{Hash: createHash(16), Number: 16}, 5001))
190 191
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
192
				requireContains(t, db, 16, 0, createHash(1))
193 194 195 196 197 198
			})
	})

	t.Run("MultipleEntriesFromSameBlock", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
199 200 201 202 203 204 205
				// create 15 empty blocks
				for i := 0; i <= 15; i++ {
					bl := eth.BlockID{Hash: createHash(i), Number: uint64(i)}
					require.NoError(t, db.SealBlock(createHash(i-1), bl, 5000+uint64(i)), "seal blocks")
				}
				// Now apply 3 logs on top of that, contents for block 16
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
206
				err := db.AddLog(createHash(1), bl15, 0, nil)
207
				require.NoError(t, err)
208
				err = db.AddLog(createHash(2), bl15, 1, nil)
209
				require.NoError(t, err)
210
				err = db.AddLog(createHash(3), bl15, 2, nil)
211
				require.NoError(t, err)
212 213 214
				// Now seal block 16
				bl16 := eth.BlockID{Hash: createHash(16), Number: 16}
				err = db.SealBlock(bl15.Hash, bl16, 5016)
215 216 217
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
218 219 220 221
				require.EqualValues(t, 16*2+3+2, m.entryCount, "empty blocks have logs")
				requireContains(t, db, 16, 0, createHash(1))
				requireContains(t, db, 16, 1, createHash(2))
				requireContains(t, db, 16, 2, createHash(3))
222 223 224 225 226 227
			})
	})

	t.Run("MultipleEntriesFromMultipleBlocks", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
228 229
				bl14 := eth.BlockID{Hash: createHash(14), Number: 14}
				err := db.SealBlock(createHash(13), bl14, 5000)
230
				require.NoError(t, err)
231 232
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err = db.SealBlock(createHash(14), bl15, 5001)
233
				require.NoError(t, err)
234
				err = db.AddLog(createHash(1), bl15, 0, nil)
235
				require.NoError(t, err)
236
				err = db.AddLog(createHash(2), bl15, 1, nil)
237 238 239 240
				require.NoError(t, err)
				bl16 := eth.BlockID{Hash: createHash(16), Number: 16}
				err = db.SealBlock(bl15.Hash, bl16, 5003)
				require.NoError(t, err)
241
				err = db.AddLog(createHash(3), bl16, 0, nil)
242
				require.NoError(t, err)
243
				err = db.AddLog(createHash(4), bl16, 1, nil)
244 245 246
				require.NoError(t, err)
				bl17 := eth.BlockID{Hash: createHash(17), Number: 17}
				err = db.SealBlock(bl16.Hash, bl17, 5003)
247 248 249
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
250 251 252 253 254
				require.EqualValues(t, 2+2+1+1+2+1+1+2, m.entryCount, "should not output new searchCheckpoint for every block")
				requireContains(t, db, 16, 0, createHash(1))
				requireContains(t, db, 16, 1, createHash(2))
				requireContains(t, db, 17, 0, createHash(3))
				requireContains(t, db, 17, 1, createHash(4))
255 256 257 258 259 260
			})
	})

	t.Run("ErrorWhenBeforeCurrentBlock", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
261 262
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.SealBlock(common.Hash{}, bl15, 5001)
263 264 265
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
266 267 268
				bl14 := eth.BlockID{Hash: createHash(14), Number: 14}
				err := db.SealBlock(createHash(13), bl14, 5000)
				require.ErrorIs(t, err, ErrConflict)
269 270 271 272 273 274
			})
	})

	t.Run("ErrorWhenBeforeCurrentBlockButAfterLastCheckpoint", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
275
				err := db.lastEntryContext.forceBlock(eth.BlockID{Hash: createHash(13), Number: 13}, 5000)
276
				require.NoError(t, err)
277 278 279
				err = db.SealBlock(createHash(13), eth.BlockID{Hash: createHash(14), Number: 14}, 5001)
				require.NoError(t, err)
				err = db.SealBlock(createHash(14), eth.BlockID{Hash: createHash(15), Number: 15}, 5002)
280 281 282
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
283
				onto := eth.BlockID{Hash: createHash(14), Number: 14}
284
				err := db.AddLog(createHash(1), onto, 0, nil)
285
				require.ErrorIs(t, err, ErrLogOutOfOrder, "cannot build logs on 14 when 15 is already sealed")
286 287 288 289 290 291
			})
	})

	t.Run("ErrorWhenBeforeCurrentLogEvent", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
292 293 294
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
				require.NoError(t, err)
295 296
				require.NoError(t, db.AddLog(createHash(1), bl15, 0, nil))
				require.NoError(t, db.AddLog(createHash(1), bl15, 1, nil))
297 298
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
299
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
300
				err := db.AddLog(createHash(1), bl15, 0, nil)
301
				require.ErrorIs(t, err, ErrLogOutOfOrder, "already at log index 2")
302 303 304
			})
	})

305
	t.Run("ErrorWhenBeforeBlockSeal", func(t *testing.T) {
306 307
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
308 309
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
310
				require.NoError(t, err)
311 312
				require.NoError(t, db.AddLog(createHash(1), bl15, 0, nil))
				require.NoError(t, db.AddLog(createHash(1), bl15, 1, nil))
313 314
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
315
				err := db.AddLog(createHash(1), eth.BlockID{Hash: createHash(16), Number: 16}, 0, nil)
316 317 318 319 320 321 322
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

	t.Run("ErrorWhenAtCurrentLogEvent", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
323 324 325
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
				require.NoError(t, err)
326 327
				require.NoError(t, db.AddLog(createHash(1), bl15, 0, nil))
				require.NoError(t, db.AddLog(createHash(1), bl15, 1, nil))
328 329
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
330
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
331
				err := db.AddLog(createHash(1), bl15, 1, nil)
332
				require.ErrorIs(t, err, ErrLogOutOfOrder, "already at log index 2")
333 334 335 336 337 338
			})
	})

	t.Run("ErrorWhenAtCurrentLogEventButAfterLastCheckpoint", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
339 340 341
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
				require.NoError(t, err)
342 343
				require.NoError(t, db.AddLog(createHash(1), bl15, 0, nil))
				require.NoError(t, db.AddLog(createHash(1), bl15, 1, nil))
344 345
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
346
				bl15 := eth.BlockID{Hash: createHash(16), Number: 16}
347
				err := db.AddLog(createHash(1), bl15, 2, nil)
348 349 350 351 352 353 354
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

	t.Run("ErrorWhenSkippingLogEvent", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
355 356
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
357
				require.NoError(t, err)
358
				require.NoError(t, db.AddLog(createHash(1), bl15, 0, nil))
359 360
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
361
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
362
				err := db.AddLog(createHash(1), bl15, 2, nil)
363 364 365 366 367
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

	t.Run("ErrorWhenFirstLogIsNotLogIdxZero", func(t *testing.T) {
368 369 370 371 372
		runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {
			bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
			err := db.lastEntryContext.forceBlock(bl15, 5000)
			require.NoError(t, err)
		},
373
			func(t *testing.T, db *DB, m *stubMetrics) {
374
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
375
				err := db.AddLog(createHash(1), bl15, 5, nil)
376 377 378 379 380 381 382
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

	t.Run("ErrorWhenFirstLogOfNewBlockIsNotLogIdxZero", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
383 384 385
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				err := db.lastEntryContext.forceBlock(bl15, 5000)
				require.NoError(t, err)
386
				err = db.AddLog(createHash(1), bl15, 0, nil)
387
				require.NoError(t, err)
388 389
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
390
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
391
				err := db.AddLog(createHash(1), bl15, 1, nil)
392 393 394 395
				require.NoError(t, err)
				bl16 := eth.BlockID{Hash: createHash(16), Number: 16}
				err = db.SealBlock(bl15.Hash, bl16, 5001)
				require.NoError(t, err)
396
				err = db.AddLog(createHash(1), bl16, 1, nil)
397 398 399 400 401
				require.ErrorIs(t, err, ErrLogOutOfOrder)
			})
	})

	t.Run("MultipleSearchCheckpoints", func(t *testing.T) {
402
		block0 := eth.BlockID{Hash: createHash(10), Number: 10}
403 404
		block1 := eth.BlockID{Hash: createHash(11), Number: 11}
		block2 := eth.BlockID{Hash: createHash(12), Number: 12}
405 406 407 408 409
		block3 := eth.BlockID{Hash: createHash(13), Number: 13}
		block4 := eth.BlockID{Hash: createHash(14), Number: 14}
		// Ignoring seal-checkpoints in checkpoint counting comments here;
		// First search-checkpoint is at entry idx 0
		// Block 1 logs don't reach the second search-checkpoint
410
		block1LogCount := searchCheckpointFrequency - 10
411 412 413 414
		// Block 2 logs extend to just after the third search-checkpoint
		block2LogCount := searchCheckpointFrequency + 16
		// Block 3 logs extend to immediately before the fourth search-checkpoint
		block3LogCount := searchCheckpointFrequency - 19
415 416 417
		block4LogCount := 2
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
418 419 420 421 422 423 424
				// force in block 0
				require.NoError(t, db.lastEntryContext.forceBlock(block0, 3000))
				expectedIndex := entrydb.EntryIdx(2)
				t.Logf("block 0 complete, at entry %d", db.lastEntryContext.NextIndex())
				require.Equal(t, expectedIndex, db.lastEntryContext.NextIndex())
				{ // create block 1
					for i := 0; i < block1LogCount; i++ {
425
						err := db.AddLog(createHash(i), block0, uint32(i), nil)
426 427 428 429
						require.NoError(t, err)
					}
					err := db.SealBlock(block0.Hash, block1, 3001) // second seal-checkpoint
					require.NoError(t, err)
430
				}
431 432 433 434 435 436
				expectedIndex += entrydb.EntryIdx(block1LogCount) + 2
				t.Logf("block 1 complete, at entry %d", db.lastEntryContext.NextIndex())
				require.Equal(t, expectedIndex, db.lastEntryContext.NextIndex(), "added logs and a seal checkpoint")
				{ // create block 2
					for i := 0; i < block2LogCount; i++ {
						// two of these imply a search checkpoint, the second and third search-checkpoint
437
						err := db.AddLog(createHash(i), block1, uint32(i), nil)
438 439 440 441
						require.NoError(t, err)
					}
					err := db.SealBlock(block1.Hash, block2, 3002) // third seal-checkpoint
					require.NoError(t, err)
442
				}
443 444 445 446 447
				expectedIndex += entrydb.EntryIdx(block2LogCount) + 2 + 2 + 2
				t.Logf("block 2 complete, at entry %d", db.lastEntryContext.NextIndex())
				require.Equal(t, expectedIndex, db.lastEntryContext.NextIndex(), "added logs, two search checkpoints, and a seal checkpoint")
				{ // create block 3
					for i := 0; i < block3LogCount; i++ {
448
						err := db.AddLog(createHash(i), block2, uint32(i), nil)
449 450 451 452
						require.NoError(t, err)
					}
					err := db.SealBlock(block2.Hash, block3, 3003)
					require.NoError(t, err)
453
				}
454 455 456 457 458
				expectedIndex += entrydb.EntryIdx(block3LogCount) + 2
				t.Logf("block 3 complete, at entry %d", db.lastEntryContext.NextIndex())
				require.Equal(t, expectedIndex, db.lastEntryContext.NextIndex(), "added logs, and a seal checkpoint")

				// Verify that we're right before the fourth search-checkpoint will be written.
459 460
				// entryCount is the number of entries, so given 0 based indexing is the index of the next entry
				// the first checkpoint is at entry 0, the second at entry searchCheckpointFrequency etc
461 462 463 464 465
				// so the fourth is at entry 3*searchCheckpointFrequency.
				require.EqualValues(t, 3*searchCheckpointFrequency-1, m.entryCount)
				{ // create block 4
					for i := 0; i < block4LogCount; i++ {
						// includes a fourth search checkpoint
466
						err := db.AddLog(createHash(i), block3, uint32(i), nil)
467 468 469 470
						require.NoError(t, err)
					}
					err := db.SealBlock(block3.Hash, block4, 3003) // fourth seal checkpoint
					require.NoError(t, err)
471
				}
472 473 474
				expectedIndex += entrydb.EntryIdx(block4LogCount) + 2 + 2
				require.Equal(t, expectedIndex, db.lastEntryContext.NextIndex(), "added logs, a search checkpoint, and a seal checkpoint")
				t.Logf("block 4 complete, at entry %d", db.lastEntryContext.NextIndex())
475 476
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
477 478
				// Check that we wrote additional search checkpoints and seal checkpoints
				expectedCheckpointCount := 4 + 4
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
				expectedEntryCount := block1LogCount + block2LogCount + block3LogCount + block4LogCount + (2 * expectedCheckpointCount)
				require.EqualValues(t, expectedEntryCount, m.entryCount)
				// Check we can find all the logs.
				for i := 0; i < block1LogCount; i++ {
					requireContains(t, db, block1.Number, uint32(i), createHash(i))
				}
				// Block 2 logs extend to just after the third checkpoint
				for i := 0; i < block2LogCount; i++ {
					requireContains(t, db, block2.Number, uint32(i), createHash(i))
				}
				// Block 3 logs extend to immediately before the fourth checkpoint
				for i := 0; i < block3LogCount; i++ {
					requireContains(t, db, block3.Number, uint32(i), createHash(i))
				}
				// Block 4 logs start immediately after the fourth checkpoint
				for i := 0; i < block4LogCount; i++ {
					requireContains(t, db, block4.Number, uint32(i), createHash(i))
				}
			})
	})
}

func TestAddDependentLog(t *testing.T) {
	execMsg := types.ExecutingMessage{
		Chain:     3,
		BlockNum:  42894,
		LogIdx:    42,
		Timestamp: 8742482,
507
		Hash:      createHash(8844),
508 509 510 511
	}
	t.Run("FirstEntry", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
512 513
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				require.NoError(t, db.lastEntryContext.forceBlock(bl15, 5000))
514
				err := db.AddLog(createHash(1), bl15, 0, &execMsg)
515 516 517
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
518
				requireContains(t, db, 16, 0, createHash(1), execMsg)
519 520 521
			})
	})

522
	t.Run("BlockSealSearchCheckpointOverlap", func(t *testing.T) {
523 524
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
525 526
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				require.NoError(t, db.lastEntryContext.forceBlock(bl15, 5000))
527
				for i := uint32(0); m.entryCount < searchCheckpointFrequency-1; i++ {
528
					require.NoError(t, db.AddLog(createHash(9), bl15, i, nil))
529
				}
530 531 532 533
				bl16 := eth.BlockID{Hash: createHash(16), Number: 16}
				require.NoError(t, db.SealBlock(bl15.Hash, bl16, 5001))
				// added 3 entries: seal-checkpoint, then a search-checkpoint, then the canonical hash
				require.Equal(t, m.entryCount, int64(searchCheckpointFrequency+2))
534
				err := db.AddLog(createHash(1), bl16, 0, &execMsg)
535 536 537
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
538 539
				requireContains(t, db, 16, 0, createHash(9))
				requireContains(t, db, 17, 0, createHash(1), execMsg)
540 541 542
			})
	})

543
	t.Run("AvoidCheckpointOverlapWithExecutingCheck", func(t *testing.T) {
544 545
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
546 547 548 549
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				require.NoError(t, db.lastEntryContext.forceBlock(bl15, 5000))
				// we add 256 - 2 (start) - 2 (init msg, exec link) = 252 entries
				for i := uint32(0); i < 252; i++ {
550
					require.NoError(t, db.AddLog(createHash(9), bl15, i, nil))
551
				}
552
				// add an executing message
553
				err := db.AddLog(createHash(1), bl15, 252, &execMsg)
554
				require.NoError(t, err)
555 556 557 558 559 560 561 562 563 564 565
				// 0,1: start
				// 2..252+2: initiating logs without exec message
				// 254 = inferred padding - 3 entries for exec msg would overlap with checkpoint
				// 255 = inferred padding
				// 256 = search checkpoint - what would be the exec check without padding
				// 257 = canonical hash
				// 258 = initiating message
				// 259 = executing message link
				// 260 = executing message check
				require.Equal(t, int64(261), m.entryCount)
				db.debugTip()
566 567
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
568 569
				requireContains(t, db, 16, 251, createHash(9))
				requireContains(t, db, 16, 252, createHash(1), execMsg)
570 571 572
			})
	})

573
	t.Run("AvoidCheckpointOverlapWithExecutingLink", func(t *testing.T) {
574 575
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
576 577 578 579
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				require.NoError(t, db.lastEntryContext.forceBlock(bl15, 5000))
				// we add 256 - 2 (start) - 1 (init msg) = 253 entries
				for i := uint32(0); i < 253; i++ {
580
					require.NoError(t, db.AddLog(createHash(9), bl15, i, nil))
581
				}
582
				// add an executing message
583
				err := db.AddLog(createHash(1), bl15, 253, &execMsg)
584
				require.NoError(t, err)
585 586 587 588 589 590 591 592 593 594
				// 0,1: start
				// 2..253+2: initiating logs without exec message
				// 255 = inferred padding - 3 entries for exec msg would overlap with checkpoint
				// 256 = search checkpoint - what would be the exec link without padding
				// 257 = canonical hash
				// 258 = initiating message
				// 259 = executing message link
				// 260 = executing message check
				db.debugTip()
				require.Equal(t, int64(261), m.entryCount)
595 596
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
597 598
				requireContains(t, db, 16, 252, createHash(9))
				requireContains(t, db, 16, 253, createHash(1), execMsg)
599 600 601 602 603 604 605
			})
	})
}

func TestContains(t *testing.T) {
	runDBTest(t,
		func(t *testing.T, db *DB, m *stubMetrics) {
606 607
			bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
			require.NoError(t, db.lastEntryContext.forceBlock(bl50, 5000))
608 609 610
			require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
			require.NoError(t, db.AddLog(createHash(3), bl50, 1, nil))
			require.NoError(t, db.AddLog(createHash(2), bl50, 2, nil))
611 612 613 614
			bl51 := eth.BlockID{Hash: createHash(51), Number: 51}
			require.NoError(t, db.SealBlock(bl50.Hash, bl51, 5001))
			bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
			require.NoError(t, db.SealBlock(bl51.Hash, bl52, 5001))
615 616
			require.NoError(t, db.AddLog(createHash(1), bl52, 0, nil))
			require.NoError(t, db.AddLog(createHash(3), bl52, 1, nil))
617 618 619
		},
		func(t *testing.T, db *DB, m *stubMetrics) {
			// Should find added logs
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
			requireContains(t, db, 51, 0, createHash(1))
			requireContains(t, db, 51, 1, createHash(3))
			requireContains(t, db, 51, 2, createHash(2))
			requireContains(t, db, 53, 0, createHash(1))
			requireContains(t, db, 53, 1, createHash(3))

			// 52 was sealed as empty
			requireConflicts(t, db, 52, 0, createHash(1))

			// 53 only contained 2 logs, not 3, and is not sealed yet
			requireFuture(t, db, 53, 2, createHash(3))
			// 54 doesn't exist yet
			requireFuture(t, db, 54, 0, createHash(3))

			// 51 only contained 3 logs, not 4
			requireConflicts(t, db, 51, 3, createHash(2))
636 637 638 639 640 641 642 643 644
		})
}

func TestExecutes(t *testing.T) {
	execMsg1 := types.ExecutingMessage{
		Chain:     33,
		BlockNum:  22,
		LogIdx:    99,
		Timestamp: 948294,
645
		Hash:      createHash(332299),
646 647 648 649 650 651
	}
	execMsg2 := types.ExecutingMessage{
		Chain:     44,
		BlockNum:  55,
		LogIdx:    66,
		Timestamp: 77777,
652
		Hash:      createHash(445566),
653 654 655 656 657 658
	}
	execMsg3 := types.ExecutingMessage{
		Chain:     77,
		BlockNum:  88,
		LogIdx:    89,
		Timestamp: 6578567,
659
		Hash:      createHash(778889),
660 661 662
	}
	runDBTest(t,
		func(t *testing.T, db *DB, m *stubMetrics) {
663 664
			bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
			require.NoError(t, db.lastEntryContext.forceBlock(bl50, 500))
665 666 667
			require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
			require.NoError(t, db.AddLog(createHash(3), bl50, 1, &execMsg1))
			require.NoError(t, db.AddLog(createHash(2), bl50, 2, nil))
668 669 670 671
			bl51 := eth.BlockID{Hash: createHash(51), Number: 51}
			require.NoError(t, db.SealBlock(bl50.Hash, bl51, 5001))
			bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
			require.NoError(t, db.SealBlock(bl51.Hash, bl52, 5001))
672 673
			require.NoError(t, db.AddLog(createHash(1), bl52, 0, &execMsg2))
			require.NoError(t, db.AddLog(createHash(3), bl52, 1, &execMsg3))
674 675 676
		},
		func(t *testing.T, db *DB, m *stubMetrics) {
			// Should find added logs
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
			requireExecutingMessage(t, db, 51, 0, types.ExecutingMessage{})
			requireExecutingMessage(t, db, 51, 1, execMsg1)
			requireExecutingMessage(t, db, 51, 2, types.ExecutingMessage{})
			requireExecutingMessage(t, db, 53, 0, execMsg2)
			requireExecutingMessage(t, db, 53, 1, execMsg3)

			// 52 was sealed without logs
			requireConflicts(t, db, 52, 0, createHash(1))

			// 53 only contained 2 logs, not 3, and is not sealed yet
			requireFuture(t, db, 53, 2, createHash(3))
			// 54 doesn't exist yet
			requireFuture(t, db, 54, 0, createHash(3))

			// 51 only contained 3 logs, not 4
			requireConflicts(t, db, 51, 3, createHash(2))
693 694 695 696
		})
}

func TestGetBlockInfo(t *testing.T) {
697
	t.Run("ReturnsErrFutureWhenEmpty", func(t *testing.T) {
698 699 700
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {},
			func(t *testing.T, db *DB, m *stubMetrics) {
701 702 703
				bl10 := eth.BlockID{Hash: createHash(10), Number: 10}
				_, err := db.FindSealedBlock(bl10)
				require.ErrorIs(t, err, ErrFuture)
704 705 706
			})
	})

707
	t.Run("ReturnsErrFutureWhenRequestedBlockBeforeFirstSearchCheckpoint", func(t *testing.T) {
708 709
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
710 711
				bl11 := eth.BlockID{Hash: createHash(11), Number: 11}
				require.NoError(t, db.lastEntryContext.forceBlock(bl11, 500))
712
				err := db.AddLog(createHash(1), bl11, 0, nil)
713 714 715
				require.NoError(t, err)
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
716 717 718 719
				// if the DB starts at 11, then shouldn't find 10
				bl10 := eth.BlockID{Hash: createHash(10), Number: 10}
				_, err := db.FindSealedBlock(bl10)
				require.ErrorIs(t, err, ErrSkipped)
720 721 722 723 724 725 726
			})
	})

	t.Run("ReturnFirstBlockInfo", func(t *testing.T) {
		block := eth.BlockID{Hash: createHash(11), Number: 11}
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
727
				require.NoError(t, db.SealBlock(common.Hash{}, block, 500))
728 729
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
730 731 732 733
				index, err := db.FindSealedBlock(block)
				require.NoError(t, err)
				require.Equal(t, entrydb.EntryIdx(2), index,
					"expecting to continue after search checkpoint that declared the block")
734 735 736 737 738 739 740 741
			})
	})
}

func requireContains(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash, execMsg ...types.ExecutingMessage) {
	require.LessOrEqual(t, len(execMsg), 1, "cannot have multiple executing messages for a single log")
	m, ok := db.m.(*stubMetrics)
	require.True(t, ok, "Did not get the expected metrics type")
742
	_, err := db.Contains(blockNum, logIdx, logHash)
743
	require.NoErrorf(t, err, "Error searching for log %v in block %v", logIdx, blockNum)
744
	require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints")
745 746 747 748 749 750 751 752 753
	require.NotZero(t, m.entriesReadForSearch, "Must read at least some entries to find the log")

	var expectedExecMsg types.ExecutingMessage
	if len(execMsg) == 1 {
		expectedExecMsg = execMsg[0]
	}
	requireExecutingMessage(t, db, blockNum, logIdx, expectedExecMsg)
}

754
func requireConflicts(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash) {
755 756
	m, ok := db.m.(*stubMetrics)
	require.True(t, ok, "Did not get the expected metrics type")
757
	_, err := db.Contains(blockNum, logIdx, logHash)
758 759
	require.ErrorIs(t, err, ErrConflict, "canonical chain must not include this log")
	require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints")
760 761
}

762
func requireFuture(t *testing.T, db *DB, blockNum uint64, logIdx uint32, logHash common.Hash) {
763 764
	m, ok := db.m.(*stubMetrics)
	require.True(t, ok, "Did not get the expected metrics type")
765
	_, err := db.Contains(blockNum, logIdx, logHash)
766 767
	require.ErrorIs(t, err, ErrFuture, "canonical chain does not yet include this log")
	require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints")
768 769
}

770
func requireExecutingMessage(t *testing.T, db *DB, blockNum uint64, logIdx uint32, execMsg types.ExecutingMessage) {
771 772
	m, ok := db.m.(*stubMetrics)
	require.True(t, ok, "Did not get the expected metrics type")
773
	_, iter, err := db.findLogInfo(blockNum, logIdx)
774
	require.NoError(t, err, "Error when searching for executing message")
775 776 777 778 779 780 781 782 783
	actualExecMsg := iter.ExecMessage() // non-nil if not just an initiating message, but also an executing message
	if execMsg == (types.ExecutingMessage{}) {
		require.Nil(t, actualExecMsg)
	} else {
		require.NotNil(t, actualExecMsg)
		require.Equal(t, execMsg, *actualExecMsg, "Should return matching executing message")
	}
	require.LessOrEqual(t, m.entriesReadForSearch, int64(searchCheckpointFrequency*2), "Should not need to read more than between two checkpoints")
	require.NotZero(t, m.entriesReadForSearch, "Must read at least some entries to find the log")
784 785 786 787 788 789
}

func TestRecoverOnCreate(t *testing.T) {
	createDb := func(t *testing.T, store *stubEntryStore) (*DB, *stubMetrics, error) {
		logger := testlog.Logger(t, log.LvlInfo)
		m := &stubMetrics{}
790
		db, err := NewFromEntryStore(logger, m, store, true)
791 792 793
		return db, m, err
	}

794 795 796 797
	storeWithEvents := func(evts ...entrydb.Entry) *stubEntryStore {
		store := &stubEntryStore{}
		store.entries = append(store.entries, evts...)
		return store
798
	}
799 800 801 802
	t.Run("NoTruncateWhenLastEntryIsLogWithNoExecMessageSealed", func(t *testing.T) {
		store := storeWithEvents(
			// seal 0, 1, 2, 3
			newSearchCheckpoint(0, 0, 100).encode(),
803
			newCanonicalHash(createHash(300)).encode(),
804
			newSearchCheckpoint(1, 0, 101).encode(),
805
			newCanonicalHash(createHash(301)).encode(),
806
			newSearchCheckpoint(2, 0, 102).encode(),
807
			newCanonicalHash(createHash(302)).encode(),
808
			newSearchCheckpoint(3, 0, 103).encode(),
809
			newCanonicalHash(createHash(303)).encode(),
810
			// open and seal 4
811
			newInitiatingEvent(createHash(1), false).encode(),
812
			newSearchCheckpoint(4, 0, 104).encode(),
813
			newCanonicalHash(createHash(304)).encode(),
814 815 816 817 818 819 820 821 822 823 824 825 826
		)
		db, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(4*2+3), m.entryCount)
		requireContains(t, db, 4, 0, createHash(1))
	})

	t.Run("NoTruncateWhenLastEntryIsExecutingCheckSealed", func(t *testing.T) {
		execMsg := types.ExecutingMessage{
			Chain:     4,
			BlockNum:  10,
			LogIdx:    4,
			Timestamp: 1288,
827
			Hash:      createHash(4),
828
		}
829 830 831 832
		linkEvt, err := newExecutingLink(execMsg)
		require.NoError(t, err)
		store := storeWithEvents(
			newSearchCheckpoint(0, 0, 100).encode(),
833
			newCanonicalHash(createHash(300)).encode(),
834
			newSearchCheckpoint(1, 0, 101).encode(),
835
			newCanonicalHash(createHash(301)).encode(),
836
			newSearchCheckpoint(2, 0, 102).encode(),
837 838
			newCanonicalHash(createHash(302)).encode(),
			newInitiatingEvent(createHash(1111), true).encode(),
839 840 841
			linkEvt.encode(),
			newExecutingCheck(execMsg.Hash).encode(),
			newSearchCheckpoint(3, 0, 103).encode(),
842
			newCanonicalHash(createHash(303)).encode(),
843 844 845 846 847 848
		)
		db, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(3*2+5), m.entryCount)
		requireContains(t, db, 3, 0, createHash(1111), execMsg)
	})
849

850 851 852 853 854 855 856 857
	t.Run("TruncateWhenLastEntrySearchCheckpoint", func(t *testing.T) {
		// A checkpoint, without a canonical blockhash, is useless, and thus truncated.
		store := storeWithEvents(
			newSearchCheckpoint(0, 0, 100).encode())
		_, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(0), m.entryCount)
	})
858

859 860 861 862
	t.Run("NoTruncateWhenLastEntryCanonicalHash", func(t *testing.T) {
		// A completed seal is fine to have as last entry.
		store := storeWithEvents(
			newSearchCheckpoint(0, 0, 100).encode(),
863
			newCanonicalHash(createHash(344)).encode(),
864 865 866 867 868
		)
		_, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(2), m.entryCount)
	})
869

870 871 872 873 874
	t.Run("TruncateWhenLastEntryInitEventWithExecMsg", func(t *testing.T) {
		// An initiating event that claims an executing message,
		// without said executing message, is dropped.
		store := storeWithEvents(
			newSearchCheckpoint(0, 0, 100).encode(),
875
			newCanonicalHash(createHash(344)).encode(),
876
			// both pruned because we go back to a seal
877 878
			newInitiatingEvent(createHash(0), false).encode(),
			newInitiatingEvent(createHash(1), true).encode(),
879 880 881 882 883
		)
		_, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(2), m.entryCount)
	})
884

885 886 887 888 889
	t.Run("NoTruncateWhenLastEntrySealed", func(t *testing.T) {
		// An initiating event that claims an executing message,
		// without said executing message, is dropped.
		store := storeWithEvents(
			newSearchCheckpoint(0, 0, 100).encode(),
890
			newCanonicalHash(createHash(300)).encode(),
891
			// pruned because we go back to a seal
892
			newInitiatingEvent(createHash(0), false).encode(),
893
			newSearchCheckpoint(1, 0, 100).encode(),
894
			newCanonicalHash(createHash(301)).encode(),
895 896 897 898 899
		)
		_, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(5), m.entryCount)
	})
900

901 902 903 904 905 906
	t.Run("TruncateWhenLastEntryInitEventWithExecLink", func(t *testing.T) {
		execMsg := types.ExecutingMessage{
			Chain:     4,
			BlockNum:  10,
			LogIdx:    4,
			Timestamp: 1288,
907
			Hash:      createHash(4),
908 909 910 911 912
		}
		linkEvt, err := newExecutingLink(execMsg)
		require.NoError(t, err)
		store := storeWithEvents(
			newSearchCheckpoint(3, 0, 100).encode(),
913 914
			newCanonicalHash(createHash(344)).encode(),
			newInitiatingEvent(createHash(1), true).encode(),
915 916 917 918 919 920
			linkEvt.encode(),
		)
		_, m, err := createDb(t, store)
		require.NoError(t, err)
		require.EqualValues(t, int64(2), m.entryCount)
	})
921 922 923 924 925 926
}

func TestRewind(t *testing.T) {
	t.Run("WhenEmpty", func(t *testing.T) {
		runDBTest(t, func(t *testing.T, db *DB, m *stubMetrics) {},
			func(t *testing.T, db *DB, m *stubMetrics) {
927 928 929
				require.ErrorIs(t, db.Rewind(100), ErrFuture)
				// Genesis is a block to, not present in an empty DB
				require.ErrorIs(t, db.Rewind(0), ErrFuture)
930 931 932 933 934 935
			})
	})

	t.Run("AfterLastBlock", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
936 937
				bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
				require.NoError(t, db.SealBlock(createHash(49), bl50, 500))
938 939
				require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
				require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil))
940 941
				bl51 := eth.BlockID{Hash: createHash(51), Number: 51}
				require.NoError(t, db.SealBlock(bl50.Hash, bl51, 502))
942
				require.NoError(t, db.AddLog(createHash(3), bl51, 0, nil))
943 944
				bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
				require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
945
				require.NoError(t, db.AddLog(createHash(4), bl52, 0, nil))
946 947
				// cannot rewind to a block that is not sealed yet
				require.ErrorIs(t, db.Rewind(53), ErrFuture)
948 949
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
950 951 952 953 954
				requireContains(t, db, 51, 0, createHash(1))
				requireContains(t, db, 51, 1, createHash(2))
				requireContains(t, db, 52, 0, createHash(3))
				// Still have the pending log of unsealed block if the rewind to unknown sealed block fails
				requireContains(t, db, 53, 0, createHash(4))
955 956 957 958 959 960
			})
	})

	t.Run("BeforeFirstBlock", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
961 962
				bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
				require.NoError(t, db.SealBlock(createHash(49), bl50, 500))
963 964
				require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
				require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil))
965 966
				// cannot go back to an unknown block
				require.ErrorIs(t, db.Rewind(25), ErrSkipped)
967 968
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
969 970
				requireContains(t, db, 51, 0, createHash(1))
				requireContains(t, db, 51, 0, createHash(1))
971 972 973 974 975 976
			})
	})

	t.Run("AtFirstBlock", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
977 978
				bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
				require.NoError(t, db.SealBlock(createHash(49), bl50, 500))
979 980
				require.NoError(t, db.AddLog(createHash(1), bl50, 0, nil))
				require.NoError(t, db.AddLog(createHash(2), bl50, 1, nil))
981 982
				bl51 := eth.BlockID{Hash: createHash(51), Number: 51}
				require.NoError(t, db.SealBlock(bl50.Hash, bl51, 502))
983 984
				require.NoError(t, db.AddLog(createHash(1), bl51, 0, nil))
				require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
985 986 987
				bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
				require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
				require.NoError(t, db.Rewind(51))
988 989
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
990 991 992 993
				requireContains(t, db, 51, 0, createHash(1))
				requireContains(t, db, 51, 1, createHash(2))
				requireFuture(t, db, 52, 0, createHash(1))
				requireFuture(t, db, 52, 1, createHash(2))
994 995 996
			})
	})

997
	t.Run("AfterSecondCheckpoint", func(t *testing.T) {
998 999
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
1000 1001
				bl50 := eth.BlockID{Hash: createHash(50), Number: 50}
				require.NoError(t, db.SealBlock(createHash(49), bl50, 500))
1002
				for i := uint32(0); m.entryCount < searchCheckpointFrequency; i++ {
1003
					require.NoError(t, db.AddLog(createHash(1), bl50, i, nil))
1004
				}
1005 1006 1007 1008 1009 1010
				// The checkpoint is added automatically,
				// it will be there as soon as it reaches 255 with log events.
				// Thus add 2 for the checkpoint.
				require.EqualValues(t, searchCheckpointFrequency+2, m.entryCount)
				bl51 := eth.BlockID{Hash: createHash(51), Number: 51}
				require.NoError(t, db.SealBlock(bl50.Hash, bl51, 502))
1011
				require.NoError(t, db.AddLog(createHash(1), bl51, 0, nil))
1012
				require.EqualValues(t, searchCheckpointFrequency+2+3, m.entryCount, "Should have inserted new checkpoint and extra log")
1013
				require.NoError(t, db.AddLog(createHash(2), bl51, 1, nil))
1014 1015 1016
				bl52 := eth.BlockID{Hash: createHash(52), Number: 52}
				require.NoError(t, db.SealBlock(bl51.Hash, bl52, 504))
				require.NoError(t, db.Rewind(51))
1017 1018
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
1019 1020 1021 1022 1023
				require.EqualValues(t, searchCheckpointFrequency+2+2, m.entryCount, "Should have deleted second checkpoint")
				requireContains(t, db, 51, 0, createHash(1))
				requireContains(t, db, 51, 1, createHash(1))
				requireFuture(t, db, 52, 0, createHash(1))
				requireFuture(t, db, 52, 1, createHash(2))
1024 1025 1026
			})
	})

1027
	t.Run("BetweenBlockEntries", func(t *testing.T) {
1028 1029
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
1030 1031 1032 1033 1034
				// create many blocks, and all the odd blocks get 2 logs
				for i := uint32(0); i < 30; i++ {
					bl := eth.BlockID{Hash: createHash(int(i)), Number: uint64(i)}
					require.NoError(t, db.SealBlock(createHash(int(i)-1), bl, 500+uint64(i)))
					if i%2 == 0 {
1035 1036
						require.NoError(t, db.AddLog(createHash(1), bl, 0, nil))
						require.NoError(t, db.AddLog(createHash(2), bl, 1, nil))
1037 1038 1039
					}
				}
				require.NoError(t, db.Rewind(15))
1040 1041
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
1042 1043 1044 1045
				requireContains(t, db, 15, 0, createHash(1))
				requireContains(t, db, 15, 1, createHash(2))
				requireFuture(t, db, 16, 0, createHash(1))
				requireFuture(t, db, 16, 1, createHash(2))
1046 1047 1048 1049 1050 1051
			})
	})

	t.Run("AtLastEntry", func(t *testing.T) {
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
1052 1053 1054 1055 1056
				// create many blocks, and all the even blocks get 2 logs
				for i := uint32(0); i <= 30; i++ {
					bl := eth.BlockID{Hash: createHash(int(i)), Number: uint64(i)}
					require.NoError(t, db.SealBlock(createHash(int(i)-1), bl, 500+uint64(i)))
					if i%2 == 1 {
1057 1058
						require.NoError(t, db.AddLog(createHash(1), bl, 0, nil))
						require.NoError(t, db.AddLog(createHash(2), bl, 1, nil))
1059 1060 1061 1062
					}
				}
				// We ended at 30, and sealed it, nothing left to prune
				require.NoError(t, db.Rewind(30))
1063 1064
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
1065 1066 1067 1068 1069
				requireContains(t, db, 20, 0, createHash(1))
				requireContains(t, db, 20, 1, createHash(2))
				// built on top of 29, these are in sealed block 30, still around
				requireContains(t, db, 30, 0, createHash(1))
				requireContains(t, db, 30, 1, createHash(2))
1070 1071 1072
			})
	})

1073
	t.Run("ReadDeletedBlocks", func(t *testing.T) {
1074 1075
		runDBTest(t,
			func(t *testing.T, db *DB, m *stubMetrics) {
1076 1077 1078 1079 1080
				// create many blocks, and all the odd blocks get 2 logs
				for i := uint32(0); i < 30; i++ {
					bl := eth.BlockID{Hash: createHash(int(i)), Number: uint64(i)}
					require.NoError(t, db.SealBlock(createHash(int(i)-1), bl, 500+uint64(i)))
					if i%2 == 0 {
1081 1082
						require.NoError(t, db.AddLog(createHash(1), bl, 0, nil))
						require.NoError(t, db.AddLog(createHash(2), bl, 1, nil))
1083 1084 1085
					}
				}
				require.NoError(t, db.Rewind(16))
1086 1087
			},
			func(t *testing.T, db *DB, m *stubMetrics) {
1088 1089
				bl29 := eth.BlockID{Hash: createHash(29), Number: 29}
				// 29 was deleted
1090
				err := db.AddLog(createHash(2), bl29, 1, nil)
1091 1092 1093 1094
				require.ErrorIs(t, err, ErrLogOutOfOrder, "Cannot add log on removed block")
				// 15 is older, we have up to 16
				bl15 := eth.BlockID{Hash: createHash(15), Number: 15}
				// try to add a third log to 15
1095
				err = db.AddLog(createHash(10), bl15, 2, nil)
1096 1097 1098
				require.ErrorIs(t, err, ErrLogOutOfOrder)
				bl16 := eth.BlockID{Hash: createHash(16), Number: 16}
				// try to add a log to 17, on top of 16
1099
				err = db.AddLog(createHash(42), bl16, 0, nil)
1100 1101
				require.NoError(t, err)
				requireContains(t, db, 17, 0, createHash(42))
1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
			})
	})
}

type stubMetrics struct {
	entryCount           int64
	entriesReadForSearch int64
}

func (s *stubMetrics) RecordDBEntryCount(count int64) {
	s.entryCount = count
}

func (s *stubMetrics) RecordDBSearchEntriesRead(count int64) {
	s.entriesReadForSearch = count
}

var _ Metrics = (*stubMetrics)(nil)

type stubEntryStore struct {
	entries []entrydb.Entry
}

func (s *stubEntryStore) Size() int64 {
	return int64(len(s.entries))
}

func (s *stubEntryStore) LastEntryIdx() entrydb.EntryIdx {
	return entrydb.EntryIdx(s.Size() - 1)
}

func (s *stubEntryStore) Read(idx entrydb.EntryIdx) (entrydb.Entry, error) {
	if idx < entrydb.EntryIdx(len(s.entries)) {
		return s.entries[idx], nil
	}
	return entrydb.Entry{}, io.EOF
}

func (s *stubEntryStore) Append(entries ...entrydb.Entry) error {
	s.entries = append(s.entries, entries...)
	return nil
}

func (s *stubEntryStore) Truncate(idx entrydb.EntryIdx) error {
	s.entries = s.entries[:min(s.Size()-1, int64(idx+1))]
	return nil
}

func (s *stubEntryStore) Close() error {
	return nil
}

var _ EntryStore = (*stubEntryStore)(nil)