use tru::{compute_focusing, Context, FocusingGraph, FocusingParams, Fx, Link};
use crate::chain::Signal;
use crate::fork::{ForkChoice, ForkError, GraphView, MinHash};
pub struct Focus {
params: FocusingParams,
}
impl Focus {
pub fn new() -> Self {
Self {
params: FocusingParams::default(),
}
}
pub fn with_params(params: FocusingParams) -> Self {
Self { params }
}
}
impl Default for Focus {
fn default() -> Self {
Self::new()
}
}
impl ForkChoice for Focus {
fn resolve(&self, members: &[Signal], view: &dyn GraphView) -> Result<usize, ForkError> {
match members.len() {
0 => return Err(ForkError::Empty),
1 => return Ok(0),
_ => {}
}
let links: Vec<Link> = view
.links()
.iter()
.map(|l| Link::stake(l.from, l.to, l.amount as u128))
.collect();
if links.is_empty() {
return MinHash.resolve(members, view);
}
let ctx = Context::none();
let graph = FocusingGraph::build(links, &ctx);
let result = compute_focusing(&graph, &self.params);
let node_ids = graph.node_ids();
let focus_of = |p: &[u8; 32]| -> Fx {
node_ids
.iter()
.position(|id| id == p)
.map(|i| result.focus[i])
.unwrap_or(Fx::ZERO)
};
let score = |sig: &Signal| -> Fx {
let mut s = Fx::ZERO;
for l in &sig.links {
s = s + focus_of(&l.to);
}
s
};
let mut best = 0usize;
let mut best_score = score(&members[0]);
let mut best_id = members[0].content_id();
for (i, m) in members.iter().enumerate().skip(1) {
let s = score(m);
let id = m.content_id();
if s > best_score || (s == best_score && id < best_id) {
best = i;
best_score = s;
best_id = id;
}
}
Ok(best)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::{CyberlinkRecord, Signal, SELF_NETWORK};
use crate::fork::LinksView;
fn p(b: u8) -> [u8; 32] {
[b; 32]
}
fn link(neuron: u8, from: u8, to: u8, amount: u64) -> CyberlinkRecord {
CyberlinkRecord {
neuron: p(neuron),
from: p(from),
to: p(to),
token: p(0),
amount,
valence: 1,
height: 0,
}
}
fn sig_to(neuron: u8, step: u64, to: u8) -> Signal {
Signal {
neuron: p(neuron),
network: SELF_NETWORK,
links: vec![link(neuron, 9, to, 1)],
delta_pi: vec![],
prev: p(0),
step,
height: 0,
proof: None,
}
}
#[test]
fn higher_focus_target_wins() {
let context = vec![
link(2, 2, 1, 1000),
link(3, 3, 1, 1000),
link(4, 4, 1, 1000),
link(5, 5, 1, 1000),
link(1, 9, 1, 1), link(1, 9, 7, 1), ];
let view = LinksView(context);
let a = sig_to(1, 0, 1); let b = sig_to(1, 0, 7); let members = vec![a.clone(), b.clone()];
let idx = Focus::new().resolve(&members, &view).unwrap();
assert_eq!(
members[idx].content_id(),
a.content_id(),
"the signal directing attention to the high-ฯ* hub should win"
);
}
#[test]
fn empty_graph_falls_back_to_minhash() {
let a = Signal {
neuron: p(1),
network: SELF_NETWORK,
links: vec![],
delta_pi: vec![],
prev: p(0),
step: 0,
height: 0,
proof: None,
};
let b = Signal {
neuron: p(1),
network: SELF_NETWORK,
links: vec![],
delta_pi: vec![(p(5), 1)],
prev: p(0),
step: 0,
height: 0,
proof: None,
};
let view = LinksView(vec![]);
let members = vec![a.clone(), b.clone()];
let focus_idx = Focus::new().resolve(&members, &view).unwrap();
let minhash_idx = MinHash.resolve(&members, &view).unwrap();
assert_eq!(focus_idx, minhash_idx);
}
#[test]
fn deterministic_across_runs() {
let view = LinksView(vec![
link(2, 2, 1, 1000),
link(1, 9, 1, 1),
link(1, 9, 7, 1),
]);
let members = vec![sig_to(1, 0, 1), sig_to(1, 0, 7)];
let r1 = Focus::new().resolve(&members, &view).unwrap();
let r2 = Focus::new().resolve(&members, &view).unwrap();
assert_eq!(r1, r2);
}
#[test]
fn empty_members_is_error() {
assert_eq!(
Focus::new().resolve(&[], &LinksView(vec![])),
Err(ForkError::Empty)
);
}
}