44 "errors"
55 "testing"
66
7+ "github.com/jordanschalm/lockctx"
78 "github.com/stretchr/testify/assert"
89 mocks "github.com/stretchr/testify/mock"
910 "github.com/stretchr/testify/require"
@@ -16,6 +17,7 @@ import (
1617 "github.com/onflow/flow-go/state/protocol/protocol_state/epochs"
1718 "github.com/onflow/flow-go/state/protocol/protocol_state/epochs/mock"
1819 protocol_statemock "github.com/onflow/flow-go/state/protocol/protocol_state/mock"
20+ "github.com/onflow/flow-go/storage"
1921 storagemock "github.com/onflow/flow-go/storage/mock"
2022 "github.com/onflow/flow-go/utils/unittest"
2123)
@@ -40,6 +42,7 @@ type EpochStateMachineSuite struct {
4042 happyPathStateMachineFactory * mock.StateMachineFactoryMethod
4143 fallbackPathStateMachineFactory * mock.StateMachineFactoryMethod
4244 candidate * flow.Header
45+ lockManager lockctx.Manager
4346
4447 stateMachine * epochs.EpochStateMachine
4548}
@@ -56,6 +59,7 @@ func (s *EpochStateMachineSuite) SetupTest() {
5659 s .happyPathStateMachine = mock .NewStateMachine (s .T ())
5760 s .happyPathStateMachineFactory = mock .NewStateMachineFactoryMethod (s .T ())
5861 s .fallbackPathStateMachineFactory = mock .NewStateMachineFactoryMethod (s .T ())
62+ s .lockManager = storage .NewTestingLockManager ()
5963
6064 s .epochStateDB .On ("ByBlockID" , mocks .Anything ).Return (func (_ flow.Identifier ) * flow.RichEpochStateEntry {
6165 return s .parentEpochState
@@ -97,17 +101,21 @@ func (s *EpochStateMachineSuite) TestBuild_NoChanges() {
97101
98102 rw := storagemock .NewReaderBatchWriter (s .T ())
99103
100- s .epochStateDB .On ("BatchIndex" , rw , s .candidate .ID (), s .parentEpochState .ID ()).Return (nil ).Once ()
101- s .mutator .On ("SetEpochStateID" , s .parentEpochState .ID ()).Return (nil ).Once ()
104+ // Create a proper lock context proof for the BatchIndex operation
105+ err = unittest .WithLock (s .T (), s .lockManager , storage .LockInsertBlock , func (lctx lockctx.Context ) error {
106+ s .epochStateDB .On ("BatchIndex" , lctx , rw , s .candidate .ID (), s .parentEpochState .ID ()).Return (nil ).Once ()
107+ s .mutator .On ("SetEpochStateID" , s .parentEpochState .ID ()).Return (nil ).Once ()
102108
103- dbUpdates , err := s .stateMachine .Build ()
104- require .NoError (s .T (), err )
109+ dbUpdates , err := s .stateMachine .Build ()
110+ require .NoError (s .T (), err )
105111
106- // Provide the blockID and execute the resulting `dbUpdates`. Thereby, the expected mock methods should be called,
107- // which is asserted by the testify framework. Passing nil lockctx proof because no operations require lock;
108- // operations are deferred only because block ID is not known yet.
109- blockID := s .candidate .ID ()
110- err = dbUpdates .Execute (nil , blockID , rw )
112+ // Storage operations are deferred, because block ID is not known when the block is newly constructed. Only at the
113+ // end after the block is fully constructed, its ID can be computed. We emulate this step here to verify that the
114+ // deferred `dbOps` have been correctly constructed. Thereby, the expected mock methods should be called,
115+ // which is asserted by the testify framework.
116+ blockID := s .candidate .ID ()
117+ return dbUpdates .Execute (lctx , blockID , rw )
118+ })
111119 require .NoError (s .T (), err )
112120}
113121
@@ -139,19 +147,22 @@ func (s *EpochStateMachineSuite) TestBuild_HappyPath() {
139147 err := s .stateMachine .EvolveState ([]flow.ServiceEvent {epochSetup .ServiceEvent (), epochCommit .ServiceEvent ()})
140148 require .NoError (s .T (), err )
141149
142- // prepare a DB update for epoch state
143- s .epochStateDB .On ("BatchIndex" , rw , s .candidate .ID (), updatedStateID ).Return (nil ).Once ()
144- s .epochStateDB .On ("BatchStore" , w , updatedStateID , updatedState .MinEpochStateEntry ).Return (nil ).Once ()
145- s .mutator .On ("SetEpochStateID" , updatedStateID ).Return (nil ).Once ()
150+ // Create a proper lock context proof for the BatchIndex operation
151+ err = unittest .WithLock (s .T (), s .lockManager , storage .LockInsertBlock , func (lctx lockctx.Context ) error {
152+ // prepare a DB update for epoch state
153+ s .epochStateDB .On ("BatchIndex" , lctx , rw , s .candidate .ID (), updatedStateID ).Return (nil ).Once ()
154+ s .epochStateDB .On ("BatchStore" , w , updatedStateID , updatedState .MinEpochStateEntry ).Return (nil ).Once ()
155+ s .mutator .On ("SetEpochStateID" , updatedStateID ).Return (nil ).Once ()
146156
147- dbUpdates , err := s .stateMachine .Build ()
148- require .NoError (s .T (), err )
157+ dbUpdates , err := s .stateMachine .Build ()
158+ require .NoError (s .T (), err )
149159
150- // Provide the blockID and execute the resulting `dbUpdates`. Thereby, the expected mock methods should be called,
151- // which is asserted by the testify framework. Passing nil lockctx proof because no operations require lock;
152- // operations are deferred only because block ID is not known yet.
153- blockID := s .candidate .ID ()
154- err = dbUpdates .Execute (nil , blockID , rw )
160+ // Provide the blockID and execute the resulting `dbUpdates`. Thereby, the expected mock methods should be called,
161+ // which is asserted by the testify framework. The lock context proof is passed to verify that the BatchIndex
162+ // operation receives the proper lock context as required by the storage layer.
163+ blockID := s .candidate .ID ()
164+ return dbUpdates .Execute (lctx , blockID , rw )
165+ })
155166 require .NoError (s .T (), err )
156167}
157168
@@ -532,29 +543,34 @@ func (s *EpochStateMachineSuite) TestEvolveStateTransitionToNextEpoch_WithInvali
532543 err = stateMachine .EvolveState ([]flow.ServiceEvent {invalidServiceEvent .ServiceEvent ()})
533544 require .NoError (s .T (), err )
534545
535- s .epochStateDB .On ("BatchIndex" , mocks .Anything , s .candidate .ID (), mocks .Anything ).Return (nil ).Once ()
546+ // Create a proper lock context proof for the BatchIndex operation
547+ err = unittest .WithLock (s .T (), s .lockManager , storage .LockInsertBlock , func (lctx lockctx.Context ) error {
548+ s .epochStateDB .On ("BatchIndex" , lctx , mocks .Anything , s .candidate .ID (), mocks .Anything ).Return (nil ).Once ()
536549
537- expectedEpochState := & flow.MinEpochStateEntry {
538- PreviousEpoch : s .parentEpochState .CurrentEpoch .Copy (),
539- CurrentEpoch : * s .parentEpochState .NextEpoch .Copy (),
540- NextEpoch : nil ,
541- EpochFallbackTriggered : true ,
542- }
550+ expectedEpochState := & flow.MinEpochStateEntry {
551+ PreviousEpoch : s .parentEpochState .CurrentEpoch .Copy (),
552+ CurrentEpoch : * s .parentEpochState .NextEpoch .Copy (),
553+ NextEpoch : nil ,
554+ EpochFallbackTriggered : true ,
555+ }
543556
544- s .epochStateDB .On ("BatchStore" , mocks .Anything , expectedEpochState .ID (), expectedEpochState ).Return (nil ).Once ()
545- s .mutator .On ("SetEpochStateID" , expectedEpochState .ID ()).Return ().Once ()
557+ s .epochStateDB .On ("BatchStore" , mocks .Anything , expectedEpochState .ID (), expectedEpochState ).Return (nil ).Once ()
558+ s .mutator .On ("SetEpochStateID" , expectedEpochState .ID ()).Return ().Once ()
546559
547- dbOps , err := stateMachine .Build ()
548- require .NoError (s .T (), err )
560+ dbOps , err := stateMachine .Build ()
561+ require .NoError (s .T (), err )
549562
550- w := storagemock .NewWriter (s .T ())
551- rw := storagemock .NewReaderBatchWriter (s .T ())
552- rw .On ("Writer" ).Return (w ).Once () // called by epochStateDB.BatchStore
563+ w := storagemock .NewWriter (s .T ())
564+ rw := storagemock .NewReaderBatchWriter (s .T ())
565+ rw .On ("Writer" ).Return (w ).Once () // called by epochStateDB.BatchStore
553566
554- // Provide the blockID and execute the resulting `dbUpdates`. Thereby, the expected mock methods should be called,
555- // which is asserted by the testify framework. Passing nil lockctx proof because no operations require lock;
556- // operations are deferred only because block ID is not known yet
557- blockID := s .candidate .ID ()
558- err = dbOps .Execute (nil , blockID , rw )
567+ // Storage operations are deferred, because block ID is not known when the block is newly constructed. Only at the
568+ // end after the block is fully constructed, its ID can be computed. We emulate this step here to verify that the
569+ // deferred `dbOps` have been correctly constructed. Thereby, the expected mock methods should be called,
570+ // which is asserted by the testify framework.
571+ blockID := s .candidate .ID ()
572+ return dbOps .Execute (lctx , blockID , rw )
573+ })
559574 require .NoError (s .T (), err )
575+
560576}
0 commit comments