mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-24 09:01:08 +00:00
Only report concluded if there is an actual dispute. (#6270)
* Only report concluded if there is an actual dispute. Hence no "non"-disputes will be added to disputes anymore. * Fix redundant check. * Test for no onesided disputes. Co-authored-by: eskimor <eskimor@no-such-url.com>
This commit is contained in:
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
|
|
||||||
use polkadot_node_primitives::{CandidateVotes, SignedDisputeStatement};
|
use polkadot_node_primitives::{CandidateVotes, DisputeStatus, SignedDisputeStatement, Timestamp};
|
||||||
use polkadot_node_subsystem_util::rolling_session_window::RollingSessionWindow;
|
use polkadot_node_subsystem_util::rolling_session_window::RollingSessionWindow;
|
||||||
use polkadot_primitives::v2::{
|
use polkadot_primitives::v2::{
|
||||||
CandidateReceipt, DisputeStatement, IndexedVec, SessionIndex, SessionInfo,
|
CandidateReceipt, DisputeStatement, IndexedVec, SessionIndex, SessionInfo,
|
||||||
@@ -154,22 +154,8 @@ pub struct CandidateVoteState<Votes> {
|
|||||||
/// Information about own votes:
|
/// Information about own votes:
|
||||||
own_vote: OwnVoteState,
|
own_vote: OwnVoteState,
|
||||||
|
|
||||||
/// Whether or not the dispute concluded invalid.
|
/// Current dispute status, if there is any.
|
||||||
concluded_invalid: bool,
|
dispute_status: Option<DisputeStatus>,
|
||||||
|
|
||||||
/// Whether or not the dispute concluded valid.
|
|
||||||
///
|
|
||||||
/// Note: Due to equivocations it is technically possible for a dispute to conclude both valid
|
|
||||||
/// and invalid. In that case the invalid result takes precedence.
|
|
||||||
concluded_valid: bool,
|
|
||||||
|
|
||||||
/// There is an ongoing dispute and we reached f+1 votes -> the dispute is confirmed
|
|
||||||
///
|
|
||||||
/// as at least one honest validator cast a vote for the candidate.
|
|
||||||
is_confirmed: bool,
|
|
||||||
|
|
||||||
/// Whether or not we have an ongoing dispute.
|
|
||||||
is_disputed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CandidateVoteState<CandidateVotes> {
|
impl CandidateVoteState<CandidateVotes> {
|
||||||
@@ -179,18 +165,11 @@ impl CandidateVoteState<CandidateVotes> {
|
|||||||
pub fn new_from_receipt(candidate_receipt: CandidateReceipt) -> Self {
|
pub fn new_from_receipt(candidate_receipt: CandidateReceipt) -> Self {
|
||||||
let votes =
|
let votes =
|
||||||
CandidateVotes { candidate_receipt, valid: BTreeMap::new(), invalid: BTreeMap::new() };
|
CandidateVotes { candidate_receipt, valid: BTreeMap::new(), invalid: BTreeMap::new() };
|
||||||
Self {
|
Self { votes, own_vote: OwnVoteState::NoVote, dispute_status: None }
|
||||||
votes,
|
|
||||||
own_vote: OwnVoteState::NoVote,
|
|
||||||
concluded_invalid: false,
|
|
||||||
concluded_valid: false,
|
|
||||||
is_confirmed: false,
|
|
||||||
is_disputed: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new `CandidateVoteState` from already existing votes.
|
/// Create a new `CandidateVoteState` from already existing votes.
|
||||||
pub fn new<'a>(votes: CandidateVotes, env: &CandidateEnvironment<'a>) -> Self {
|
pub fn new<'a>(votes: CandidateVotes, env: &CandidateEnvironment<'a>, now: Timestamp) -> Self {
|
||||||
let own_vote = OwnVoteState::new(&votes, env);
|
let own_vote = OwnVoteState::new(&votes, env);
|
||||||
|
|
||||||
let n_validators = env.validators().len();
|
let n_validators = env.validators().len();
|
||||||
@@ -198,16 +177,31 @@ impl CandidateVoteState<CandidateVotes> {
|
|||||||
let supermajority_threshold =
|
let supermajority_threshold =
|
||||||
polkadot_primitives::v2::supermajority_threshold(n_validators);
|
polkadot_primitives::v2::supermajority_threshold(n_validators);
|
||||||
|
|
||||||
let concluded_invalid = votes.invalid.len() >= supermajority_threshold;
|
|
||||||
let concluded_valid = votes.valid.len() >= supermajority_threshold;
|
|
||||||
|
|
||||||
// We have a dispute, if we have votes on both sides:
|
// We have a dispute, if we have votes on both sides:
|
||||||
let is_disputed = !votes.invalid.is_empty() && !votes.valid.is_empty();
|
let is_disputed = !votes.invalid.is_empty() && !votes.valid.is_empty();
|
||||||
|
|
||||||
let byzantine_threshold = polkadot_primitives::v2::byzantine_threshold(n_validators);
|
let dispute_status = if is_disputed {
|
||||||
let is_confirmed = votes.voted_indices().len() > byzantine_threshold && is_disputed;
|
let mut status = DisputeStatus::active();
|
||||||
|
let byzantine_threshold = polkadot_primitives::v2::byzantine_threshold(n_validators);
|
||||||
|
let is_confirmed = votes.voted_indices().len() > byzantine_threshold;
|
||||||
|
if is_confirmed {
|
||||||
|
status = status.confirm();
|
||||||
|
};
|
||||||
|
let concluded_for = votes.valid.len() >= supermajority_threshold;
|
||||||
|
if concluded_for {
|
||||||
|
status = status.conclude_for(now);
|
||||||
|
};
|
||||||
|
|
||||||
Self { votes, own_vote, concluded_invalid, concluded_valid, is_confirmed, is_disputed }
|
let concluded_against = votes.invalid.len() >= supermajority_threshold;
|
||||||
|
if concluded_against {
|
||||||
|
status = status.conclude_against(now);
|
||||||
|
};
|
||||||
|
Some(status)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { votes, own_vote, dispute_status }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import fresh statements.
|
/// Import fresh statements.
|
||||||
@@ -217,6 +211,7 @@ impl CandidateVoteState<CandidateVotes> {
|
|||||||
self,
|
self,
|
||||||
env: &CandidateEnvironment,
|
env: &CandidateEnvironment,
|
||||||
statements: Vec<(SignedDisputeStatement, ValidatorIndex)>,
|
statements: Vec<(SignedDisputeStatement, ValidatorIndex)>,
|
||||||
|
now: Timestamp,
|
||||||
) -> ImportResult {
|
) -> ImportResult {
|
||||||
let (mut votes, old_state) = self.into_old_state();
|
let (mut votes, old_state) = self.into_old_state();
|
||||||
|
|
||||||
@@ -294,7 +289,7 @@ impl CandidateVoteState<CandidateVotes> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_state = Self::new(votes, env);
|
let new_state = Self::new(votes, env, now);
|
||||||
|
|
||||||
ImportResult {
|
ImportResult {
|
||||||
old_state,
|
old_state,
|
||||||
@@ -313,32 +308,15 @@ impl CandidateVoteState<CandidateVotes> {
|
|||||||
|
|
||||||
/// Extract `CandidateVotes` for handling import of new statements.
|
/// Extract `CandidateVotes` for handling import of new statements.
|
||||||
fn into_old_state(self) -> (CandidateVotes, CandidateVoteState<()>) {
|
fn into_old_state(self) -> (CandidateVotes, CandidateVoteState<()>) {
|
||||||
let CandidateVoteState {
|
let CandidateVoteState { votes, own_vote, dispute_status } = self;
|
||||||
votes,
|
(votes, CandidateVoteState { votes: (), own_vote, dispute_status })
|
||||||
own_vote,
|
|
||||||
concluded_invalid,
|
|
||||||
concluded_valid,
|
|
||||||
is_confirmed,
|
|
||||||
is_disputed,
|
|
||||||
} = self;
|
|
||||||
(
|
|
||||||
votes,
|
|
||||||
CandidateVoteState {
|
|
||||||
votes: (),
|
|
||||||
own_vote,
|
|
||||||
concluded_invalid,
|
|
||||||
concluded_valid,
|
|
||||||
is_confirmed,
|
|
||||||
is_disputed,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> CandidateVoteState<V> {
|
impl<V> CandidateVoteState<V> {
|
||||||
/// Whether or not we have an ongoing dispute.
|
/// Whether or not we have an ongoing dispute.
|
||||||
pub fn is_disputed(&self) -> bool {
|
pub fn is_disputed(&self) -> bool {
|
||||||
self.is_disputed
|
self.dispute_status.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether there is an ongoing confirmed dispute.
|
/// Whether there is an ongoing confirmed dispute.
|
||||||
@@ -346,7 +324,7 @@ impl<V> CandidateVoteState<V> {
|
|||||||
/// This checks whether there is a dispute ongoing and we have more than byzantine threshold
|
/// This checks whether there is a dispute ongoing and we have more than byzantine threshold
|
||||||
/// votes.
|
/// votes.
|
||||||
pub fn is_confirmed(&self) -> bool {
|
pub fn is_confirmed(&self) -> bool {
|
||||||
self.is_confirmed
|
self.dispute_status.map_or(false, |s| s.is_confirmed_concluded())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This machine already cast some vote in that dispute/for that candidate.
|
/// This machine already cast some vote in that dispute/for that candidate.
|
||||||
@@ -359,14 +337,19 @@ impl<V> CandidateVoteState<V> {
|
|||||||
self.own_vote.approval_votes()
|
self.own_vote.approval_votes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this dispute has already enough valid votes to conclude.
|
/// Whether or not there is a dispute and it has already enough valid votes to conclude.
|
||||||
pub fn is_concluded_valid(&self) -> bool {
|
pub fn has_concluded_for(&self) -> bool {
|
||||||
self.concluded_valid
|
self.dispute_status.map_or(false, |s| s.has_concluded_for())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this dispute has already enough invalid votes to conclude.
|
/// Whether or not there is a dispute and it has already enough invalid votes to conclude.
|
||||||
pub fn is_concluded_invalid(&self) -> bool {
|
pub fn has_concluded_against(&self) -> bool {
|
||||||
self.concluded_invalid
|
self.dispute_status.map_or(false, |s| s.has_concluded_against())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get access to the dispute status, in case there is one.
|
||||||
|
pub fn dispute_status(&self) -> &Option<DisputeStatus> {
|
||||||
|
&self.dispute_status
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access to underlying votes.
|
/// Access to underlying votes.
|
||||||
@@ -451,18 +434,18 @@ impl ImportResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not any dispute just concluded valid due to the import.
|
/// Whether or not any dispute just concluded valid due to the import.
|
||||||
pub fn is_freshly_concluded_valid(&self) -> bool {
|
pub fn is_freshly_concluded_for(&self) -> bool {
|
||||||
!self.old_state().is_concluded_valid() && self.new_state().is_concluded_valid()
|
!self.old_state().has_concluded_for() && self.new_state().has_concluded_for()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not any dispute just concluded invalid due to the import.
|
/// Whether or not any dispute just concluded invalid due to the import.
|
||||||
pub fn is_freshly_concluded_invalid(&self) -> bool {
|
pub fn is_freshly_concluded_against(&self) -> bool {
|
||||||
!self.old_state().is_concluded_invalid() && self.new_state().is_concluded_invalid()
|
!self.old_state().has_concluded_against() && self.new_state().has_concluded_against()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not any dispute just concluded either invalid or valid due to the import.
|
/// Whether or not any dispute just concluded either invalid or valid due to the import.
|
||||||
pub fn is_freshly_concluded(&self) -> bool {
|
pub fn is_freshly_concluded(&self) -> bool {
|
||||||
self.is_freshly_concluded_invalid() || self.is_freshly_concluded_valid()
|
self.is_freshly_concluded_against() || self.is_freshly_concluded_for()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modify this `ImportResult`s, by importing additional approval votes.
|
/// Modify this `ImportResult`s, by importing additional approval votes.
|
||||||
@@ -473,6 +456,7 @@ impl ImportResult {
|
|||||||
self,
|
self,
|
||||||
env: &CandidateEnvironment,
|
env: &CandidateEnvironment,
|
||||||
approval_votes: HashMap<ValidatorIndex, ValidatorSignature>,
|
approval_votes: HashMap<ValidatorIndex, ValidatorSignature>,
|
||||||
|
now: Timestamp,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let Self {
|
let Self {
|
||||||
old_state,
|
old_state,
|
||||||
@@ -508,7 +492,7 @@ impl ImportResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_state = CandidateVoteState::new(votes, env);
|
let new_state = CandidateVoteState::new(votes, env, now);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
old_state,
|
old_state,
|
||||||
|
|||||||
@@ -748,7 +748,7 @@ impl Initialized {
|
|||||||
.load_candidate_votes(session, &candidate_hash)?
|
.load_candidate_votes(session, &candidate_hash)?
|
||||||
.map(CandidateVotes::from)
|
.map(CandidateVotes::from)
|
||||||
{
|
{
|
||||||
Some(votes) => CandidateVoteState::new(votes, &env),
|
Some(votes) => CandidateVoteState::new(votes, &env, now),
|
||||||
None =>
|
None =>
|
||||||
if let MaybeCandidateReceipt::Provides(candidate_receipt) = candidate_receipt {
|
if let MaybeCandidateReceipt::Provides(candidate_receipt) = candidate_receipt {
|
||||||
CandidateVoteState::new_from_receipt(candidate_receipt)
|
CandidateVoteState::new_from_receipt(candidate_receipt)
|
||||||
@@ -766,7 +766,7 @@ impl Initialized {
|
|||||||
gum::trace!(target: LOG_TARGET, ?candidate_hash, ?session, "Loaded votes");
|
gum::trace!(target: LOG_TARGET, ?candidate_hash, ?session, "Loaded votes");
|
||||||
|
|
||||||
let import_result = {
|
let import_result = {
|
||||||
let intermediate_result = old_state.import_statements(&env, statements);
|
let intermediate_result = old_state.import_statements(&env, statements, now);
|
||||||
|
|
||||||
// Handle approval vote import:
|
// Handle approval vote import:
|
||||||
//
|
//
|
||||||
@@ -803,7 +803,7 @@ impl Initialized {
|
|||||||
);
|
);
|
||||||
intermediate_result
|
intermediate_result
|
||||||
},
|
},
|
||||||
Ok(votes) => intermediate_result.import_approval_votes(&env, votes),
|
Ok(votes) => intermediate_result.import_approval_votes(&env, votes, now),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gum::trace!(
|
gum::trace!(
|
||||||
@@ -977,43 +977,34 @@ impl Initialized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// All good, update recent disputes if state has changed:
|
// All good, update recent disputes if state has changed:
|
||||||
if import_result.dispute_state_changed() {
|
if let Some(new_status) = new_state.dispute_status() {
|
||||||
let mut recent_disputes = overlay_db.load_recent_disputes()?.unwrap_or_default();
|
// Only bother with db access, if there was an actual change.
|
||||||
|
if import_result.dispute_state_changed() {
|
||||||
|
let mut recent_disputes = overlay_db.load_recent_disputes()?.unwrap_or_default();
|
||||||
|
|
||||||
let status = recent_disputes.entry((session, candidate_hash)).or_insert_with(|| {
|
let status =
|
||||||
gum::info!(
|
recent_disputes.entry((session, candidate_hash)).or_insert_with(|| {
|
||||||
|
gum::info!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
?candidate_hash,
|
||||||
|
session,
|
||||||
|
"New dispute initiated for candidate.",
|
||||||
|
);
|
||||||
|
DisputeStatus::active()
|
||||||
|
});
|
||||||
|
|
||||||
|
*status = *new_status;
|
||||||
|
|
||||||
|
gum::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?candidate_hash,
|
?candidate_hash,
|
||||||
session,
|
?status,
|
||||||
"New dispute initiated for candidate.",
|
has_concluded_for = ?new_state.has_concluded_for(),
|
||||||
|
has_concluded_against = ?new_state.has_concluded_against(),
|
||||||
|
"Writing recent disputes with updates for candidate"
|
||||||
);
|
);
|
||||||
DisputeStatus::active()
|
overlay_db.write_recent_disputes(recent_disputes);
|
||||||
});
|
|
||||||
|
|
||||||
if new_state.is_confirmed() {
|
|
||||||
*status = status.confirm();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: concluded-invalid overwrites concluded-valid,
|
|
||||||
// so we do this check first. Dispute state machine is
|
|
||||||
// non-commutative.
|
|
||||||
if new_state.is_concluded_valid() {
|
|
||||||
*status = status.concluded_for(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_state.is_concluded_invalid() {
|
|
||||||
*status = status.concluded_against(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
gum::trace!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
?candidate_hash,
|
|
||||||
?status,
|
|
||||||
is_concluded_valid = ?new_state.is_concluded_valid(),
|
|
||||||
is_concluded_invalid = ?new_state.is_concluded_invalid(),
|
|
||||||
"Writing recent disputes with updates for candidate"
|
|
||||||
);
|
|
||||||
overlay_db.write_recent_disputes(recent_disputes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update metrics:
|
// Update metrics:
|
||||||
@@ -1036,7 +1027,7 @@ impl Initialized {
|
|||||||
);
|
);
|
||||||
|
|
||||||
self.metrics.on_approval_votes(import_result.imported_approval_votes());
|
self.metrics.on_approval_votes(import_result.imported_approval_votes());
|
||||||
if import_result.is_freshly_concluded_valid() {
|
if import_result.is_freshly_concluded_for() {
|
||||||
gum::info!(
|
gum::info!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?candidate_hash,
|
?candidate_hash,
|
||||||
@@ -1045,7 +1036,7 @@ impl Initialized {
|
|||||||
);
|
);
|
||||||
self.metrics.on_concluded_valid();
|
self.metrics.on_concluded_valid();
|
||||||
}
|
}
|
||||||
if import_result.is_freshly_concluded_invalid() {
|
if import_result.is_freshly_concluded_against() {
|
||||||
gum::info!(
|
gum::info!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
?candidate_hash,
|
?candidate_hash,
|
||||||
|
|||||||
@@ -2917,6 +2917,68 @@ fn redundant_votes_ignored() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Make sure no disputes are recorded when there are no opposing votes, even if we reached supermajority.
|
||||||
|
fn no_onesided_disputes() {
|
||||||
|
test_harness(|mut test_state, mut virtual_overseer| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let session = 1;
|
||||||
|
|
||||||
|
test_state.handle_resume_sync(&mut virtual_overseer, session).await;
|
||||||
|
|
||||||
|
let candidate_receipt = make_valid_candidate_receipt();
|
||||||
|
let candidate_hash = candidate_receipt.hash();
|
||||||
|
test_state
|
||||||
|
.activate_leaf_at_session(&mut virtual_overseer, session, 1, Vec::new())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut statements = Vec::new();
|
||||||
|
for index in 1..10 {
|
||||||
|
statements.push((
|
||||||
|
test_state
|
||||||
|
.issue_backing_statement_with_index(
|
||||||
|
ValidatorIndex(index),
|
||||||
|
candidate_hash,
|
||||||
|
session,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
ValidatorIndex(index),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (pending_confirmation, confirmation_rx) = oneshot::channel();
|
||||||
|
virtual_overseer
|
||||||
|
.send(FromOrchestra::Communication {
|
||||||
|
msg: DisputeCoordinatorMessage::ImportStatements {
|
||||||
|
candidate_receipt: candidate_receipt.clone(),
|
||||||
|
session,
|
||||||
|
statements,
|
||||||
|
pending_confirmation: Some(pending_confirmation),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_matches!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport));
|
||||||
|
|
||||||
|
// We should not have any active disputes now.
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
virtual_overseer
|
||||||
|
.send(FromOrchestra::Communication {
|
||||||
|
msg: DisputeCoordinatorMessage::ActiveDisputes(tx),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(rx.await.unwrap().is_empty());
|
||||||
|
|
||||||
|
virtual_overseer.send(FromOrchestra::Signal(OverseerSignal::Conclude)).await;
|
||||||
|
|
||||||
|
// No more messages expected:
|
||||||
|
assert!(virtual_overseer.try_recv().await.is_none());
|
||||||
|
|
||||||
|
test_state
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn refrain_from_participation() {
|
fn refrain_from_participation() {
|
||||||
test_harness(|mut test_state, mut virtual_overseer| {
|
test_harness(|mut test_state, mut virtual_overseer| {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ use polkadot_primitives::v2::{
|
|||||||
///
|
///
|
||||||
/// And most likely has been constructed correctly. This is used with
|
/// And most likely has been constructed correctly. This is used with
|
||||||
/// `DisputeDistributionMessage::SendDispute` for sending out votes.
|
/// `DisputeDistributionMessage::SendDispute` for sending out votes.
|
||||||
|
///
|
||||||
|
/// NOTE: This is sent over the wire, any changes are a change in protocol and need to be
|
||||||
|
/// versioned.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DisputeMessage(UncheckedDisputeMessage);
|
pub struct DisputeMessage(UncheckedDisputeMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,12 @@ use parity_scale_codec::{Decode, Encode};
|
|||||||
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS reboots.
|
/// Timestamp based on the 1 Jan 1970 UNIX base, which is persistent across node restarts and OS reboots.
|
||||||
pub type Timestamp = u64;
|
pub type Timestamp = u64;
|
||||||
|
|
||||||
/// The status of dispute. This is a state machine which can be altered by the
|
/// The status of dispute.
|
||||||
/// helper methods.
|
///
|
||||||
|
/// As managed by the dispute coordinator.
|
||||||
|
///
|
||||||
|
/// NOTE: This status is persisted to the database, any changes have to be versioned and a db
|
||||||
|
/// migration will be needed.
|
||||||
#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq)]
|
#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq)]
|
||||||
pub enum DisputeStatus {
|
pub enum DisputeStatus {
|
||||||
/// The dispute is active and unconcluded.
|
/// The dispute is active and unconcluded.
|
||||||
@@ -69,9 +73,24 @@ impl DisputeStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Concluded valid?
|
||||||
|
pub fn has_concluded_for(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&DisputeStatus::ConcludedFor(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Concluded invalid?
|
||||||
|
pub fn has_concluded_against(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&DisputeStatus::ConcludedAgainst(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Transition the status to a new status after observing the dispute has concluded for the candidate.
|
/// Transition the status to a new status after observing the dispute has concluded for the candidate.
|
||||||
/// This may be a no-op if the status was already concluded.
|
/// This may be a no-op if the status was already concluded.
|
||||||
pub fn concluded_for(self, now: Timestamp) -> DisputeStatus {
|
pub fn conclude_for(self, now: Timestamp) -> DisputeStatus {
|
||||||
match self {
|
match self {
|
||||||
DisputeStatus::Active | DisputeStatus::Confirmed => DisputeStatus::ConcludedFor(now),
|
DisputeStatus::Active | DisputeStatus::Confirmed => DisputeStatus::ConcludedFor(now),
|
||||||
DisputeStatus::ConcludedFor(at) => DisputeStatus::ConcludedFor(std::cmp::min(at, now)),
|
DisputeStatus::ConcludedFor(at) => DisputeStatus::ConcludedFor(std::cmp::min(at, now)),
|
||||||
@@ -81,7 +100,7 @@ impl DisputeStatus {
|
|||||||
|
|
||||||
/// Transition the status to a new status after observing the dispute has concluded against the candidate.
|
/// Transition the status to a new status after observing the dispute has concluded against the candidate.
|
||||||
/// This may be a no-op if the status was already concluded.
|
/// This may be a no-op if the status was already concluded.
|
||||||
pub fn concluded_against(self, now: Timestamp) -> DisputeStatus {
|
pub fn conclude_against(self, now: Timestamp) -> DisputeStatus {
|
||||||
match self {
|
match self {
|
||||||
DisputeStatus::Active | DisputeStatus::Confirmed =>
|
DisputeStatus::Active | DisputeStatus::Confirmed =>
|
||||||
DisputeStatus::ConcludedAgainst(now),
|
DisputeStatus::ConcludedAgainst(now),
|
||||||
|
|||||||
Reference in New Issue
Block a user