-
Notifications
You must be signed in to change notification settings - Fork 24
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
Do not pass value to instance #371
base: alan
Are you sure you want to change the base?
Conversation
This reverts commit 63996b3.
#370 was opened while working on this |
@@ -121,7 +132,7 @@ func (i *Instance) ProcessMsg(msg *SignedMessage) (decided bool, decidedValue [] | |||
} | |||
return err | |||
case RoundChangeMsgType: | |||
return i.uponRoundChange(i.StartValue, msg, i.State.RoundChangeContainer, i.config.GetValueCheckF()) | |||
return i.uponRoundChange(msg, i.State.RoundChangeContainer, i.config.GetValueCheckF()) |
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 noticed we're passing i.StartValue
, i.State.RoundChangeContainer
, and i.config.GetValueCheckF()
as arguments to the function. Given these are accessed via the instance i
, could someone help clarify the rationale behind explicitly passing these as parameters instead of accessing them directly within the uponRoundChange method?
Is there a specific design choice or benefit we're aiming for by structuring the call this way? For instance, does this approach enhance testability, readability, or separation of concerns in a way that accessing them directly within the function would not?
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.
ditto
i.uponProposal
i.uponPrepare
i.UponCommit
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.
@GalRogozinski I agree with this, either only pass and not save (if its only used within these function) or save and don't pass.
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.
this is out of scope of PR, but can be done
@@ -121,7 +132,7 @@ func (i *Instance) ProcessMsg(msg *SignedMessage) (decided bool, decidedValue [] | |||
} | |||
return err | |||
case RoundChangeMsgType: | |||
return i.uponRoundChange(i.StartValue, msg, i.State.RoundChangeContainer, i.config.GetValueCheckF()) | |||
return i.uponRoundChange(msg, i.State.RoundChangeContainer, i.config.GetValueCheckF()) |
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.
case CommitMsgType:
decided, decidedValue, aggregatedCommit, err = i.UponCommit(msg, i.State.CommitContainer)
if decided {
i.State.Decided = decided
i.State.DecidedValue = decidedValue
}
return err
not related to the change, but we should check for err before assign
@@ -24,6 +24,7 @@ type Instance struct { | |||
// forceStop will force stop the instance if set to true | |||
forceStop bool | |||
StartValue []byte | |||
CdFetcher *types.DataFetcher `json:"-"` |
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.
Could we rename CdFetcher to CDFetcher for clarity? 'CD' stands for 'Consensus Data', and capitalizing the acronym could improve readability and align with our naming conventions.
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 agree. I even prefer the whole ConsensusDataFetcher
, or simply DataFetcher
.
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.
Lets to this, I prefer DataFetcher
or Fetcher
. anyway if using acronym always capital letters for all words.
ssv/attester.go
Outdated
} | ||
|
||
if err := r.BaseRunner.decide(r, input); err != nil { | ||
if err := r.BaseRunner.decide(r, attestationFetcher); err != nil { |
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.
Why are we passing r to r.BaseRunner.decide? Can't it access r internally?
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.
same for
aggregator
proposer
and 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.
It seems to me that the BaseRunner can't access the runner internally. But, also, the decide
function doesn't need the runner as far as I understand. So, I think this could be simplified to r.BaseRunner.decide(fetcher)
too
@@ -5,6 +5,7 @@ import ( | |||
) | |||
|
|||
// EmptyValue tests a starting an instance for an empty value (not passing value check) | |||
// Instance starts but with an empty value and no proposal gets broadcasted |
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.
Is there a way no proposals gets broadcasted?
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.
@olegshmuelov I assume if CDFetcher fails fetching..
@@ -6,6 +6,7 @@ import ( | |||
) | |||
|
|||
// InvalidValue tests a starting an instance for an invalid value (not passing value check) | |||
// Instance starts but with an empty value and no proposal gets broadcasted |
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.
ditto
@@ -5,6 +5,7 @@ import ( | |||
) | |||
|
|||
// NilValue tests a starting an instance for a nil value (not passing value check) | |||
// Instance starts but with an empty value and no proposal gets broadcasted |
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.
ditto
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.
Good work!! Just left a suggestion
qbft/round_change.go
Outdated
func chooseValueToPropose(roundChangeMsg *SignedMessage, cdFetcher *types.DataFetcher) ([]byte, error) { | ||
var valueToPropose []byte | ||
if roundChangeMsg.Message.RoundChangePrepared() { | ||
valueToPropose = roundChangeMsg.FullData | ||
} else { | ||
cd, err := cdFetcher.GetConsensusData() | ||
if err != nil { | ||
return nil, err | ||
} | ||
valueToPropose = cd | ||
} | ||
return valueToPropose, nil | ||
} |
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 believe this function can be called more than once during the Round-Change phase. If so, we would also wait for the Beacon Node more than once. Is there a way to save the data, e.g. storing it in the i.StartValue
, so it can be returned faster?
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.
The idea of cdFetcher
is that implementation can do it in a way where it calls it asynchronously and even caches values. But this is an implementation detail.
} | ||
if err = valueCheckF(value); err != nil { | ||
fmt.Printf("%s\n", err.Error()) | ||
return |
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.
should we also return if CreateProposal returns an error?
@@ -46,6 +46,12 @@ func (c *Contributions) HashTreeRootWith(hh ssz.HashWalker) error { | |||
return nil | |||
} | |||
|
|||
// DataFetcher asynchronusly fetches data from the beacon node upon instantiation |
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.
// DataFetcher asynchronusly fetches data from the beacon node upon instantiation | |
// DataFetcher asynchronously fetches data from the beacon node upon instantiation |
@@ -46,6 +46,12 @@ func (c *Contributions) HashTreeRootWith(hh ssz.HashWalker) error { | |||
return nil | |||
} | |||
|
|||
// DataFetcher asynchronusly fetches data from the beacon node upon instantiation | |||
type DataFetcher struct { |
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.
Why not interface? Using concrete types is not flexible and inconvenient for testing and when spec repo is used by other repositories
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.
@nkryuchkov @GalRogozinski
Usually I support using concrete types. As they are more predictable than interfaces. but this time you are using this concrete type as an interface so it might make more sense for it to be an interface ( but then also moved to the runners ).
the rule is -
Return concrete types
Receive interfaces
@@ -56,16 +57,26 @@ func (i *Instance) ForceStop() { | |||
} | |||
|
|||
// Start is an interface implementation | |||
func (i *Instance) Start(value []byte, height Height) { | |||
func (i *Instance) Start(cdFetcher *types.DataFetcher, height Height, valueCheckF ProposedValueCheckF) { |
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.
Can't we use the i.GetConfig().GetValueCheck()
instead of using an argument? It seems a duplication to me
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 agree, if value check only used here then maybe we don't need it as part of Config
.
fmt.Printf("%s\n", err.Error()) | ||
return |
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.
The error should be returned and not printed, no?
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.
apparently we printed errors in main before...
not sure why...
Maybe we should add errors and test them?
I will open an issue
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 proposer(state, config, roundChangeMsg.QBFTMessage.Round) != state.CommitteeMember.OperatorID { | ||
return nil, errors.New("not proposer") | ||
} |
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.
The same check is done below. Any reason for duplicating it or we can remove it?
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.
where? I don't see it
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.
196-197
func chooseValueToPropose(roundChangeMsg *Message, cdFetcher *types.DataFetcher, preparedValue []byte) ([]byte, error) { | ||
var valueToPropose []byte | ||
if roundChangeMsg.RoundChangePrepared() { | ||
valueToPropose = preparedValue | ||
} else { | ||
cd, err := cdFetcher.GetConsensusData() | ||
if err != nil { | ||
return nil, err | ||
} | ||
valueToPropose = cd | ||
} | ||
return valueToPropose, nil |
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.
Since the CDFetcher may update its value, we need to make sure that two different proposals for the same round can't be done. One way to do that is to check, in the validation of Round-Change messages, that the msg.Round
is strictly above our current round.
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 understand.. we call this once per round
if err := c.GetConfig().GetValueCheckF()(value); err != nil { | ||
return errors.Wrap(err, "value invalid") | ||
} | ||
func (c *Controller) StartNewInstance(height Height, cdFetcher *types.DataFetcher) 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.
Why not move the fetching logic outside of controller and pass an already fetched value? this of course also means determining that we are leaders outside of the controller. IMO it makes more sense than passing a fetcher. less changes to controller.
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.
thinking again this might not work if we need to fetch data after round change? but probably we should fetch data at the beginning of the slot regardless.
// chooseValueToPropose | ||
// If justifiedRoundChangeMsg has no prepare justification choose state value | ||
// If justifiedRoundChangeMsg has prepare justification choose prepared value | ||
func chooseValueToPropose(roundChangeMsg *Message, cdFetcher *types.DataFetcher, preparedValue []byte) ([]byte, 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.
since its only used in one place, this shouldn't be a method IMO, or at least the name should reflect that a long-awaited fetch is part of this function.
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.
you think its possible to do the fetching outside of the qbft controller?
@GalRogozinski (if/once you get back to this code) could you clarify/confirm whether this PR (after finished and merged) will support and be 100% compatible with the solution for the issue I described here (which we'll probably want to address at some point) ? I haven't looked through the code of this PR too much yet, but from cursory overview I think
lets consider the following scenario that shows when/why "value injection" might be needed:
|
Hey @iurii-ssv, Yeah, this PR is suppose to allow making async calls. So currently we need to wait for the next fork |
Description
Instance will now fetch consensus data only if it is the round leader.
Timer starts before data is fetched to ensure it is synchronized across committee nodes.