Skip to content

Conversation

@sstanculeanu
Copy link
Collaborator

Reasoning behind the pull request

  • as now all participants can aggregate signatures and send final info, the network might get flooded with the invalid signers messages

Proposed changes

  • added a mechanism similar to equivalent proofs to avoid further broadcast in case of known invalid signers message

Testing procedure

  • with rc branch

Pre-requisites

Based on the Contributing Guidelines the PR author and the reviewers must check the following requirements are met:

  • was the PR targeted to the correct branch?
  • if this is a larger feature that probably needs more than one PR, is there a feat branch created?
  • if this is a feat branch merging, do all satellite projects have a proper tag inside go.mod?

@sstanculeanu sstanculeanu self-assigned this Mar 12, 2025
cache.invalidSignersMap[hash] = struct{}{}
}

// HasInvalidSigners check whether the provided hash exists in int internal map or not
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo in int 🙃

Comment on lines 6 to 7
sync.RWMutex
invalidSignersMap map[string]struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure, maybe sync.Map would have also worked (shorter code)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

applied

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

back to map + mut due to reset method

Comment on lines 578 to 587
func (wrk *Worker) verifyMessageWithInvalidSigners(cnsMsg *consensus.Message) error {
// No need to guard this method by verification of common.EquivalentMessagesFlag as invalidSignersCache will have entries only for consensus v2
invalidSignersHash := wrk.hasher.Compute(string(cnsMsg.InvalidSigners))
if wrk.invalidSignersCache.HasInvalidSigners(string(invalidSignersHash)) {
// return error here to avoid further broadcast of this message
return ErrInvalidSignersAlreadyReceived
}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

So all good if someone sends a clandestine message with this topic shortly before Andromeda's activation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added check for flag to be active. used IsFlagEnabled as header epoch is unknown for this message

@raduchis raduchis self-requested a review March 12, 2025 12:01
func (sr *subroundEndRound) handleInvalidSignersOnAggSigFail() ([]byte, []byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), sr.RoundHandler().TimeDuration())
invalidPubKeys, err := sr.verifyNodesOnAggSigFail(ctx)
cancel()
Copy link
Contributor

Choose a reason for hiding this comment

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

not related to the changes from this PR, but this cancel() doesn't seem right to call it after the "intensive". Maybe move it up 1 line and call it on a defer?


invalidSignersHash := sr.Hasher().Compute(string(cnsDta.InvalidSigners))
invalidSignersCache := sr.InvalidSignersCache()
if invalidSignersCache.HasInvalidSigners(string(invalidSignersHash)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if there are different number of invalidSigners: first time 2: A and B, and in on a second message it gets only A, it seems that the message will be propagated 2 times?

}

if len(pubKey) > 0 {
pubKeys = append(pubKeys, pubKey)
Copy link
Contributor

Choose a reason for hiding this comment

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

So we only collect the public keys of the good signers? And if there's any invalid one, we collect nothing. Should we find a better name for the function e.g. findGoodSigners?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not really, this method checks whether the invalid signers provided an invalid signature.. so this appends the invalid signers that we confirmed they had an invalid signature

return err
pubKey, errVerify := sr.verifyInvalidSigner(msg)
if errVerify != nil {
return nil, errVerify
Copy link
Contributor

Choose a reason for hiding this comment

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

Thus, here we break & return, all good?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, it means we were not able to verify if the provided invalid signers indeed had invalid signatures

err := sr.MessageSigningHandler().Verify(msg)
if err != nil {
return err
return "", err
Copy link
Contributor

Choose a reason for hiding this comment

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

Note to ourselves - this is the raw byte pubkey, not the hex-encoded one (from what I understand). No change needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

correct

type invalidSignersCache struct {
sync.RWMutex
invalidSignersHashesMap map[string]struct{}
invalidSignersForHeaderMap map[string]map[string]struct{}
Copy link
Contributor

Choose a reason for hiding this comment

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

So, for each header, we retain the public keys of the "bad" signers (as a map, for easy lookup), correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

correct

Comment on lines +76 to +78
for _, pk := range invalidPublicKeys {
cache.invalidSignersForHeaderMap[string(headerHash)][pk] = struct{}{}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice automatic de-duplication, in case we get different sets of bad signers for the same header 👍

invalidSignersHash := cache.hasher.Compute(string(invalidSigners))
_, hasSameInvalidSigners := cache.invalidSignersHashesMap[string(invalidSignersHash)]
if hasSameInvalidSigners {
return true
Copy link
Contributor

Choose a reason for hiding this comment

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

Early answer if we've previously received the exact same message, correct? I mean, this is just an optimization - is it a crucial one?

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 out loud - if this node already knows about this exact set of invalid signers, for the given block, it will not forward the message / it will stop the gossip. Also, if the node does not know about this exact set, but knows about a superset of invalid signers (which include this set), it will also stop the gossip.

Correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

for the first question, it indeed is just an optimization to avoid the Deserialization

for the second one, correct

cnsMsg := &consensus.Message{}
err = cache.marshaller.Unmarshal(cnsMsg, msg.Data())
if err != nil {
return false
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunate / rare circumstance? If so, maybe we should log it (as warning perhaps)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hm.. not sure it is needed. worst case, the node will process twice the same message

return false
}

messages, err := cache.signingHandler.Deserialize(invalidSigners)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe can be renamed from messages to signers. Or even invalidSigners, while the existing variable can be renamed to serializedInvalidSigners.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

applied suggestion, renamed to invalidSignersP2PMessages

}
}

return knownInvalidSigners == len(messages)
Copy link
Contributor

Choose a reason for hiding this comment

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

Opinion (sorry if mistaken, I might have missed something): we can also return early if we find an unknown one, and skip the counting logic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good suggestion, applied

return knownInvalidSigners == len(messages)
}

// Reset clears the internal map
Copy link
Contributor

Choose a reason for hiding this comment

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

maps.

andreibancioiu
andreibancioiu previously approved these changes Mar 13, 2025
@sstanculeanu sstanculeanu merged commit f7f185c into feat/andromeda-fixes Mar 17, 2025
4 checks passed
@sstanculeanu sstanculeanu deleted the invalid_signers_cache branch March 17, 2025 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants