github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/storage/badger/transaction/deferred_block_persist_test.go (about)

     1  package transaction_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/dgraph-io/badger/v2"
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/stretchr/testify/suite"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/storage/badger/transaction"
    13  	"github.com/onflow/flow-go/utils/unittest"
    14  )
    15  
    16  func TestDeferredBlockPersist(t *testing.T) {
    17  	suite.Run(t, new(DeferredBlockPersistSuite))
    18  }
    19  
    20  type DeferredBlockPersistSuite struct {
    21  	suite.Suite
    22  }
    23  
    24  // TestEmpty verifies that DeferredBlockPersist behaves like a no-op if nothing is scheduled
    25  func (s *DeferredBlockPersistSuite) TestEmpty() {
    26  	deferredPersistOps := transaction.NewDeferredBlockPersist()
    27  	require.True(s.T(), deferredPersistOps.IsEmpty())
    28  
    29  	// NewDeferredBlockPersist.Pending() should be a no-op and therefore not care that transaction.Tx is nil
    30  	err := deferredPersistOps.Pending()(unittest.IdentifierFixture(), nil)
    31  	require.NoError(s.T(), err)
    32  }
    33  
    34  // Test_AddBadgerOp adds 1 or 2 DeferredBadgerUpdate(s) and verifies that they are executed in the expected order
    35  func (s *DeferredBlockPersistSuite) Test_AddBadgerOp() {
    36  	blockID := unittest.IdentifierFixture()
    37  	unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) {
    38  		s.Run("single DeferredBadgerUpdate", func() {
    39  			m := NewBlockPersistCallMonitor(s.T())
    40  			deferredPersistOps := transaction.NewDeferredBlockPersist().AddBadgerOp(m.MakeBadgerUpdate())
    41  			require.False(s.T(), deferredPersistOps.IsEmpty())
    42  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    43  			require.NoError(s.T(), err)
    44  		})
    45  
    46  		s.Run("two DeferredBadgerUpdates added individually", func() {
    47  			m := NewBlockPersistCallMonitor(s.T())
    48  			deferredPersistOps := transaction.NewDeferredBlockPersist().
    49  				AddBadgerOp(m.MakeBadgerUpdate()).
    50  				AddBadgerOp(m.MakeBadgerUpdate())
    51  			require.False(s.T(), deferredPersistOps.IsEmpty())
    52  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    53  			require.NoError(s.T(), err)
    54  		})
    55  
    56  		s.Run("two DeferredBadgerUpdates added as a sequence", func() {
    57  			m := NewBlockPersistCallMonitor(s.T())
    58  			deferredPersistOps := transaction.NewDeferredBlockPersist().AddBadgerOps(
    59  				m.MakeBadgerUpdate(),
    60  				m.MakeBadgerUpdate())
    61  			require.False(s.T(), deferredPersistOps.IsEmpty())
    62  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    63  			require.NoError(s.T(), err)
    64  		})
    65  	})
    66  }
    67  
    68  // TestDbOp adds 1 or 2 DeferredDBUpdate(s) and verifies that they are executed in the expected order
    69  func (s *DeferredBlockPersistSuite) Test_AddDbOp() {
    70  	blockID := unittest.IdentifierFixture()
    71  	unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) {
    72  		s.Run("single DeferredDBUpdate without callback", func() {
    73  			m := NewBlockPersistCallMonitor(s.T())
    74  			deferredPersistOps := transaction.NewDeferredBlockPersist().
    75  				AddDbOp(m.MakeDBUpdate(0))
    76  			require.False(s.T(), deferredPersistOps.IsEmpty())
    77  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    78  			require.NoError(s.T(), err)
    79  		})
    80  
    81  		s.Run("single DeferredDBUpdate with one callback", func() {
    82  			m := NewBlockPersistCallMonitor(s.T())
    83  			deferredPersistOps := transaction.NewDeferredBlockPersist().
    84  				AddDbOp(m.MakeDBUpdate(1))
    85  			require.False(s.T(), deferredPersistOps.IsEmpty())
    86  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    87  			require.NoError(s.T(), err)
    88  		})
    89  
    90  		s.Run("single DeferredDBUpdate with multiple callbacks", func() {
    91  			m := NewBlockPersistCallMonitor(s.T())
    92  			deferredPersistOps := transaction.NewDeferredBlockPersist().
    93  				AddDbOp(m.MakeDBUpdate(21))
    94  			require.False(s.T(), deferredPersistOps.IsEmpty())
    95  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
    96  			require.NoError(s.T(), err)
    97  		})
    98  
    99  		s.Run("two DeferredDBUpdates added individually", func() {
   100  			m := NewBlockPersistCallMonitor(s.T())
   101  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   102  				AddDbOp(m.MakeDBUpdate(17)).
   103  				AddDbOp(m.MakeDBUpdate(0))
   104  			require.False(s.T(), deferredPersistOps.IsEmpty())
   105  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   106  			require.NoError(s.T(), err)
   107  		})
   108  
   109  		s.Run("two DeferredDBUpdates added as a sequence", func() {
   110  			m := NewBlockPersistCallMonitor(s.T())
   111  			deferredPersistOps := transaction.NewDeferredBlockPersist()
   112  			deferredPersistOps.AddDbOps(
   113  				m.MakeDBUpdate(0),
   114  				m.MakeDBUpdate(17))
   115  			require.False(s.T(), deferredPersistOps.IsEmpty())
   116  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   117  			require.NoError(s.T(), err)
   118  		})
   119  	})
   120  }
   121  
   122  // Test_AddIndexingOp adds 1 or 2 DeferredBlockPersistOp(s) and verifies that they are executed in the expected order
   123  func (s *DeferredBlockPersistSuite) Test_AddIndexingOp() {
   124  	blockID := unittest.IdentifierFixture()
   125  	unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) {
   126  		s.Run("single DeferredBlockPersistOp without callback", func() {
   127  			m := NewBlockPersistCallMonitor(s.T())
   128  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   129  				AddIndexingOp(m.MakeIndexingOp(blockID, 0))
   130  			require.False(s.T(), deferredPersistOps.IsEmpty())
   131  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   132  			require.NoError(s.T(), err)
   133  		})
   134  
   135  		s.Run("single DeferredBlockPersistOp with one callback", func() {
   136  			m := NewBlockPersistCallMonitor(s.T())
   137  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   138  				AddIndexingOp(m.MakeIndexingOp(blockID, 1))
   139  			require.False(s.T(), deferredPersistOps.IsEmpty())
   140  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   141  			require.NoError(s.T(), err)
   142  		})
   143  
   144  		s.Run("single DeferredBlockPersistOp with multiple callbacks", func() {
   145  			m := NewBlockPersistCallMonitor(s.T())
   146  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   147  				AddIndexingOp(m.MakeIndexingOp(blockID, 21))
   148  			require.False(s.T(), deferredPersistOps.IsEmpty())
   149  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   150  			require.NoError(s.T(), err)
   151  		})
   152  
   153  		s.Run("two DeferredBlockPersistOp added individually", func() {
   154  			m := NewBlockPersistCallMonitor(s.T())
   155  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   156  				AddIndexingOp(m.MakeIndexingOp(blockID, 17)).
   157  				AddIndexingOp(m.MakeIndexingOp(blockID, 0))
   158  			require.False(s.T(), deferredPersistOps.IsEmpty())
   159  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   160  			require.NoError(s.T(), err)
   161  		})
   162  
   163  		s.Run("two DeferredBlockPersistOp added as a sequence", func() {
   164  			m := NewBlockPersistCallMonitor(s.T())
   165  			deferredPersistOps := transaction.NewDeferredBlockPersist()
   166  			deferredPersistOps.AddIndexingOps(
   167  				m.MakeIndexingOp(blockID, 0),
   168  				m.MakeIndexingOp(blockID, 17))
   169  			require.False(s.T(), deferredPersistOps.IsEmpty())
   170  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   171  			require.NoError(s.T(), err)
   172  		})
   173  	})
   174  }
   175  
   176  // Test_AddOnSucceedCallback adds 1 or 2 callback(s) and verifies that they are executed in the expected order
   177  func (s *DeferredBlockPersistSuite) Test_AddOnSucceedCallback() {
   178  	blockID := unittest.IdentifierFixture()
   179  	unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) {
   180  		s.Run("single callback", func() {
   181  			m := NewBlockPersistCallMonitor(s.T())
   182  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   183  				OnSucceed(m.MakeCallback())
   184  			require.False(s.T(), deferredPersistOps.IsEmpty())
   185  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   186  			require.NoError(s.T(), err)
   187  		})
   188  
   189  		s.Run("two callbacks added individually", func() {
   190  			m := NewBlockPersistCallMonitor(s.T())
   191  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   192  				OnSucceed(m.MakeCallback()).
   193  				OnSucceed(m.MakeCallback())
   194  			require.False(s.T(), deferredPersistOps.IsEmpty())
   195  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   196  			require.NoError(s.T(), err)
   197  		})
   198  
   199  		s.Run("many callbacks added as a sequence", func() {
   200  			m := NewBlockPersistCallMonitor(s.T())
   201  			deferredPersistOps := transaction.NewDeferredBlockPersist().
   202  				OnSucceeds(m.MakeCallbacks(11)...)
   203  			require.False(s.T(), deferredPersistOps.IsEmpty())
   204  			err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   205  			require.NoError(s.T(), err)
   206  		})
   207  	})
   208  }
   209  
   210  // Test_EverythingMixed uses all ways to add functors in combination and verifies that they are executed in the expected order
   211  func (s *DeferredBlockPersistSuite) Test_EverythingMixed() {
   212  	blockID := unittest.IdentifierFixture()
   213  	unittest.RunWithBadgerDB(s.T(), func(db *badger.DB) {
   214  		m := NewBlockPersistCallMonitor(s.T())
   215  		deferredPersistOps := transaction.NewDeferredBlockPersist().
   216  			OnSucceed(m.MakeCallback()).
   217  			AddDbOp(m.MakeDBUpdate(1)).
   218  			AddBadgerOp(m.MakeBadgerUpdate()).
   219  			AddIndexingOp(m.MakeIndexingOp(blockID, 2)).
   220  			OnSucceeds(m.MakeCallbacks(3)...).
   221  			AddDbOp(m.MakeDBUpdate(0)).
   222  			AddBadgerOps(
   223  				m.MakeBadgerUpdate(),
   224  				m.MakeBadgerUpdate(),
   225  				m.MakeBadgerUpdate()).
   226  			AddIndexingOps(
   227  				m.MakeIndexingOp(blockID, 7),
   228  				m.MakeIndexingOp(blockID, 0)).
   229  			OnSucceeds(
   230  				m.MakeCallback(),
   231  				m.MakeCallback()).
   232  			AddDbOps(
   233  				m.MakeDBUpdate(7),
   234  				m.MakeDBUpdate(0),
   235  				m.MakeDBUpdate(1)).
   236  			OnSucceed(m.MakeCallback())
   237  		require.False(s.T(), deferredPersistOps.IsEmpty())
   238  		err := transaction.Update(db, deferredPersistOps.Pending().WithBlock(blockID))
   239  		require.NoError(s.T(), err)
   240  	})
   241  }
   242  
   243  /* ***************************************** Testing Utility BlockPersistCallMonitor ***************************************** */
   244  
   245  // BlockPersistCallMonitor is a utility for testing that DeferredBlockPersist calls its input functors and callbacks
   246  // in the correct order. DeferredBlockPersist is expected to proceed as follows:
   247  //
   248  //  0. Record functors added via `AddBadgerOp`, `AddDbOp`, `AddIndexingOp`, `OnSucceed` ...
   249  //  1. Execute the functors in the order they were added
   250  //  2. During each functor's execution:
   251  //     - some functor's may schedule callbacks (depending on their type)
   252  //     - record those callbacks in the order they are scheduled (no execution yet)
   253  //     `OnSucceed` schedules its callback during its execution at this step as well
   254  //  3. If and only if the underlying database transaction _successfully_ completed, run the callbacks
   255  //
   256  // To verify the correct order of calls, the BlockPersistCallMonitor generates functors. Each functor has a
   257  // dedicated index value. When the functor is called, it checks that its index matches the functor index
   258  // that the BlockPersistCallMonitor expects to be executed next. For callbacks, we proceed analogously.
   259  //
   260  // Usage note:
   261  // The call BlockPersistCallMonitor assumes that functors are added to DeferredBlockPersist exactly in the order that
   262  // BlockPersistCallMonitor generates them. This works very intuitively, when the tests proceed as in the following example:
   263  //
   264  //	m := NewBlockPersistCallMonitor(t)
   265  //	deferredPersistOps :=  transaction.NewDeferredBlockPersist()
   266  //	deferredPersistOps.AddBadgerOp(m.MakeBadgerUpdate()) // here, we add the functor right when it is generated
   267  //	transaction.Update(db, deferredPersistOps.Pending())
   268  type BlockPersistCallMonitor struct {
   269  	generatedTxFunctors int
   270  	generatedCallbacks  int
   271  
   272  	T                        *testing.T
   273  	nextExpectedTxFunctorIdx int
   274  	nextExpectedCallbackIdx  int
   275  }
   276  
   277  func NewBlockPersistCallMonitor(t *testing.T) *BlockPersistCallMonitor {
   278  	return &BlockPersistCallMonitor{T: t}
   279  }
   280  
   281  func (cm *BlockPersistCallMonitor) MakeIndexingOp(expectedBlockID flow.Identifier, withCallbacks int) transaction.DeferredBlockPersistOp {
   282  	myFunctorIdx := cm.generatedTxFunctors       // copy into local scope. Determined when we construct functor
   283  	callbacks := cm.MakeCallbacks(withCallbacks) // pre-generate callback functors
   284  	functor := func(blockID flow.Identifier, tx *transaction.Tx) error {
   285  		if expectedBlockID != blockID {
   286  			cm.T.Errorf("expected block ID %v but got %v", expectedBlockID, blockID)
   287  			return fmt.Errorf("expected block ID %v but got %v", expectedBlockID, blockID)
   288  		}
   289  		for _, c := range callbacks {
   290  			tx.OnSucceed(c) // schedule callback
   291  		}
   292  		if cm.nextExpectedTxFunctorIdx != myFunctorIdx {
   293  			// nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist
   294  			// should execute the functors in the order they were added, which is violated. Hence, we fail:
   295  			cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   296  			return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   297  		}
   298  
   299  		// happy path:
   300  		cm.nextExpectedTxFunctorIdx += 1
   301  		return nil
   302  	}
   303  
   304  	cm.generatedTxFunctors += 1
   305  	return functor
   306  }
   307  
   308  func (cm *BlockPersistCallMonitor) MakeDBUpdate(withCallbacks int) transaction.DeferredDBUpdate {
   309  	myFunctorIdx := cm.generatedTxFunctors       // copy into local scope. Determined when we construct functor
   310  	callbacks := cm.MakeCallbacks(withCallbacks) // pre-generate callback functors
   311  	functor := func(tx *transaction.Tx) error {
   312  		for _, c := range callbacks {
   313  			tx.OnSucceed(c) // schedule callback
   314  		}
   315  		if cm.nextExpectedTxFunctorIdx != myFunctorIdx {
   316  			// nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist
   317  			// should execute the functors in the order they were added, which is violated. Hence, we fail:
   318  			cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   319  			return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   320  		}
   321  
   322  		// happy path:
   323  		cm.nextExpectedTxFunctorIdx += 1
   324  		return nil
   325  	}
   326  
   327  	cm.generatedTxFunctors += 1
   328  	return functor
   329  }
   330  
   331  func (cm *BlockPersistCallMonitor) MakeBadgerUpdate() transaction.DeferredBadgerUpdate {
   332  	myFunctorIdx := cm.generatedTxFunctors // copy into local scope. Determined when we construct functor
   333  	functor := func(tx *badger.Txn) error {
   334  		if cm.nextExpectedTxFunctorIdx != myFunctorIdx {
   335  			// nextExpectedTxFunctorIdx holds the Index of the Functor that was generated next. DeferredBlockPersist
   336  			// should execute the functors in the order they were added, which is violated. Hence, we fail:
   337  			cm.T.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   338  			return fmt.Errorf("expected next Functor Index is %d but my value is %d", cm.nextExpectedTxFunctorIdx, myFunctorIdx)
   339  		}
   340  
   341  		// happy path:
   342  		cm.nextExpectedTxFunctorIdx += 1
   343  		return nil
   344  	}
   345  
   346  	cm.generatedTxFunctors += 1
   347  	return functor
   348  }
   349  
   350  func (cm *BlockPersistCallMonitor) MakeCallback() func() {
   351  	myFunctorIdx := cm.generatedCallbacks // copy into local scope. Determined when we construct callback
   352  	functor := func() {
   353  		if cm.nextExpectedCallbackIdx != myFunctorIdx {
   354  			// nextExpectedCallbackIdx holds the Index of the callback that was generated next. DeferredBlockPersist
   355  			// should execute the callback in the order they were scheduled, which is violated. Hence, we fail:
   356  			cm.T.Errorf("expected next Callback Index is %d but my value is %d", cm.nextExpectedCallbackIdx, myFunctorIdx)
   357  		}
   358  		cm.nextExpectedCallbackIdx += 1 // happy path
   359  	}
   360  
   361  	cm.generatedCallbacks += 1
   362  	return functor
   363  }
   364  
   365  func (cm *BlockPersistCallMonitor) MakeCallbacks(numberCallbacks int) []func() {
   366  	callbacks := make([]func(), 0, numberCallbacks)
   367  	for ; 0 < numberCallbacks; numberCallbacks-- {
   368  		callbacks = append(callbacks, cm.MakeCallback())
   369  	}
   370  	return callbacks
   371  }