Unverified Commit dc39e1c7 authored by asynchronous rob's avatar asynchronous rob Committed by GitHub
Browse files

remove statement::invalid (#2597)

parent 378d3831
Pipeline #127689 passed with stages
in 32 minutes and 40 seconds
...@@ -197,7 +197,6 @@ fn primitive_statement_to_table(s: &SignedFullStatement) -> TableSignedStatement ...@@ -197,7 +197,6 @@ fn primitive_statement_to_table(s: &SignedFullStatement) -> TableSignedStatement
let statement = match s.payload() { let statement = match s.payload() {
Statement::Seconded(c) => TableStatement::Seconded(c.clone()), Statement::Seconded(c) => TableStatement::Seconded(c.clone()),
Statement::Valid(h) => TableStatement::Valid(h.clone()), Statement::Valid(h) => TableStatement::Valid(h.clone()),
Statement::Invalid(h) => TableStatement::Invalid(h.clone()),
}; };
TableSignedStatement { TableSignedStatement {
...@@ -555,14 +554,11 @@ impl CandidateBackingJob { ...@@ -555,14 +554,11 @@ impl CandidateBackingJob {
ValidatedCandidateCommand::Attest(res) => { ValidatedCandidateCommand::Attest(res) => {
// sanity check. // sanity check.
if !self.issued_statements.contains(&candidate_hash) { if !self.issued_statements.contains(&candidate_hash) {
let statement = if res.is_ok() { if res.is_ok() {
Statement::Valid(candidate_hash) let statement = Statement::Valid(candidate_hash);
} else { self.sign_import_and_distribute_statement(statement, &parent_span).await?;
Statement::Invalid(candidate_hash) }
};
self.issued_statements.insert(candidate_hash); self.issued_statements.insert(candidate_hash);
self.sign_import_and_distribute_statement(statement, &parent_span).await?;
} }
} }
} }
...@@ -1241,7 +1237,6 @@ mod tests { ...@@ -1241,7 +1237,6 @@ mod tests {
match statement { match statement {
TableStatement::Seconded(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt), TableStatement::Seconded(committed_candidate_receipt) => Statement::Seconded(committed_candidate_receipt),
TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash), TableStatement::Valid(candidate_hash) => Statement::Valid(candidate_hash),
TableStatement::Invalid(candidate_hash) => Statement::Invalid(candidate_hash),
} }
} }
...@@ -1883,15 +1878,11 @@ mod tests { ...@@ -1883,15 +1878,11 @@ mod tests {
}.build(); }.build();
let candidate_a_hash = candidate_a.hash(); let candidate_a_hash = candidate_a.hash();
let public0 = CryptoStore::sr25519_generate_new(
&*test_state.keystore,
ValidatorId::ID, Some(&test_state.validators[0].to_seed())
).await.expect("Insert key into keystore");
let public2 = CryptoStore::sr25519_generate_new( let public2 = CryptoStore::sr25519_generate_new(
&*test_state.keystore, &*test_state.keystore,
ValidatorId::ID, Some(&test_state.validators[2].to_seed()) ValidatorId::ID, Some(&test_state.validators[2].to_seed())
).await.expect("Insert key into keystore"); ).await.expect("Insert key into keystore");
let signed_a = SignedFullStatement::sign( let seconded_2 = SignedFullStatement::sign(
&test_state.keystore, &test_state.keystore,
Statement::Seconded(candidate_a.clone()), Statement::Seconded(candidate_a.clone()),
&test_state.signing_context, &test_state.signing_context,
...@@ -1899,25 +1890,17 @@ mod tests { ...@@ -1899,25 +1890,17 @@ mod tests {
&public2.into(), &public2.into(),
).await.ok().flatten().expect("should be signed"); ).await.ok().flatten().expect("should be signed");
let signed_b = SignedFullStatement::sign( let valid_2 = SignedFullStatement::sign(
&test_state.keystore, &test_state.keystore,
Statement::Invalid(candidate_a_hash), Statement::Valid(candidate_a_hash),
&test_state.signing_context, &test_state.signing_context,
ValidatorIndex(2), ValidatorIndex(2),
&public2.into(), &public2.into(),
).await.ok().flatten().expect("should be signed"); ).await.ok().flatten().expect("should be signed");
let signed_c = SignedFullStatement::sign( let statement = CandidateBackingMessage::Statement(test_state.relay_parent, seconded_2.clone());
&test_state.keystore,
Statement::Invalid(candidate_a_hash),
&test_state.signing_context,
ValidatorIndex(0),
&public0.into(),
).await.ok().flatten().expect("should be signed");
let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_a.clone()); virtual_overseer.send(FromOverseer::Communication { msg: statement }).await;
virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await;
assert_matches!( assert_matches!(
virtual_overseer.recv().await, virtual_overseer.recv().await,
...@@ -1976,51 +1959,10 @@ mod tests { ...@@ -1976,51 +1959,10 @@ mod tests {
} }
); );
// This `Invalid` statement contradicts the `Candidate` statement // This `Valid` statement is redundant after the `Seconded` statement already sent.
// sent at first. let statement = CandidateBackingMessage::Statement(test_state.relay_parent, valid_2.clone());
let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_b.clone());
virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await; virtual_overseer.send(FromOverseer::Communication { msg: statement }).await;
assert_matches!(
virtual_overseer.recv().await,
AllMessages::Provisioner(
ProvisionerMessage::ProvisionableData(
_,
ProvisionableData::MisbehaviorReport(
relay_parent,
validator_index,
Misbehavior::ValidityDoubleVote(vdv),
)
)
) if relay_parent == test_state.relay_parent => {
let ((t1, s1), (t2, s2)) = vdv.deconstruct::<TableContext>();
let t1 = table_statement_to_primitive(t1);
let t2 = table_statement_to_primitive(t2);
SignedFullStatement::new(
t1,
validator_index,
s1,
&test_state.signing_context,
&test_state.validator_public[validator_index.0 as usize],
).expect("signature must be valid");
SignedFullStatement::new(
t2,
validator_index,
s2,
&test_state.signing_context,
&test_state.validator_public[validator_index.0 as usize],
).expect("signature must be valid");
}
);
// This `Invalid` statement contradicts the `Valid` statement the subsystem
// should have issued behind the scenes.
let statement = CandidateBackingMessage::Statement(test_state.relay_parent, signed_c.clone());
virtual_overseer.send(FromOverseer::Communication{ msg: statement }).await;
assert_matches!( assert_matches!(
virtual_overseer.recv().await, virtual_overseer.recv().await,
...@@ -2192,7 +2134,7 @@ mod tests { ...@@ -2192,7 +2134,7 @@ mod tests {
// Test that if we have already issued a statement (in this case `Invalid`) about a // Test that if we have already issued a statement (in this case `Invalid`) about a
// candidate we will not be issuing a `Seconded` statement on it. // candidate we will not be issuing a `Seconded` statement on it.
#[test] #[test]
fn backing_multiple_statements_work() { fn backing_second_after_first_fails_works() {
let test_state = TestState::default(); let test_state = TestState::default();
test_harness(test_state.keystore.clone(), |test_harness| async move { test_harness(test_state.keystore.clone(), |test_harness| async move {
let TestHarness { mut virtual_overseer } = test_harness; let TestHarness { mut virtual_overseer } = test_harness;
...@@ -2213,8 +2155,6 @@ mod tests { ...@@ -2213,8 +2155,6 @@ mod tests {
..Default::default() ..Default::default()
}.build(); }.build();
let candidate_hash = candidate.hash();
let validator2 = CryptoStore::sr25519_generate_new( let validator2 = CryptoStore::sr25519_generate_new(
&*test_state.keystore, &*test_state.keystore,
ValidatorId::ID, Some(&test_state.validators[2].to_seed()) ValidatorId::ID, Some(&test_state.validators[2].to_seed())
...@@ -2262,24 +2202,6 @@ mod tests { ...@@ -2262,24 +2202,6 @@ mod tests {
} }
); );
// The invalid message is shared.
assert_matches!(
virtual_overseer.recv().await,
AllMessages::StatementDistribution(
StatementDistributionMessage::Share(
relay_parent,
signed_statement,
)
) => {
assert_eq!(relay_parent, test_state.relay_parent);
signed_statement.check_signature(
&test_state.signing_context,
&test_state.validator_public[0],
).unwrap();
assert_eq!(*signed_statement.payload(), Statement::Invalid(candidate_hash));
}
);
// Ask subsystem to `Second` a candidate that already has a statement issued about. // Ask subsystem to `Second` a candidate that already has a statement issued about.
// This should emit no actions from subsystem. // This should emit no actions from subsystem.
let second = CandidateBackingMessage::Second( let second = CandidateBackingMessage::Second(
......
...@@ -179,7 +179,7 @@ impl PeerRelayParentKnowledge { ...@@ -179,7 +179,7 @@ impl PeerRelayParentKnowledge {
self.known_candidates.insert(h.clone()) self.known_candidates.insert(h.clone())
}, },
CompactStatement::Valid(ref h) | CompactStatement::Invalid(ref h) => { CompactStatement::Valid(ref h) => {
// The peer can only accept Valid and Invalid statements for which it is aware // The peer can only accept Valid and Invalid statements for which it is aware
// of the corresponding candidate. // of the corresponding candidate.
if !self.known_candidates.contains(h) { if !self.known_candidates.contains(h) {
...@@ -235,7 +235,7 @@ impl PeerRelayParentKnowledge { ...@@ -235,7 +235,7 @@ impl PeerRelayParentKnowledge {
h h
} }
CompactStatement::Valid(ref h)| CompactStatement::Invalid(ref h) => { CompactStatement::Valid(ref h) => {
if !self.known_candidates.contains(&h) { if !self.known_candidates.contains(&h) {
return Err(COST_UNEXPECTED_STATEMENT); return Err(COST_UNEXPECTED_STATEMENT);
} }
...@@ -454,7 +454,7 @@ impl ActiveHeadData { ...@@ -454,7 +454,7 @@ impl ActiveHeadData {
NotedStatement::UsefulButKnown NotedStatement::UsefulButKnown
} }
} }
CompactStatement::Valid(h) | CompactStatement::Invalid(h) => { CompactStatement::Valid(h) => {
if !self.candidates.contains(&h) { if !self.candidates.contains(&h) {
return NotedStatement::NotUseful; return NotedStatement::NotUseful;
} }
......
...@@ -52,9 +52,6 @@ pub enum Statement { ...@@ -52,9 +52,6 @@ pub enum Statement {
/// A statement that a validator has deemed a candidate valid. /// A statement that a validator has deemed a candidate valid.
#[codec(index = 2)] #[codec(index = 2)]
Valid(CandidateHash), Valid(CandidateHash),
/// A statement that a validator has deemed a candidate invalid.
#[codec(index = 3)]
Invalid(CandidateHash),
} }
impl Statement { impl Statement {
...@@ -64,7 +61,7 @@ impl Statement { ...@@ -64,7 +61,7 @@ impl Statement {
/// for large candidates. /// for large candidates.
pub fn candidate_hash(&self) -> CandidateHash { pub fn candidate_hash(&self) -> CandidateHash {
match *self { match *self {
Statement::Valid(ref h) | Statement::Invalid(ref h) => *h, Statement::Valid(ref h) => *h,
Statement::Seconded(ref c) => c.hash(), Statement::Seconded(ref c) => c.hash(),
} }
} }
...@@ -75,7 +72,6 @@ impl Statement { ...@@ -75,7 +72,6 @@ impl Statement {
match *self { match *self {
Statement::Seconded(ref c) => CompactStatement::Seconded(c.hash()), Statement::Seconded(ref c) => CompactStatement::Seconded(c.hash()),
Statement::Valid(hash) => CompactStatement::Valid(hash), Statement::Valid(hash) => CompactStatement::Valid(hash),
Statement::Invalid(hash) => CompactStatement::Invalid(hash),
} }
} }
} }
......
...@@ -688,8 +688,6 @@ pub enum CompactStatement { ...@@ -688,8 +688,6 @@ pub enum CompactStatement {
Seconded(CandidateHash), Seconded(CandidateHash),
/// State that a parachain candidate is valid. /// State that a parachain candidate is valid.
Valid(CandidateHash), Valid(CandidateHash),
/// State that a parachain candidate is invalid.
Invalid(CandidateHash),
} }
// Inner helper for codec on `CompactStatement`. // Inner helper for codec on `CompactStatement`.
...@@ -699,8 +697,6 @@ enum CompactStatementInner { ...@@ -699,8 +697,6 @@ enum CompactStatementInner {
Seconded(CandidateHash), Seconded(CandidateHash),
#[codec(index = 2)] #[codec(index = 2)]
Valid(CandidateHash), Valid(CandidateHash),
#[codec(index = 3)]
Invalid(CandidateHash),
} }
impl From<CompactStatement> for CompactStatementInner { impl From<CompactStatement> for CompactStatementInner {
...@@ -708,7 +704,6 @@ impl From<CompactStatement> for CompactStatementInner { ...@@ -708,7 +704,6 @@ impl From<CompactStatement> for CompactStatementInner {
match s { match s {
CompactStatement::Seconded(h) => CompactStatementInner::Seconded(h), CompactStatement::Seconded(h) => CompactStatementInner::Seconded(h),
CompactStatement::Valid(h) => CompactStatementInner::Valid(h), CompactStatement::Valid(h) => CompactStatementInner::Valid(h),
CompactStatement::Invalid(h) => CompactStatementInner::Invalid(h),
} }
} }
} }
...@@ -735,7 +730,6 @@ impl parity_scale_codec::Decode for CompactStatement { ...@@ -735,7 +730,6 @@ impl parity_scale_codec::Decode for CompactStatement {
Ok(match CompactStatementInner::decode(input)? { Ok(match CompactStatementInner::decode(input)? {
CompactStatementInner::Seconded(h) => CompactStatement::Seconded(h), CompactStatementInner::Seconded(h) => CompactStatement::Seconded(h),
CompactStatementInner::Valid(h) => CompactStatement::Valid(h), CompactStatementInner::Valid(h) => CompactStatement::Valid(h),
CompactStatementInner::Invalid(h) => CompactStatement::Invalid(h),
}) })
} }
} }
...@@ -744,10 +738,7 @@ impl CompactStatement { ...@@ -744,10 +738,7 @@ impl CompactStatement {
/// Get the underlying candidate hash this references. /// Get the underlying candidate hash this references.
pub fn candidate_hash(&self) -> &CandidateHash { pub fn candidate_hash(&self) -> &CandidateHash {
match *self { match *self {
CompactStatement::Seconded(ref h) CompactStatement::Seconded(ref h) | CompactStatement::Valid(ref h) => h,
| CompactStatement::Valid(ref h)
| CompactStatement::Invalid(ref h)
=> h
} }
} }
} }
......
...@@ -70,9 +70,6 @@ pub enum Statement<Candidate, Digest> { ...@@ -70,9 +70,6 @@ pub enum Statement<Candidate, Digest> {
/// Broadcast by a authority to attest that the candidate with given digest is valid. /// Broadcast by a authority to attest that the candidate with given digest is valid.
#[codec(index = 2)] #[codec(index = 2)]
Valid(Digest), Valid(Digest),
/// Broadcast by a authority to attest that the candidate with given digest is invalid.
#[codec(index = 3)]
Invalid(Digest),
} }
/// A signed statement. /// A signed statement.
...@@ -94,10 +91,6 @@ pub struct SignedStatement<Candidate, Digest, AuthorityId, Signature> { ...@@ -94,10 +91,6 @@ pub struct SignedStatement<Candidate, Digest, AuthorityId, Signature> {
pub enum ValidityDoubleVote<Candidate, Digest, Signature> { pub enum ValidityDoubleVote<Candidate, Digest, Signature> {
/// Implicit vote by issuing and explicitly voting validity. /// Implicit vote by issuing and explicitly voting validity.
IssuedAndValidity((Candidate, Signature), (Digest, Signature)), IssuedAndValidity((Candidate, Signature), (Digest, Signature)),
/// Implicit vote by issuing and explicitly voting invalidity
IssuedAndInvalidity((Candidate, Signature), (Digest, Signature)),
/// Direct votes for validity and invalidity
ValidityAndInvalidity(Candidate, Signature, Signature),
} }
impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signature> { impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signature> {
...@@ -117,15 +110,6 @@ impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signatu ...@@ -117,15 +110,6 @@ impl<Candidate, Digest, Signature> ValidityDoubleVote<Candidate, Digest, Signatu
Self::IssuedAndValidity((c, s1), (d, s2)) => { Self::IssuedAndValidity((c, s1), (d, s2)) => {
((Statement::Seconded(c), s1), (Statement::Valid(d), s2)) ((Statement::Seconded(c), s1), (Statement::Valid(d), s2))
} }
Self::IssuedAndInvalidity((c, s1), (d, s2)) => {
((Statement::Seconded(c), s1), (Statement::Invalid(d), s2))
}
Self::ValidityAndInvalidity(c, s1, s2) => {
(
(Statement::Valid(Ctx::candidate_digest(&c)), s1),
(Statement::Invalid(Ctx::candidate_digest(&c)), s2),
)
}
} }
} }
} }
...@@ -137,8 +121,6 @@ pub enum DoubleSign<Candidate, Digest, Signature> { ...@@ -137,8 +121,6 @@ pub enum DoubleSign<Candidate, Digest, Signature> {
Seconded(Candidate, Signature, Signature), Seconded(Candidate, Signature, Signature),
/// On validity. /// On validity.
Validity(Digest, Signature, Signature), Validity(Digest, Signature, Signature),
/// On invalidity.
Invalidity(Digest, Signature, Signature),
} }
impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> { impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> {
...@@ -148,7 +130,6 @@ impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> { ...@@ -148,7 +130,6 @@ impl<Candidate, Digest, Signature> DoubleSign<Candidate, Digest, Signature> {
match self { match self {
Self::Seconded(candidate, a, b) => (Statement::Seconded(candidate), a, b), Self::Seconded(candidate, a, b) => (Statement::Seconded(candidate), a, b),
Self::Validity(digest, a, b) => (Statement::Valid(digest), a, b), Self::Validity(digest, a, b) => (Statement::Valid(digest), a, b),
Self::Invalidity(digest, a, b) => (Statement::Invalid(digest), a, b),
} }
} }
} }
...@@ -198,8 +179,6 @@ enum ValidityVote<Signature: Eq + Clone> { ...@@ -198,8 +179,6 @@ enum ValidityVote<Signature: Eq + Clone> {
Issued(Signature), Issued(Signature),
// direct validity vote // direct validity vote
Valid(Signature), Valid(Signature),
// direct invalidity vote
Invalid(Signature),
} }
/// A summary of import of a statement. /// A summary of import of a statement.
...@@ -211,8 +190,6 @@ pub struct Summary<Digest, Group> { ...@@ -211,8 +190,6 @@ pub struct Summary<Digest, Group> {
pub group_id: Group, pub group_id: Group,
/// How many validity votes are currently witnessed. /// How many validity votes are currently witnessed.
pub validity_votes: usize, pub validity_votes: usize,
/// Whether this has been signalled bad by at least one participant.
pub signalled_bad: bool,
} }
/// A validity attestation. /// A validity attestation.
...@@ -251,15 +228,9 @@ pub struct CandidateData<Ctx: Context> { ...@@ -251,15 +228,9 @@ pub struct CandidateData<Ctx: Context> {
group_id: Ctx::GroupId, group_id: Ctx::GroupId,
candidate: Ctx::Candidate, candidate: Ctx::Candidate,
validity_votes: HashMap<Ctx::AuthorityId, ValidityVote<Ctx::Signature>>, validity_votes: HashMap<Ctx::AuthorityId, ValidityVote<Ctx::Signature>>,
indicated_bad_by: Vec<Ctx::AuthorityId>,
} }
impl<Ctx: Context> CandidateData<Ctx> { impl<Ctx: Context> CandidateData<Ctx> {
/// whether this has been indicated bad by anyone.
pub fn indicated_bad(&self) -> bool {
!self.indicated_bad_by.is_empty()
}
/// Yield a full attestation for a candidate. /// Yield a full attestation for a candidate.
/// If the candidate can be included, it will return `Some`. /// If the candidate can be included, it will return `Some`.
pub fn attested(&self, validity_threshold: usize) pub fn attested(&self, validity_threshold: usize)
...@@ -267,18 +238,17 @@ impl<Ctx: Context> CandidateData<Ctx> { ...@@ -267,18 +238,17 @@ impl<Ctx: Context> CandidateData<Ctx> {
Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature, Ctx::GroupId, Ctx::Candidate, Ctx::AuthorityId, Ctx::Signature,
>> >>
{ {
let valid_votes = self.validity_votes.len().saturating_sub(self.indicated_bad_by.len()); let valid_votes = self.validity_votes.len();
if valid_votes < validity_threshold { if valid_votes < validity_threshold {
return None; return None;
} }
let validity_votes = self.validity_votes.iter() let validity_votes = self.validity_votes.iter()
.filter_map(|(a, v)| match *v { .map(|(a, v)| match *v {
ValidityVote::Invalid(_) => None,
ValidityVote::Valid(ref s) => ValidityVote::Valid(ref s) =>
Some((a.clone(), ValidityAttestation::Explicit(s.clone()))), (a.clone(), ValidityAttestation::Explicit(s.clone())),
ValidityVote::Issued(ref s) => ValidityVote::Issued(ref s) =>
Some((a.clone(), ValidityAttestation::Implicit(s.clone()))), (a.clone(), ValidityAttestation::Implicit(s.clone())),
}) })
.collect(); .collect();
...@@ -294,7 +264,6 @@ impl<Ctx: Context> CandidateData<Ctx> { ...@@ -294,7 +264,6 @@ impl<Ctx: Context> CandidateData<Ctx> {
candidate: digest, candidate: digest,
group_id: self.group_id.clone(), group_id: self.group_id.clone(),
validity_votes: self.validity_votes.len(), validity_votes: self.validity_votes.len(),
signalled_bad: self.indicated_bad(),
} }
} }
} }
...@@ -377,12 +346,6 @@ impl<Ctx: Context> Table<Ctx> { ...@@ -377,12 +346,6 @@ impl<Ctx: Context> Table<Ctx> {
digest, digest,
ValidityVote::Valid(signature), ValidityVote::Valid(signature),
), ),
Statement::Invalid(digest) => self.validity_vote(
context,
signer.clone(),
digest,
ValidityVote::Invalid(signature),
),
}; };
match res { match res {
...@@ -483,7 +446,6 @@ impl<Ctx: Context> Table<Ctx> { ...@@ -483,7 +446,6 @@ impl<Ctx: Context> Table<Ctx> {
group_id: group, group_id: group,
candidate, candidate,
validity_votes: HashMap::new(), validity_votes: HashMap::new(),
indicated_bad_by: Vec::new(),
}); });
} }
...@@ -509,9 +471,8 @@ impl<Ctx: Context> Table<Ctx> { ...@@ -509,9 +471,8 @@ impl<Ctx: Context> Table<Ctx> {
// check that this authority actually can vote in this group. // check that this authority actually can vote in this group.
if !context.is_member_of(&from, &votes.group_id) { if !context.is_member_of(&from, &votes.group_id) {
let (sig, valid) = match vote { let sig = match vote {
ValidityVote::Valid(s) => (s, true), ValidityVote::Valid(s) => s,
ValidityVote::Invalid(s) => (s, false),
ValidityVote::Issued(_) => ValidityVote::Issued(_) =>
panic!("implicit issuance vote only cast from `import_candidate` after \ panic!("implicit issuance vote only cast from `import_candidate` after \
checking group membership of issuer; qed"), checking group membership of issuer; qed"),
...@@ -521,11 +482,7 @@ impl<Ctx: Context> Table<Ctx> { ...@@ -521,11 +482,7 @@ impl<Ctx: Context> Table<Ctx> {
statement: SignedStatement { statement: SignedStatement {
signature: sig, signature: sig,
sender: from, sender: from,
statement: if valid { statement: Statement::Valid(digest),
Statement::Valid(digest)
} else {
Statement::Invalid(digest)
}
} }
}));