Skip to content
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

Message Validation #491

Open
wants to merge 24 commits into
base: dev
Choose a base branch
from
Open

Conversation

MatheusFranco99
Copy link
Contributor

@MatheusFranco99 MatheusFranco99 commented Aug 22, 2024

Overview

This PR updates the message validation module.

It adds a MessageValidator structure that implements the ValidatorForTopic function, as defined by the libp2p framework.

Its main validation function starts a chain of validation checks for the pub-sub, syntax, semantics, QBFT and duty logic rules.

Documentation and message rate estimation

The PR also updates the message validation and scoring documentation in p2p/SPEC.md and p2p/SCORING.md. The go code for the message rate estimation is added (along with tests) since this estimation is referenced by the scoring documentation.

Tests

This PR also adds tests for each implemented rule, as can be seen in the following list.

PubSub

  • PubSub message with empty data
  • PubSub message with a data bigger than the allowed size
  • PubSub message that can't decode a SignedSSVMessage

Syntax validation

  • No signers
  • No signatures
  • Wrong RSA signature size
  • Signers not sorted
  • Zero signer
  • Duplicated signer
  • Signers and signatures with different lengths
  • Nil SSVMessage. This test can't be implemented due to the internals of ssz decoding
  • Empty data
  • SSVMessage with data above the allowed limit. This test can't be implemented due to ssz encoding errors.
  • Undecodable SSVMessage.Data for consensus message
  • Undecodable SSVMessage.Data for partial signature message
  • Undecodable PrepareJustificaiton
  • Undecodable RoundChangeJustification

General semantics validation

  • Signer not in committee
  • Wrong domain
  • Invalid role
  • Unknown validator
  • Validator not attesting
  • Validator liquidated
  • Non-xistent CommitteeID
  • Incorrect topic
  • DKG Message
  • Unknown MessageType

Consensus semantics validation

  • Non-decided with multiple signers
  • Decided with not enough signers
  • Prepare with FullData
  • Commit with FullData
  • Invalid root
  • Unknown QBFT MessageType
  • Zero round
  • Mismatched identifier

Consensus QBFT logic validation

  • Signer not leader in proposal message
  • Decided with the same signers as previous message
  • Duplicated proposal with different FullData
  • Duplicated consensus message
  • Round not allowed considering time spread
  • Round already advanced
  • Unexpected RoundChangeJustification in Prepare message
  • Unexpected RoundChangeJustification in Commit message
  • Unexpected PrepareJustification in Prepare message
  • Unexpected PrepareJustification in Commit message
  • Unexpected PrepareJustification in RoundChange message

Consensus duty logic validation

  • Unexpected consensus message for ValidatorRegistration
  • Unexpected consensus message for VoluntaryExit
  • Round too high

Partial signature semantics validation

  • Multiple signers
  • With FullData
  • Invalid type
  • Type mismatch with role
  • No PartialSignatureMessages
  • Wrong partial signature size. This test can't be implemented due to ssz encoding errors
  • Inconsistent signers
  • Validator index mismatch considering the validator public key

Partial signature duty logic validation

  • Type count
  • Too many signatures
  • Triple ValidatorIndex

General duty logic validation

  • Slot already advanced
  • Message sent before slot starts
  • Late slot
  • No proposer duty
  • No SyncCommitteeContribution duty
  • Too many duties

Wrong signature

  • Wrong signature

Valid

  • Happy flow

y0sher and others added 19 commits July 22, 2024 16:23
* Filter shares for slot `CommitteeRunner` based on validators that have duty for that slot.

* Filter duty and create share map according to owned validators

* Add test: start duty with no shares for duty's validators

* Add test: happy flow for committee with fraction of duty's validators

* Generate JSON tests

* Apply suggestions

---------

Co-authored-by: MatheusFranco99 <[email protected]>
* Update go1.20 to go1.22

* Update go.sum with mod tidy
* Update dependencies

* Fix lint issue

* Generate JSON tests to trigger actions

* Update fastssz

* Generate JSON tests and align ssz error

* Revert go-eth2-client version change

* Revert fastssz upgrade

* Generate SSZ and JSON tests
* Solve potential file inclusion via variable

* Fix file permission (0644 to 0600)

* Add nosec comment for PRNG (pseudo-random number generator) used for testing

* Fix lint issue on nil check in []byte type

* Update permission from 0444 to 0600

* Update 0444 to 0400
* Remove nolint comment and export timeout variables

* Drop unnecessary nolint

* Add comment

* Fix lint issue
* Add share length validation in runner construction

* Align to error handling in runners constructions

* Add validation to committee runner

* Add runner construction tests

* Refactor runner construction in testingutil to deal with creation errors

* Generate JSON tests

* Fix lint issue

* Fix comments
* Remove redundant validation

