feat: adding resharing per-domain#3326
Conversation
SimonRastikian
left a comment
There was a problem hiding this comment.
Not sure if I need to add requirements for validate_incoming_proposal. In fact the requirements there feel to me very randomly chosen and without clear basis. I wouldn't know if something is overdone or something is missing. However, I am pretty sure there is good logic behind them but it's not clear to me what.
Would need someone's wisdom on that.
| @@ -215,6 +215,7 @@ impl IntoContractType<Participants> for dtos::Participants { | |||
| impl IntoContractType<ThresholdParameters> for dtos::ThresholdParameters { | |||
| fn into_contract_type(self) -> ThresholdParameters { | |||
| ThresholdParameters::new_unvalidated(self.participants.into_contract_type(), self.threshold) | |||
There was a problem hiding this comment.
I did not want to change that but question to the public: Why is this a new_unvalidated instead of the normal new (with validation)?
There was a problem hiding this comment.
I think to allow low threshold values in tests that would otherwise not pass in production (e.g. >= 60%)
gilcu3
left a comment
There was a problem hiding this comment.
Thank you!
I have two main problems with the current solution.
- The first is that it tries to reuse
ThresholdParameters, while I think it now makes sense to have one type for the running state:
pub struct ThresholdParameters {
participants: Participants,
threshold: Threshold,
}
and another for the voting:
pub struct ThresholdParametersVote {
participants: Participants,
threshold: Threshold,
per_domain_thresholds: BTreeMap<DomainId, ReconstructionThreshold>,
}
- The second is that the way to achieve backward compatibility in this PR seems hacky, as it uses:
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
It should be better to define Compat versions of the modified struct in the interface crate, as we did with the previous migrations.
I am also torn about the overlay concept. Most manual resharing operations will anyway change the thresholds per domain (as they add or remove participants). On the other hand, automatic resharing operations (in the future) should have no problem creating the correct parameters. Therefore, it seems strictly better, to have a simpler system, to require that the votes always contain a ReconstructionThreshold per domain.
| /// Clears the per-domain overlay. Called when a resharing proposal has | ||
| /// been applied to the [`super::domain::DomainRegistry`] — the proposal | ||
| /// map is no longer meaningful as part of the running state's parameters. | ||
| pub fn clear_per_domain_thresholds(&mut self) { | ||
| self.per_domain_thresholds.clear(); | ||
| } |
There was a problem hiding this comment.
So this is needed because ThresholdParameters is used both for voting and for storing the current parameter set. Wouldn't it make sense to separate the types then?
There was a problem hiding this comment.
I agree it would be cleaner to keep ThresholdParameters as is and add something like
ProposedThresholdParameters { parameters, per_domain_thresholds } used only by vote_new_parameters, the votes map, and KeyEvent.
This will require some more refactor I prefer to push into a newer PR.
There was a problem hiding this comment.
+1 for separate structs for voting and storing parameters.
| /// Clears the per-domain overlay. Called when a resharing proposal has | ||
| /// been applied to the [`super::domain::DomainRegistry`] — the proposal | ||
| /// map is no longer meaningful as part of the running state's parameters. | ||
| pub fn clear_per_domain_thresholds(&mut self) { | ||
| self.per_domain_thresholds.clear(); | ||
| } |
There was a problem hiding this comment.
+1 for separate structs for voting and storing parameters.
| @@ -215,6 +215,7 @@ impl IntoContractType<Participants> for dtos::Participants { | |||
| impl IntoContractType<ThresholdParameters> for dtos::ThresholdParameters { | |||
| fn into_contract_type(self) -> ThresholdParameters { | |||
| ThresholdParameters::new_unvalidated(self.participants.into_contract_type(), self.threshold) | |||
There was a problem hiding this comment.
I think to allow low threshold values in tests that would otherwise not pass in production (e.g. >= 60%)
|
@anodar Validation is not the job of the DTO mapping, the contract that accepts the parameter will call a certain |
|
@claude review Pay attention to what in the changes is making the contract binary bigger, would be nice to keep the current limits but not a problem if we need to bump them a bit |
Pull request overviewReplaces the single Changes:
Reviewed changesPer-file summary
FindingsNon-blocking (nits, follow-ups, suggestions):
✅ Approved — subject to the binary-size tidy-up on the two |
anodar
left a comment
There was a problem hiding this comment.
LGTM, please fix the test as part of fixing conflict with main.
| use crate::primitives::test_utils::{NUM_PROTOCOLS, gen_participants}; | ||
| use crate::primitives::thresholds::Threshold; | ||
|
|
||
| /// Borsh round-trip: write a `ThresholdParametersVotes` in the OLD layout |
There was a problem hiding this comment.
It's not really testing it though is it? Seems to serialize ThresholdParameters and deserialize it into ThresholdParameters again. I don't see OldThresholdParametersVotes used here.
gilcu3
left a comment
There was a problem hiding this comment.
Partial review, dropped quite a few comments.
The most concerning for me are the current backward compatibility approach, and the interpretation of ThresholdParamters as cryptographic (while I believe it is only about governance now).
As I had mentioned in slack, we need to cleanup the 3.10 migrations before this can land.
| @@ -165,6 +168,68 @@ impl ThresholdParameters { | |||
| } | |||
| } | |||
There was a problem hiding this comment.
Isnt this wrong? I thought this type was the governance threshold after this PR?
I also don't understand the text:
so this type can never hold a meaningless overlay
The comment should just mention what it is
| /// Builder-style helper: replace the per-domain reconstruction-threshold | ||
| /// overlay. Convenient for constructing proposals (notably in tests). | ||
| pub fn with_per_domain_thresholds( | ||
| mut self, | ||
| per_domain_thresholds: BTreeMap<DomainId, ReconstructionThreshold>, | ||
| ) -> Self { | ||
| self.per_domain_thresholds = per_domain_thresholds; | ||
| self | ||
| } |
There was a problem hiding this comment.
if it happens to be used only in tests, we should feature gate it to save contract space.
| } | ||
| }, | ||
| "ProposedThresholdParameters": { | ||
| "description": "A proposed set of threshold parameters submitted to `vote_new_parameters`. Carries the new [`ThresholdParameters`] plus an optional per-domain `ReconstructionThreshold` overlay for the resharing it would trigger.", |
There was a problem hiding this comment.
What do you mean by "new [ThresholdParameters]" this is not accurate
| #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] | ||
| #[cfg_attr( | ||
| all(feature = "abi", not(target_arch = "wasm32")), | ||
| derive(schemars::JsonSchema) | ||
| )] | ||
| pub struct ProposedThresholdParameters { | ||
| #[serde(flatten)] | ||
| pub parameters: ThresholdParameters, | ||
| #[serde(default)] | ||
| pub per_domain_thresholds: BTreeMap<DomainId, ReconstructionThreshold>, | ||
| } |
There was a problem hiding this comment.
I don't see how we would remove #[serde(flatten)] after 3.12. Please make sure we have a migration path forward, using compat structs if needed
| pub struct ProposedThresholdParameters { | ||
| #[serde(flatten)] | ||
| pub parameters: ThresholdParameters, | ||
| #[serde(default)] | ||
| pub per_domain_thresholds: BTreeMap<DomainId, ReconstructionThreshold>, | ||
| } |
There was a problem hiding this comment.
Also, what's the reason for having this type duplicate? We are trying to avoid that to keep contract size low. I see that ThresholdParameters is also duplicate (not in this PR), but wondering if that's the only reason, because in that case we could solve it first
There was a problem hiding this comment.
Another PR needs to land so that these changes are made against the 3.11 state instead
| /// Enforces the 3.11-transition lock: every CaitSith domain (ForeignTx | ||
| /// included) must share a single `reconstruction_threshold` so the legacy | ||
| /// unprefixed `DBCol::Triple` mirror (#3292) can't collide. Domains using | ||
| /// other protocols are unconstrained. The first CaitSith domain encountered | ||
| /// fixes the expected value; any later CaitSith domain with a different | ||
| /// threshold is rejected. Remove once #3298 drops the mirror. | ||
| pub fn validate_caitsith_uniform_threshold(domains: &[DomainConfig]) -> Result<(), Error> { |
| #[expect(non_snake_case)] | ||
| mod per_domain_threshold_overlay { | ||
| use super::*; | ||
| use crate::state::key_event::tests::Environment; | ||
| use crate::state::test_utils::gen_running_state; | ||
| use near_mpc_contract_interface::types::ReconstructionThreshold; | ||
| use std::collections::BTreeMap; |
There was a problem hiding this comment.
why using a different mod for these tests?
Closes #3169