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  }