use std::collections::BTreeMap;
use std::ops::Deref;
use hemera::hash as hemera_hash;
use lens::Commitment;
use crate::keyed::Keyed;
use crate::state::BbgState;
use crate::trie::{Digest, TrieProof};
use crate::types::{CardRecord, NeuronId, Particle, Side};
pub const D_CARD: &[u8; 20] = b"soft3/chain/card/v1\0";
pub const D_STAKE: &[u8; 21] = b"soft3/chain/stake/v1\0";
pub const D_FOCUS: &[u8; 21] = b"soft3/chain/focus/v1\0";
fn tagged(tag: &[u8], parts: &[&[u8; 32]]) -> [u8; 32] {
let mut buf = Vec::with_capacity(tag.len() + 32 * parts.len());
buf.extend_from_slice(tag);
for part in parts {
buf.extend_from_slice(*part);
}
let h = hemera_hash(&buf);
let b = h.as_bytes();
let mut out = [0u8; 32];
out[..b.len().min(32)].copy_from_slice(&b[..b.len().min(32)]);
out
}
pub fn card_id(author: &NeuronId, p: &Particle, q: &Particle) -> Particle {
tagged(D_CARD, &[author, p, q])
}
pub fn stake_key(card: &Particle) -> [u8; 32] {
tagged(D_STAKE, &[card])
}
pub fn focus_key(token: &Particle) -> [u8; 32] {
tagged(D_FOCUS, &[token])
}
#[derive(Default)]
pub struct Cards {
records: Keyed<Particle, CardRecord>,
by_axon: BTreeMap<Particle, BTreeMap<Particle, [u8; 32]>>,
}
impl Deref for Cards {
type Target = BTreeMap<Particle, CardRecord>;
fn deref(&self) -> &BTreeMap<Particle, CardRecord> {
&self.records
}
}
impl Cards {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, card: Particle, record: CardRecord) -> Option<CardRecord> {
let old = self.records.insert(card, record);
let key = match old {
Some(o) if o.particle == record.particle => return old,
Some(o) => self.unindex(&o.particle, &card),
None => stake_key(&card),
};
self.by_axon.entry(record.particle).or_default().insert(card, key);
old
}
pub fn remove(&mut self, card: &Particle) -> Option<CardRecord> {
let old = self.records.remove(card)?;
self.unindex(&old.particle, card);
Some(old)
}
fn unindex(&mut self, axon: &Particle, card: &Particle) -> [u8; 32] {
let cards = self.by_axon.get_mut(axon).expect("indexed axon");
let key = cards.remove(card).expect("indexed card");
if cards.is_empty() {
self.by_axon.remove(axon);
}
key
}
pub fn clear(&mut self) {
self.records.clear();
self.by_axon.clear();
}
pub fn on_axon<'a>(&'a self, axon: &Particle) -> impl Iterator<Item = (&'a Particle, &'a CardRecord)> + use<'a> {
self.by_axon
.get(axon)
.into_iter()
.flat_map(move |cards| cards.keys().map(move |c| (c, &self.records[c])))
}
pub fn axon_count(&self) -> usize {
self.by_axon.len()
}
pub fn commitment(&self) -> Digest {
self.records.commitment()
}
pub fn legacy_commitment(&self, f: impl FnOnce(&BTreeMap<Particle, CardRecord>) -> Commitment) -> Commitment {
self.records.legacy_commitment(f)
}
pub fn prove(&self, card: &Particle) -> TrieProof {
self.records.prove(card)
}
pub fn pending(&self) -> usize {
self.records.pending()
}
pub fn leaf(card: &Particle, record: &CardRecord) -> Digest {
Keyed::<Particle, CardRecord>::leaf(card, record)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SideSums {
pub plus: u128,
pub minus: u128,
}
impl SideSums {
fn add(&mut self, side: Side, stake: u64) {
match side {
Side::Plus => self.plus += stake as u128,
Side::Minus => self.minus += stake as u128,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Position {
pub card: Particle,
pub record: CardRecord,
pub stake: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AxonPositions {
pub axon: Particle,
pub sums: SideSums,
pub positions: Vec<Position>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PositionSnapshot {
axons: Vec<AxonPositions>,
len: usize,
total: u128,
active: u128,
}
impl PositionSnapshot {
pub fn axons(&self) -> &[AxonPositions] {
&self.axons
}
pub fn get(&self, axon: &Particle) -> Option<&AxonPositions> {
self.axons.binary_search_by(|a| a.axon.cmp(axon)).ok().map(|i| &self.axons[i])
}
pub fn find(&self, axon: &Particle, card: &Particle) -> Option<&Position> {
let a = self.get(axon)?;
a.positions.binary_search_by(|p| p.card.cmp(card)).ok().map(|i| &a.positions[i])
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn total_stake(&self) -> u128 {
self.total
}
pub fn active_stake(&self) -> u128 {
self.active
}
}
impl BbgState {
pub fn stake(&self, card: &Particle) -> u64 {
self.balances.get(&stake_key(card)).copied().unwrap_or(0)
}
pub fn focus_pool(&self, token: &Particle) -> u64 {
self.balances.get(&focus_key(token)).copied().unwrap_or(0)
}
pub fn side_sums(&self, axon: &Particle) -> SideSums {
let mut sums = SideSums::default();
for (record, key) in self.indexed(axon) {
sums.add(record.valence.side(), self.balance_at(key));
}
sums
}
pub fn positions(&self) -> PositionSnapshot {
let mut snap = PositionSnapshot::default();
for (axon, cards) in &self.cards.by_axon {
let mut sums = SideSums::default();
let positions: Vec<Position> = cards
.iter()
.map(|(card, key)| {
let record = self.cards[card];
let stake = self.balance_at(key);
sums.add(record.valence.side(), stake);
if record.valence.is_active() {
snap.active += stake as u128;
}
Position { card: *card, record, stake }
})
.collect();
snap.len += positions.len();
snap.total += sums.plus + sums.minus;
snap.axons.push(AxonPositions { axon: *axon, sums, positions });
}
snap
}
fn indexed<'a>(&'a self, axon: &Particle) -> impl Iterator<Item = (&'a CardRecord, &'a [u8; 32])> + use<'a> {
self.cards
.by_axon
.get(axon)
.into_iter()
.flat_map(move |cards| cards.iter().map(move |(c, key)| (&self.cards[c], key)))
}
fn balance_at(&self, key: &[u8; 32]) -> u64 {
self.balances.get(key).copied().unwrap_or(0)
}
}