* Align error string
* Sort signers in decided message

* Add test for sorted signers in decided msg

* Generate JSON tests

* Fix lint issue
* Stop processing consensus messages after instance is decided

* Align error in qbft tests

* Align errors in ssv tests

* Generate JSON tests

* Fix lint issue
* Remove leftover err check

* Align argument variable name to type
@alan-ssvlabs
Copy link

Maybe p2p/SPEC.md needs some update at line 268-283 about Basic Validation

@MatheusFranco99
Copy link
Contributor Author

@alan-ssvlabs Indeed. Thanks!

@MatheusFranco99
Copy link
Contributor Author

@GalRogozinski @alan-ssvlabs

In the p2p/SCORING.md file I have a code snippet for the message rate estimation. What do you guys think of creating a file with this code? The fact that p2p scoring needs it may be enough reason to add it. Also, the SSV repo uses exactly this function.

@MatheusFranco99
Copy link
Contributor Author

@GalRogozinski @alan-ssvlabs Moved the msg estimation code to a go file and added some tests.

@GalRogozinski GalRogozinski added the Networking P2P networking specs label Oct 13, 2024
@GalRogozinski
Copy link
Contributor

Fixes #453

Copy link
Contributor

@GalRogozinski GalRogozinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good!
Just a few comments

// Expected number of messages per duty step

func consensusMessages(n int) int {
return 1 + n + n + 2 // 1 Proposal + n Prepares + n Commits + 2 Decideds (average)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the average of 2 decides depends on n, no?

Comment on lines +44 to +58
func (test *MessageRateTest) Run(t *testing.T) {
for _, testCase := range test.TestCases {

// Build the list of committees
committees := make([]*messagerate.Committee, 0)
for _, committeesWithValidators := range testCase.CommitteesConfig {
committees = append(committees, testingutils.TestingDisjointCommittees(committeesWithValidators.NumCommittees, committeesWithValidators.NumValidators)...)
}

// Check result
expectedValue := testCase.ExpectedMessageRate
result := messagerate.EstimateMessageRateForTopic(committees)
require.InDelta(t, expectedValue, result, allowedError)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking whether those tests are spectests?

The point of spec tests is to make sure the spec didn't break and for nodes to test spec alignment.
Are those tests doing anything like this? You wrote that this was added since the documentation referenced the message rate.
Still... I think this belongs to the research repository where we do simulations


// Protocol configuration for the MessageValidator
type Config interface {
ProposerForHeightAndRound(height qbft.Height, round qbft.Round) types.OperatorID
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably need to know the committeeLength?

Usually for a single function we declare a function type.
What you did here works as well but maybe we should be consistent.
I would simply add a function to validator.go and add in a comment that its output is defined by the QBFT leader selection function for clarity

Comment on lines +8 to +12
type DutyFetcher interface {
HasProposerDuty(validatorIndex phase0.ValidatorIndex, slot phase0.Slot) bool
HasSyncCommitteeContributionDuty(validatorIndex phase0.ValidatorIndex, slot phase0.Slot) bool
HasSyncCommitteeDuty(validatorIndex phase0.ValidatorIndex, slot phase0.Slot) bool
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like something that would be as part of our BeaconNode interface

Comment on lines +12 to +15
ExistingValidator(validatorPK types.ValidatorPK) bool
ActiveValidator(validatorPK types.ValidatorPK) bool
ValidatorLiquidated(validatorPK types.ValidatorPK) bool
ExistingCommitteeID(committeeID types.CommitteeID) bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For those methods we query the contract right?
And for the others we don't, right? So maybe should be 2 interfaces?

I also think we should add comments for interfaces methods, especially ones we only implement as test methods

@@ -39,6 +39,7 @@ func LateCommit() tests.SpecTest {
ControllerPostState: sc.ExpectedState,
},
},
ExpectedError: "not processing consensus message since instance is already decided",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come all these messages were added?

Comment on lines +63 to +86
// Sort the OperatorIDs and Signatures in the SignedSSVMessage

pairs := make([]struct {
OpID types.OperatorID
Sig types.Signature
}, len(ret.OperatorIDs))

for i, id := range ret.OperatorIDs {
pairs[i] = struct {
OpID types.OperatorID
Sig types.Signature
}{OpID: id, Sig: ret.Signatures[i]}
}

// Sort the slice of pairs
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].OpID < pairs[j].OpID
})

// Extract the sorted IDs and Signatures back into separate slices
for i, pair := range pairs {
ret.OperatorIDs[i] = pair.OpID
ret.Signatures[i] = pair.Sig
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qbft changes because of message validation?

Comment on lines +32 to +37
) (Runner, error) {

if len(share) != 1 {
return nil, errors.New("must have one share")
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has nothing to do with the PR right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Networking P2P networking specs spec
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants