-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Dynamic Protocol State] Block payload contains protocol state ID #4868
[Dynamic Protocol State] Block payload contains protocol state ID #4868
Conversation
…convertion methods
…ow/flow-go into yurii/5517-extend-payload
…ow/flow-go into yurii/5517-extend-payload
…ng applied. Updating test setups
…nflow/flow-go into yurii/5517-extend-payload
…s from last merge
… duplicated epoch events
…building process. Updated mocks and tests
…thNoChanges -> TestOnHappyPathNoDbCchanges
setups storage.EpochSetups | ||
commits storage.EpochCommits | ||
stateMachine ProtocolStateMachine | ||
pendingDbUpdates []func(tx *transaction.Tx) error | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would suggest to introduce a dedicated type for better readability:
// DeferredDBUpdate is a shorthand notation for an anonymous function. The function takes | |
// a badger transaction as input to runs some database updates as part of that transaction. | |
type DeferredDBUpdate = []func(tx *transaction.Tx) error | |
I explicitly used a type alias here to codify that is literally just a shorthand notation and not meant as a standalone type.
I would use the DeferredDBUpdate
everywhere here and in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved it into transaction
package so we can use it in other places.
8264c2a
s.resultsDB.On("ByID", seal.ResultID).Return(result, nil) | ||
|
||
s.stateMachine.On("ProcessEpochSetup", epochSetup).Return(nil).Once() | ||
s.setupsDB.On("StoreTx", epochSetup).Return(func(*transaction.Tx) error { return nil }).Once() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In line 84 below
require.Len(s.T(), dbUpdates, 1) |
we check that there is one element in the
dbUpdates
but whether that is the correct one is not asserted. But I think we can easily check that. Essentially we are expecting that exactly the object returned by the StoreTx
call is in dbUpdates
s.setupsDB.On("StoreTx", epochSetup).Return(func(*transaction.Tx) error { return nil }).Once() | |
deferredEpochSetupDBStore := func(*transaction.Tx) error { return nil } | |
s.setupsDB.On("StoreTx", epochSetup).Return(deferredEpochSetupDBStore).Once() |
and in line 84 we would write:
assert.ElementsMatch(s.T(), []func(tx *transaction.Tx) error{deferredEpochSetupDBStore}, dbUpdates)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you can compare first-class functions in golang, there is a discussion about it but the whole approach seems hacky to me. It doesn't work with Equal
, ElementsMatch
or EqualValues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have taken slightly different approach to perform assertions in that function: 38b3210
s.stateMachine.On("SetInvalidStateTransitionAttempted").Return().Once() | ||
|
||
err := s.mutator.ApplyServiceEventsFromValidatedSeals([]*flow.Seal{seal}) | ||
require.NoError(s.T(), err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we still need to explicitly call AssertExpectations
on stateMachine
like this:
require.NoError(s.T(), err) | |
s.stateMachine.AssertExpectations(s.T()) | |
require.NoError(s.T(), err) |
to explicitly enforce that SetInvalidStateTransitionAttempted()
was called at all? This tutorial from 2023 talks about calling AssertExpectations
:
Use the AssertExpectations method Testify’s AssertExpectations method can be used to check that the expected methods were called on the mock object. Always use this method to ensure that the expected behavior was observed during the test.
Not sure ... maybe testify is now doing that automatically. If not, my comment applies to various places in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using "smart" constructor which automatically registers mocks and performs assertions:
flow-go/state/protocol/protocol_state/mock/protocol_state_machine.go
Lines 147 to 154 in 9fc2f66
func NewProtocolStateMachine(t mockConstructorTestingTNewProtocolStateMachine) *ProtocolStateMachine { | |
mock := &ProtocolStateMachine{} | |
mock.Mock.Test(t) | |
t.Cleanup(func() { mock.AssertExpectations(t) }) | |
return mock | |
} |
If you are using those then you can be safe that all expected calls will be executed and no unexpected calls will be called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understood correctly, in TestHappyPathWithDbChanges
we test the state transition from Staking to Epoch Setup phase. I don't think we test the happy path for EpochCommit event, right? I.e. the transition from Epoch Setup to Epoch Commit Phase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Purpose of that test was to ensure that successfully processing a service event creates a deferred DB update to store that event in database, since we cache db updates in stateMutator
we need to ensure that we don't drop them and get the exact set as return values of Build
. To summarize it's not about the transition but more about what happens after successfully processing a service event.
I will update the documentation and add processing of commit event as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing PR. Thanks for putting all this work in. The structure is becoming really clear and clean. All my remaining comments are stylistic, like function names, etc.
There is this conceptual point around epoch fallback triggers and finalization, where it isn't clear to me that we are on the same page. Its probably most efficient to discuss in person. I added a corresponding todo to issue #4649 and tried to give a some details.
It could easily be that we are on the same page and I am just misunderstanding a comment.
…nflow/flow-go into yurii/5517-extend-payload
…/flow-go into yurii/5517-extend-payload
Co-authored-by: Alexander Hentschel <alex.hentschel@axiomzen.co>
https://github.com/dapperlabs/flow-go/issues/5517
Context
This PR adds protocol state as part of block payload. Essentially a valid block now needs to have a correct protocol state ID which will be validated by replicas, to support that leader runs calculation of expected protocol state ID in block building process.
Lots of changes were made to the testing code since we have increased requirements on what is a valid block.
Essentially main and the most important changes are in:
state/protocol/protocol_state/mutator.go
- allows to mutate protocol state and apply service eventsstate/protocol/badger/mutator.go
- uses mutator and validates block proposalsmodule/builder/consensus/builder.go
- uses mutator to build a valid proposalrootblock
command and then calledfinalize
to finish root snapshot creation. With the new changesrootblock
wasn't able to produce a valid block anymore so I had to extract generation of epoch setup and commit events fromfinalize
and move it torootblock
. In new implementationrootblock
generates epoch setup, epoch commit events and creates a valid root block. It isn't a huge change but nowrootblock
is not deterministic anymore(it generates assignments and in process of generating assignments we randomly shuffle elements, additionally we generate a random "random source").I will be adding a few more tests for mutator to cover all scenarios but in general it's ready for a review.