Skip to content

Conversation

bastienfaivre
Copy link
Contributor

Closes: #911


PR author checklist

For all contributors

For external contributors

@bastienfaivre bastienfaivre marked this pull request as draft May 2, 2025 10:06
Copy link

github-actions bot commented May 2, 2025

✅ Semver Check Passed

Great job! All semver violations have been resolved. This PR now complies with semantic versioning rules.

If you made version updates, please ensure that:

  • The version in Cargo.toml accurately reflects the nature of your changes
  • Any breaking changes are properly documented in BREAKING_CHANGES.md

@bastienfaivre
Copy link
Contributor Author

If we want to inform the engine and the application of the equivocation that occurred during a height, we can simply add the evidence map to the Decided event. This is done in commit 3b83e0f.
Now, if we want to know about the equivocation as soon as it occurs, we can throw an error for the vote and the proposal, respectively. This works well for the vote, since the engine (or more specifically, the driver) does not have to do anything other than return if it is an equivocation. However, this approach does not work for the proposal since, even if it is an equivocation, the driver still needs to execute multiplex_proposal (c.f. mux.rs), which, depending on the output, will need to perform something else in the driver that can also lead to an error. Therefore, we may need to carry two errors, which is inconvenient.
Another approach would be to define proper data structures at the same level as the evidence map so that the engine can check and clear them every time it processes a vote or a proposal.

What do you think? @romac @ancazamfir

Copy link

codecov bot commented May 5, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 0.00%. Comparing base (01420b4) to head (e85f987).

✅ All tests successful. No failed tests found.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #999   +/-   ##
===========================
===========================

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bastienfaivre bastienfaivre force-pushed the bastien/equivocation-evidence branch from 1f4c5ec to f7c1b9c Compare May 6, 2025 11:06
perform!(
co,
Effect::Decide(certificate, extensions, Default::default())
Effect::Decide(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add some evidence metrics above and also some error/warn logs?

@ancazamfir
Copy link
Contributor

ancazamfir commented May 6, 2025

If we want to inform the engine and the application of the equivocation that occurred during a height, we can simply add the evidence map to the Decided event. This is done in commit 3b83e0f.

Let't do this in a separate PR. We need to figure out if we send the actual equivocating votes/ proposals or just a statement "This node/address has equivocated". It depends on the app -- consensus trust level and if the app is "ready" to handle votes and proposals.

Now, if we want to know about the equivocation as soon as it occurs, we can throw an error for the vote and the proposal, respectively. This works well for the vote, since the engine (or more specifically, the driver) does not have to do anything other than return if it is an equivocation. However, this approach does not work for the proposal since, even if it is an equivocation, the driver still needs to execute multiplex_proposal (c.f. mux.rs), which, depending on the output, will need to perform something else in the driver that can also lead to an error. Therefore, we may need to carry two errors, which is inconvenient. Another approach would be to define proper data structures at the same level as the evidence map so that the engine can check and clear them every time it processes a vote or a proposal.

Yes, the latter is better. Not sure about clearing it though. So maybe we can check after applying a proposal or vote if "new" evidence is recorded in the driver. This is just for logging purposes at this point.

What do you think? @romac @ancazamfir

@romac
Copy link
Contributor

romac commented May 8, 2025

I opened https://github.com/informalsystems/malachite/pull/1009 which addresses some of my comments above and simplifies the implementation by only retrieving the evidence on decide, rather than emit events at the time when equivocation happens.

@ancazamfir
Copy link
Contributor

I opened #1009 which addresses some of my comments above and simplifies the implementation by only retrieving the evidence on decide, rather than emit events at the time when equivocation happens.

I think we should postpone sending the evidence to the app in decide. It is not clear to me the evidence form that the app could digest. I was proposing above to do this in a different PR once we have a conversation with app devs.

We should also log the evidence in consensus and update metrics. Ideally when it happens as the height may not decide and this information may prove important in debugging.

@bastienfaivre
Copy link
Contributor Author

@ancazamfir @romac What should this branch implement, then? Based on your perspectives, #1009 is already in better shape than this one.

@romac
Copy link
Contributor

romac commented May 9, 2025

See my comment in #1009.

bastienfaivre and others added 6 commits May 11, 2025 20:24
…n decide (#1009)

* refactor(code): Simplify implementation by only checking for evidence on decide

* feat: equivocation logs

* fix: missing update

* fix: debug -> warn

* fix: show full proposal and vote

* fix: temporary middlewares warnings

---------

Co-authored-by: Bastien Faivre <[email protected]>
use crate::TestBuilder;

#[tokio::test]
pub async fn equivocation_proposer() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@romac, the inject feature is not enough/complete. Indeed, injecting a ProposeValue message does not work as the engine/driver will not consider two propose values; therefore, no conflicting proposals are sent. If we want to force receiving a proposal via a NetworkEvent, it also does not work since we do not have access to the peer id and cannot sign the proposal.
Do you have an idea on how to fix this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah bummer… Let me think about it and I'll come back to you

Copy link
Contributor

@romac romac May 13, 2025

Choose a reason for hiding this comment

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

Maybe we can pass an unsigned message to inject and use Node::get_signing_provider to sign the message in the test framework?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For that, we need to have access to the app instance (on which we can actually call get_signing_provider), but I don't see where we can access it in the framework.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe an overkill but can you run once and collect the proposals from that run? And then run again (with same keys) and inject in the second run the proposal from the first?

Copy link
Contributor

Choose a reason for hiding this comment

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

The other question, does it make sense to do the tests in a separate PR and merge this one as is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe an overkill but can you run once and collect the proposals from that run? And then run again (with same keys) and inject in the second run the proposal from the first?

Do you mean hardcoding the messages? For example, for votes, retrieve data from such logs:

2025-05-13T12:08:41.926664Z DEBUG test{id=1}:node{id=2}: Event: Published(msg: Vote(SignedMessage { message: Vote { typ: Precommit, height: Height(2), round: Some(0), value: Val(ValueId(81418)), validator_address: Address(AD52C99C243AAB9755836D3055A4ECAA5A5D76C7), extension: None }, signature: Signature(Signature { R_bytes: "f75cf3e1a3ddafe8c340f8700b5e792f47c0fe3a0d364446ae206bf7f8c54e4b", s_bytes: "4610ac2c9868b57f50bdb9064d503e2145e22978dfbc67da9994bcf38224ce09" }) }))

That might work, but does the framework support specifying a key seed? @romac

I would likely prefer the inject approach, which might also be useful for other tests.

The other question, does it make sense to do the tests in a separate PR and merge this one as is?

That is technically possible, yes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you mean hardcoding the messages? For example, for votes, retrieve data from such logs:

I thought you could intercept the proposal (or vote) in the first run, save it in some test state and then inject it in the second run.

I would likely prefer the inject approach, which might also be useful for other tests.

You still do the inject but you need a properly signed (second) proposal

Copy link
Contributor

Choose a reason for hiding this comment

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

Nifty idea, I'll give it a go next week!

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.

code: Surface proposal and vote equivocation evidence

3 participants