use chia_bls::Signature;
use dig_block::L2BlockHeader;
use dig_protocol::Bytes32;
use crate::appeal::ground::ProposerAppealGround;
use crate::appeal::verdict::{AppealRejectReason, AppealSustainReason, AppealVerdict};
use crate::constants::BLS_SIGNATURE_SIZE;
use crate::evidence::attester_slashing::AttesterSlashing;
use crate::evidence::indexed_attestation::IndexedAttestation;
use crate::evidence::invalid_block::InvalidBlockProof;
use crate::evidence::proposer_slashing::ProposerSlashing;
use crate::evidence::verify::block_signing_message;
use crate::traits::{ExecutionOutcome, InvalidBlockOracle, PublicKeyLookup, ValidatorView};
#[must_use]
pub fn verify_proposer_appeal_headers_identical(evidence: &ProposerSlashing) -> AppealVerdict {
if evidence.signed_header_a.message == evidence.signed_header_b.message {
AppealVerdict::Sustained {
reason: AppealSustainReason::HeadersIdentical,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_proposer_appeal_proposer_index_mismatch(
evidence: &ProposerSlashing,
) -> AppealVerdict {
let a = evidence.signed_header_a.message.proposer_index;
let b = evidence.signed_header_b.message.proposer_index;
if a != b {
AppealVerdict::Sustained {
reason: AppealSustainReason::ProposerIndexMismatch,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_proposer_appeal_signature_a_invalid(
evidence: &ProposerSlashing,
validator_view: &dyn ValidatorView,
network_id: &Bytes32,
) -> AppealVerdict {
verify_proposer_appeal_signature_side(
&evidence.signed_header_a.message,
&evidence.signed_header_a.signature,
validator_view,
network_id,
AppealSustainReason::SignatureAInvalid,
)
}
#[must_use]
pub fn verify_proposer_appeal_slot_mismatch(evidence: &ProposerSlashing) -> AppealVerdict {
let a = evidence.signed_header_a.message.height;
let b = evidence.signed_header_b.message.height;
if a != b {
AppealVerdict::Sustained {
reason: AppealSustainReason::SlotMismatch,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_proposer_appeal_signature_b_invalid(
evidence: &ProposerSlashing,
validator_view: &dyn ValidatorView,
network_id: &Bytes32,
) -> AppealVerdict {
verify_proposer_appeal_signature_side(
&evidence.signed_header_b.message,
&evidence.signed_header_b.signature,
validator_view,
network_id,
AppealSustainReason::SignatureBInvalid,
)
}
#[must_use]
pub fn verify_proposer_appeal_validator_not_active_at_epoch(
evidence: &ProposerSlashing,
validator_view: &dyn ValidatorView,
) -> AppealVerdict {
let header = &evidence.signed_header_a.message;
let sustain = AppealVerdict::Sustained {
reason: AppealSustainReason::ValidatorNotActiveAtEpoch,
};
let Some(entry) = validator_view.get(header.proposer_index) else {
return sustain;
};
if entry.is_active_at_epoch(header.epoch) {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
} else {
sustain
}
}
fn verify_proposer_appeal_signature_side(
header: &L2BlockHeader,
sig_bytes: &[u8],
validator_view: &dyn ValidatorView,
network_id: &Bytes32,
sustain_reason: AppealSustainReason,
) -> AppealVerdict {
let sustain = AppealVerdict::Sustained {
reason: sustain_reason,
};
let Ok(sig_arr) = <&[u8; BLS_SIGNATURE_SIZE]>::try_from(sig_bytes) else {
return sustain;
};
let Ok(sig) = Signature::from_bytes(sig_arr) else {
return sustain;
};
let Some(entry) = validator_view.get(header.proposer_index) else {
return sustain;
};
let pk = entry.public_key();
let msg = block_signing_message(
network_id,
header.epoch,
&header.hash(),
header.proposer_index,
);
if chia_bls::verify(&sig, pk, &msg) {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
} else {
sustain
}
}
#[must_use]
pub fn verify_attester_appeal_not_slashable_by_predicate(
evidence: &AttesterSlashing,
) -> AppealVerdict {
let a = &evidence.attestation_a.data;
let b = &evidence.attestation_b.data;
let double_vote = a.target.epoch == b.target.epoch && a != b;
let surround_vote = (a.source.epoch < b.source.epoch && a.target.epoch > b.target.epoch)
|| (b.source.epoch < a.source.epoch && b.target.epoch > a.target.epoch);
if !(double_vote || surround_vote) {
AppealVerdict::Sustained {
reason: AppealSustainReason::NotSlashableByPredicate,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_attester_appeal_empty_intersection(evidence: &AttesterSlashing) -> AppealVerdict {
if evidence.slashable_indices().is_empty() {
AppealVerdict::Sustained {
reason: AppealSustainReason::EmptyIntersection,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_attester_appeal_signature_a_invalid(
evidence: &AttesterSlashing,
pks: &dyn PublicKeyLookup,
network_id: &Bytes32,
) -> AppealVerdict {
verify_attester_appeal_signature_side(
&evidence.attestation_a,
pks,
network_id,
AppealSustainReason::AttesterSignatureAInvalid,
)
}
#[must_use]
pub fn verify_attester_appeal_signature_b_invalid(
evidence: &AttesterSlashing,
pks: &dyn PublicKeyLookup,
network_id: &Bytes32,
) -> AppealVerdict {
verify_attester_appeal_signature_side(
&evidence.attestation_b,
pks,
network_id,
AppealSustainReason::AttesterSignatureBInvalid,
)
}
fn verify_attester_appeal_signature_side(
attestation: &IndexedAttestation,
pks: &dyn PublicKeyLookup,
network_id: &Bytes32,
sustain_reason: AppealSustainReason,
) -> AppealVerdict {
if attestation.verify_signature(pks, network_id).is_err() {
AppealVerdict::Sustained {
reason: sustain_reason,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_attester_appeal_invalid_indexed_attestation_structure(
evidence: &AttesterSlashing,
) -> AppealVerdict {
if evidence.attestation_a.validate_structure().is_err()
|| evidence.attestation_b.validate_structure().is_err()
{
AppealVerdict::Sustained {
reason: AppealSustainReason::InvalidIndexedAttestationStructure,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_attester_appeal_validator_not_in_intersection(
evidence: &AttesterSlashing,
validator_index: u32,
) -> AppealVerdict {
if evidence.slashable_indices().contains(&validator_index) {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
} else {
AppealVerdict::Sustained {
reason: AppealSustainReason::ValidatorNotInIntersection,
}
}
}
#[must_use]
pub fn verify_attester_appeal_attestations_identical(evidence: &AttesterSlashing) -> AppealVerdict {
if evidence.attestation_a == evidence.attestation_b {
AppealVerdict::Sustained {
reason: AppealSustainReason::AttestationsIdentical,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[must_use]
pub fn verify_invalid_block_appeal_proposer_signature_invalid(
evidence: &InvalidBlockProof,
validator_view: &dyn ValidatorView,
network_id: &Bytes32,
) -> AppealVerdict {
verify_proposer_appeal_signature_side(
&evidence.signed_header.message,
&evidence.signed_header.signature,
validator_view,
network_id,
AppealSustainReason::ProposerSignatureInvalid,
)
}
#[must_use]
pub fn verify_invalid_block_appeal_block_actually_valid(
evidence: &InvalidBlockProof,
appeal_witness: &[u8],
oracle: Option<&dyn InvalidBlockOracle>,
) -> AppealVerdict {
let Some(oracle) = oracle else {
return AppealVerdict::Rejected {
reason: AppealRejectReason::MissingOracle,
};
};
match oracle.re_execute(&evidence.signed_header.message, appeal_witness) {
Ok(ExecutionOutcome::Valid) => AppealVerdict::Sustained {
reason: AppealSustainReason::BlockActuallyValid,
},
Ok(ExecutionOutcome::Invalid(_)) => AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
},
Err(_) => AppealVerdict::Rejected {
reason: AppealRejectReason::MalformedWitness,
},
}
}
#[must_use]
pub fn verify_invalid_block_appeal_failure_reason_mismatch(
evidence: &InvalidBlockProof,
appeal_witness: &[u8],
oracle: Option<&dyn InvalidBlockOracle>,
) -> AppealVerdict {
let Some(oracle) = oracle else {
return AppealVerdict::Rejected {
reason: AppealRejectReason::MissingOracle,
};
};
match oracle.re_execute(&evidence.signed_header.message, appeal_witness) {
Ok(ExecutionOutcome::Invalid(actual)) if actual != evidence.failure_reason => {
AppealVerdict::Sustained {
reason: AppealSustainReason::FailureReasonMismatch,
}
}
Ok(_) => AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
},
Err(_) => AppealVerdict::Rejected {
reason: AppealRejectReason::MalformedWitness,
},
}
}
#[must_use]
pub fn verify_invalid_block_appeal_evidence_epoch_mismatch(
evidence: &InvalidBlockProof,
evidence_epoch: u64,
) -> AppealVerdict {
if evidence.signed_header.message.epoch != evidence_epoch {
AppealVerdict::Sustained {
reason: AppealSustainReason::EvidenceEpochMismatch,
}
} else {
AppealVerdict::Rejected {
reason: AppealRejectReason::GroundDoesNotHold,
}
}
}
#[allow(dead_code)]
const _HEADERS_IDENTICAL_GROUND: ProposerAppealGround = ProposerAppealGround::HeadersIdentical;
#[allow(dead_code)]
const _PROPOSER_INDEX_MISMATCH_GROUND: ProposerAppealGround =
ProposerAppealGround::ProposerIndexMismatch;
#[allow(dead_code)]
const _SIGNATURE_A_INVALID_GROUND: ProposerAppealGround = ProposerAppealGround::SignatureAInvalid;
#[allow(dead_code)]
const _SIGNATURE_B_INVALID_GROUND: ProposerAppealGround = ProposerAppealGround::SignatureBInvalid;
#[allow(dead_code)]
const _SLOT_MISMATCH_GROUND: ProposerAppealGround = ProposerAppealGround::SlotMismatch;
#[allow(dead_code)]
const _VALIDATOR_NOT_ACTIVE_GROUND: ProposerAppealGround =
ProposerAppealGround::ValidatorNotActiveAtEpoch;