use cyber_hemera::hash as hemera_hash;
use tru::{Context, FocusingParams, Link};
use zheng::phi::{verify_phi_star, PhiProof, SparseGraph, TriKernelParams as PhiParams};
use crate::beacon::{verify_beacon, BeaconArtifact};
use crate::marginal_cert::{prove_replayed_batch, replay_marginals, CertError};
use crate::rewards::{contributions_with_rho, verify_receipt, RewardClaim, SettleReceipt};
use crate::ticket_proof::{verify_fold_seal, FoldSeal};
use crate::tickets::easy_target;
use crate::tip::{Tip, TipTrust};
#[derive(Clone)]
pub struct EpochCertificate {
pub epoch: u64,
pub tip_height: u64,
pub tip_root: [u8; 32],
pub tip_trust: TipTrust,
pub claims_root: [u8; 32],
pub beacon: BeaconArtifact,
pub phi_star_hash: Option<[u8; 32]>,
pub settle: Option<SettleReceipt>,
pub ticket_batch_seal: Option<FoldSeal>,
pub cert_hash: [u8; 32],
}
pub struct SettleVerifyInputs<'a> {
pub base: &'a [Link],
pub claims: &'a [RewardClaim],
pub ctx: &'a Context,
pub params: &'a FocusingParams,
}
pub fn cert_hash(c: &EpochCertificate) -> [u8; 32] {
let mut buf = Vec::with_capacity(256);
buf.extend_from_slice(b"foculus-epoch-cert-v2");
buf.extend_from_slice(&c.epoch.to_le_bytes());
buf.extend_from_slice(&c.tip_height.to_le_bytes());
buf.extend_from_slice(&c.tip_root);
buf.extend_from_slice(&(c.tip_trust as u8).to_le_bytes());
buf.extend_from_slice(&c.claims_root);
buf.extend_from_slice(&c.beacon.beacon);
if let Some(s) = &c.settle {
buf.extend_from_slice(&s.receipt_hash);
}
match &c.phi_star_hash {
Some(h) => {
buf.push(1);
buf.extend_from_slice(h);
}
None => buf.push(0),
}
if let Some(seal) = &c.ticket_batch_seal {
buf.extend_from_slice(&seal.steps.to_le_bytes());
buf.extend_from_slice(&seal.acc.step_count.to_le_bytes());
}
*hemera_hash(&buf)
.as_bytes()
.first_chunk::<32>()
.unwrap_or(&[0u8; 32])
}
pub fn issue_epoch_cert(
epoch: u64,
tip: &Tip,
beacon: BeaconArtifact,
claims_root: [u8; 32],
settle: Option<SettleReceipt>,
ticket_batch_seal: Option<FoldSeal>,
phi: Option<&PhiProof>,
) -> EpochCertificate {
let phi_star_hash = phi.map(|p| p.statement.phi_star_hash);
let mut c = EpochCertificate {
epoch,
tip_height: tip.height,
tip_root: tip.root,
tip_trust: tip.trust,
claims_root,
beacon,
phi_star_hash,
settle,
ticket_batch_seal,
cert_hash: [0u8; 32],
};
c.cert_hash = cert_hash(&c);
c
}
pub fn verify_epoch_cert(
cert: &EpochCertificate,
settle_inputs: Option<&SettleVerifyInputs<'_>>,
) -> bool {
if cert.cert_hash != cert_hash(cert) {
return false;
}
if !matches!(
cert.tip_trust,
TipTrust::LocalApplied | TipTrust::FoldDecided
) {
return false;
}
if !verify_beacon(&cert.beacon) {
return false;
}
if cert.beacon.claims_root != cert.claims_root || cert.beacon.epoch != cert.epoch {
return false;
}
if let Some(rec) = &cert.settle {
if !verify_receipt(rec) {
return false;
}
if rec.epoch != cert.epoch || rec.beacon != cert.beacon.beacon {
return false;
}
if rec.claims_root != cert.claims_root {
return false;
}
if let Some(seal) = &cert.ticket_batch_seal {
if !verify_fold_seal(seal) {
return false;
}
}
if let Some(inp) = settle_inputs {
let contribs = contributions_with_rho(inp.claims);
if contribs.is_empty() {
return false;
}
let all: Vec<Link> = inp.claims.iter().flat_map(|c| c.links.clone()).collect();
let directed = tru::impulse(
inp.base,
&all,
inp.ctx,
inp.params,
inp.params.epsilon,
)
.directed;
if directed.raw().as_u64() != rec.directed_total.raw().as_u64() {
if directed.to_f64() > 0.0 && rec.directed_total.to_f64() <= 0.0 {
return false;
}
}
let _ = contribs;
}
}
true
}
pub fn verify_phi_on_cert(
cert: &EpochCertificate,
proof: &PhiProof,
transition: &SparseGraph,
sym: &SparseGraph,
degree: &[nebu::Goldilocks],
teleport: &[nebu::Goldilocks],
phi0: &[nebu::Goldilocks],
params: &PhiParams,
) -> bool {
match cert.phi_star_hash {
Some(h) if h == proof.statement.phi_star_hash => {}
_ => return false,
}
verify_phi_star(transition, sym, degree, teleport, phi0, proof, params)
}
pub fn seal_settle_tickets(
base: &[Link],
claims: &[RewardClaim],
ctx: &Context,
params: &FocusingParams,
beacon: &[u8; 32],
cluster: &[u8; 32],
tickets: &[crate::tickets::SettlementTicket],
) -> Result<FoldSeal, CertError> {
let contribs = contributions_with_rho(claims);
for t in tickets {
if replay_marginals(base, &contribs, ctx, params, beacon, t).is_none() {
return Err(CertError::ReplayFailed);
}
}
prove_replayed_batch(base, &contribs, ctx, params, beacon, cluster, tickets)
}
pub fn test_settle_target() -> u64 {
easy_target()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::beacon::{open_beacon, GENESIS_PREV, TEST_OUTER_T};
use crate::epoch::EpochRunner;
use crate::rewards::{claim_from_links, TicketPolicy};
use crate::tip::Tip;
use crate::tickets::easy_target;
use bbg::Checkpoint;
use tru::Link;
fn h(b: u8) -> [u8; 32] {
let mut x = [0u8; 32];
x[0] = b;
x
}
#[test]
fn epoch_cert_from_runner_verifies() {
let mut runner = EpochRunner::genesis(1);
runner.budget = 500;
runner.outer_t = TEST_OUTER_T;
runner
.propose(claim_from_links(
h(0xA1),
h(10),
vec![Link::stake(h(2), h(1), 8000)],
1,
))
.unwrap();
let cr = runner.freeze().unwrap();
runner.open_quiet_beacon().unwrap();
let base = vec![
Link::stake(h(1), h(2), 100),
Link::stake(h(2), h(3), 100),
Link::stake(h(3), h(1), 100),
];
let policy = TicketPolicy {
want: 2,
max_attempts: 32,
miner: h(10),
settle_target: easy_target(),
..TicketPolicy::default()
};
let rec = runner
.settle(
&base,
&Context::none(),
&FocusingParams::default(),
&policy,
)
.unwrap()
.clone();
let tip = Tip::from_local(&Checkpoint {
root: h(0xB1),
acc: None,
height: 3,
});
let art = runner.beacon.clone().unwrap();
let cert = issue_epoch_cert(
1,
&tip,
art,
cr,
Some(rec.clone()),
rec.ticket_seal.clone(),
None,
);
let cert2 = cert.clone();
assert_eq!(cert.cert_hash, cert2.cert_hash);
assert!(verify_epoch_cert(&cert2, None));
let inputs = SettleVerifyInputs {
base: &base,
claims: runner.claims(),
ctx: &Context::none(),
params: &FocusingParams::default(),
};
assert!(verify_epoch_cert(&cert, Some(&inputs)));
assert!(verify_epoch_cert(&cert, None));
}
#[test]
fn bad_beacon_fails() {
let tip = Tip::from_local(&Checkpoint {
root: h(1),
acc: None,
height: 1,
});
let mut art = open_beacon(1, &GENESIS_PREV, &h(2), &[], TEST_OUTER_T);
art.beacon[0] ^= 1;
let cert = issue_epoch_cert(1, &tip, art, h(2), None, None, None);
assert!(!verify_beacon(&cert.beacon) || !verify_epoch_cert(&cert, None));
}
}