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

identities: refactor is_malicious check to read minimal amount of data #6346

Closed
wants to merge 6 commits into from

Conversation

dshulyak
Copy link
Contributor

@dshulyak dshulyak commented Sep 23, 2024

part of #6326

in the current implementation IsMalicious has to read whole identities table twice whenever new atx is received.
even if it table relatively small (not GB), it has to scan whole table on every query, best case it is served from memory and then only wastes cpu, in worst case it goes to disk if total amount of memory is low.

changes:

  • migration to add rowid to identities table, because the table stores a "large" proof and soon it will store many marriages, querying it without rowid is very inefficient
  • two new indexes, index that has only malicious identities, this will make IsMalicious check to read as little as possible from database, and will likely never hit the disk. index with marriages, this is used to filter or update identities with same marriage, as otherwise any query will have to scan whole identities table
  • refactor to invoke IsMalicious once
  • mark any new identity as malicious if marriage is malicious, while proof is stored in one place wherever original equivocation happened
  • mark all identities within marriage as malicious, when any becomes malicious

@dshulyak dshulyak marked this pull request as ready for review September 24, 2024 07:17
@dshulyak
Copy link
Contributor Author

bors try

spacemesh-bors bot added a commit that referenced this pull request Sep 24, 2024
hare3/hare.go Outdated
Comment on lines 342 to 346
err := h.db.WithTx(context.Background(), func(tx sql.Transaction) error {
return identities.SetMalicious(
h.db, equivocation.Messages[0].SmesherID, codec.MustEncode(proof), time.Now())
})
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain why a transaction is used? Btw, the tx isn't used as h.db is passed to identities.SetMalicious() - is it on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SetMalicious runs more than one statement, if they are executed without transaction then on failure the state will be inconsistent, e.g only one of the atxs will be marked as malicious

Copy link
Contributor

Choose a reason for hiding this comment

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

OK makes sense, but in this case it would be great if SetMalicious enforced being run in a transaction by taking a sql.Transaction type instead of sql.Executor, wdyt? Such API would prevent mistakes like in this snippet.

Comment on lines 685 to 697
if atx.MarriageATX != nil {
receivedProof, err := identities.IsMarriageMalicious(tx, *atx.MarriageATX)
if err != nil {
return false, fmt.Errorf("checking if marriage ATX is malicious: %w", err)
}
if receivedProof != nil {
err = identities.SetMaliciousBecauseOfMarriage(tx, atx.SmesherID, *receivedProof)
if err != nil {
return false, fmt.Errorf("setting node as malicious because of marriage: %w", err)
}
return true, nil
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this might not be sufficient. The marriage ATX (the one that marries IDs - it includes the marriage certificates) must also be marked as malicious when it is processed if any of the married IDs is already malicious. This was previously covered by matching by marriage ATX in the IsMalicious() func.

Copy link
Contributor Author

@dshulyak dshulyak Sep 24, 2024

Choose a reason for hiding this comment

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

so there are 2 things now:

  • all identities with same marriage_atx that are present in db at the moment of SetMalicious are marked malicious
  • if some identity is learned after SetMalicious and it has same marriage_atx we update it here as malicious

it doesn't cover the case you described?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so the case is MarriageAtx is nil, so that it doesn't point to itself?
but instead it has some certificates, does it work if pick id of such atx as marriage_atx and run everything the same way?

although i don't see that covered in this query

	SELECT 1 FROM identities
	WHERE (
		marriage_atx = (SELECT marriage_atx FROM identities WHERE pubkey = ?1 AND marriage_atx IS NOT NULL)
		AND proof IS NOT NULL
	)
	OR (pubkey = ?1 AND marriage_atx IS NULL AND proof IS NOT NULL);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i feel stupid there is even a failing test and i can't figure what is going on

Copy link
Member

Choose a reason for hiding this comment

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

The problem is that an ATX that contains a marriage and is syntactically valid marks all identities in the marriage set as malicious if at least one identity in the set is already malicious.

I do have a fix for the problem but it requires calling IsMalicious on all identities in a marriage ATX before updating the marriage set (which I think is OK, it should be much faster then inserting the knowledge about the set anyway)

@spacemesh-bors
Copy link

try

Build failed:

hare4/hare.go Outdated Show resolved Hide resolved
activation/handler_v1.go Outdated Show resolved Hide resolved
Copy link
Member

@fasmat fasmat left a comment

Choose a reason for hiding this comment

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

For now I believe this is good enough to merge. For the new malfeasance syncer we probably need to revisit the DB logic without making the performance of the ATX handler worse:

  1. When a peer asks for the set of identities that are malicious we should reconciliate on the full set of identities (not just one identity per marriage set), because nodes might not agree on who's part of a set and who is not
  2. Per marriage set we only send one proof to a peer asking together with a list of identities we consider to also be part of the marriage set the identity we proved malicious belongs to. This list might be incomplete in the view of the requesting node (it might now that additional identities belong to any marriage set).
  3. All proofs served must cover the full set the nodes reconciliated on.

Example: two nodes compare their local list of malicious identities and node A realizies it is missing proofs for identities 100 - 120 while node B realizes that it doesn't consider identities 200 - 210 malfeasant yet

Node A requests proof for identity 100 from node B, node b sends proof together with proof that identities 101 - 110 also belong to that set. Node A now has proof for 100 - 110 and continues with 111 until it has proofs for all identities that B claims to know to be malfeasant and A doesn't.
Node B then requests proof for identity 200, Node A serves a proof for identity 220 (which it already knows to be malfeasant) and proof that 200 belongs to the same marriage set, it just updates it local set and continues from there.

Copy link
Member

@fasmat fasmat left a comment

Choose a reason for hiding this comment

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

After thinking about the change again I think I have identified issues with the way we are updating information about the marriage set and just fixing the failing test is not sufficient to address those 🙁

@fasmat
Copy link
Member

fasmat commented Oct 8, 2024

Superseded by #6378

@fasmat fasmat closed this Oct 8, 2024
@fasmat fasmat deleted the simplify-is-malicious branch October 8, 2024 18:31
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.

3 participants