#![cfg(feature = "memory")]
use crate::index::IndexDb;
use crate::index::keys::{PROPOSAL_KIND_SKILL, PROPOSAL_KIND_TOMBSTONE, proposal_by_id};
use super::memory_ops::MemoryOpError;
use super::types_governance::ProposalRecord;
use super::types_memory::MemoryRecord;
pub(crate) type ProposalItem = (String, ProposalRecord);
pub(crate) struct ListResult {
pub items: Vec<ProposalItem>,
pub truncated: bool,
pub next_cursor: Option<Vec<u8>>,
}
pub(crate) fn list_core(
idx: &IndexDb,
scope: &str,
kind_bytes: &[u8],
limit: usize,
scan_cap: usize,
cursor: Option<&[u8]>,
) -> Result<ListResult, MemoryOpError> {
use std::ops::Bound;
use super::cursor::prefix_upper_bound;
let mut items: Vec<ProposalItem> = Vec::new();
let mut truncated = false;
let mut last_key_bytes: Option<Vec<u8>> = None;
let resume_key: Option<Vec<u8>> = cursor.map(<[u8]>::to_vec);
'outer: for &kind_byte in kind_bytes {
let prefix = crate::index::keys::proposal_ns_prefix(scope, kind_byte);
let upper = prefix_upper_bound(&prefix);
let lower_bound: Bound<Vec<u8>> = match &resume_key {
Some(key) if key.starts_with(&prefix) => Bound::Excluded(key.clone()),
_ => Bound::Included(prefix.clone()),
};
let upper_bound: Bound<Vec<u8>> = match upper {
Some(u) => Bound::Excluded(u),
None => Bound::Unbounded,
};
let iter = idx.proposals.range::<Vec<u8>, _>((lower_bound, upper_bound));
for (scanned, guard) in iter.enumerate() {
if scanned >= scan_cap {
truncated = true;
break 'outer;
}
if items.len() >= limit {
truncated = true;
break 'outer;
}
let (raw_key, raw_val) = guard
.into_inner()
.map_err(|source| MemoryOpError::Fjall { op: "iter", source })?;
let Some((_, _, id)) = crate::index::keys::parse_proposal_by_id(&raw_key) else {
continue;
};
let Ok(record) = rmp_serde::from_slice::<ProposalRecord>(&raw_val) else {
continue;
};
last_key_bytes = Some(raw_key.to_vec());
items.push((id, record));
}
}
let next_cursor = if truncated { last_key_bytes } else { None };
Ok(ListResult {
items,
truncated,
next_cursor,
})
}
pub(crate) fn reject_core(idx: &IndexDb, scope: &str, id: &str) -> Result<(), MemoryOpError> {
let proposal_key = proposal_by_id(scope, PROPOSAL_KIND_SKILL, id);
let tombstone_key = proposal_by_id(scope, PROPOSAL_KIND_TOMBSTONE, id);
idx.proposals
.remove(proposal_key)
.map_err(|source| MemoryOpError::Fjall { op: "remove", source })?;
idx.proposals
.insert(tombstone_key, b"")
.map_err(|source| MemoryOpError::Fjall { op: "insert", source })
}
pub(crate) fn apply_mine_core(idx: &IndexDb, scope: &str, candidates: &[ProposalItem]) -> Result<u32, MemoryOpError> {
let mut mined: u32 = 0;
for (id, record) in candidates {
let tombstone_key = proposal_by_id(scope, PROPOSAL_KIND_TOMBSTONE, id);
let has_tombstone = idx
.proposals
.get(&tombstone_key)
.map_err(|source| MemoryOpError::Fjall { op: "get", source })?
.is_some();
if has_tombstone {
continue;
}
let raw_key = proposal_by_id(scope, PROPOSAL_KIND_SKILL, id);
let bytes = rmp_serde::to_vec_named(record).map_err(MemoryOpError::Serialize)?;
idx.proposals
.insert(raw_key, bytes)
.map_err(|source| MemoryOpError::Fjall { op: "insert", source })?;
mined += 1;
}
Ok(mined)
}
pub(crate) fn get_core(idx: &IndexDb, scope: &str, id: &str) -> Result<Option<ProposalRecord>, MemoryOpError> {
let raw_key = proposal_by_id(scope, PROPOSAL_KIND_SKILL, id);
let bytes = idx
.proposals
.get(raw_key)
.map_err(|source| MemoryOpError::Fjall { op: "get", source })?;
Ok(bytes.and_then(|b| rmp_serde::from_slice(&b).ok()))
}
pub(crate) fn promote_core(
idx: &IndexDb,
scope: &str,
memory_key: &str,
record: &MemoryRecord,
proposal_id: &str,
) -> Result<(), MemoryOpError> {
let mem_key = crate::index::keys::memory_by_key(scope, crate::index::keys::MEMORY_VIS_GROUP, "", memory_key);
let bytes = rmp_serde::to_vec_named(record).map_err(MemoryOpError::Serialize)?;
idx.memory_by_key
.insert(mem_key, bytes)
.map_err(|source| MemoryOpError::Fjall { op: "insert", source })?;
let raw_key = proposal_by_id(scope, PROPOSAL_KIND_SKILL, proposal_id);
idx.proposals
.remove(raw_key)
.map_err(|source| MemoryOpError::Fjall { op: "remove", source })
}
pub(crate) struct AuditScanArgs<'a> {
pub vis_byte: u8,
pub owner: &'a str,
pub key: Option<&'a str>,
pub from_archive: bool,
pub limit: usize,
pub scan_cap: usize,
}
pub(crate) fn audit_scan_core(
idx: &IndexDb,
scope: &str,
args: &AuditScanArgs<'_>,
) -> Result<Vec<(String, Vec<u8>)>, MemoryOpError> {
use crate::index::keys::{memory_by_key, memory_by_key_ns_prefix, parse_memory_key_only};
let keyspace = if args.from_archive {
&idx.memory_archive
} else {
&idx.memory_by_key
};
if let Some(single_key) = args.key {
let raw_key = memory_by_key(scope, args.vis_byte, args.owner, single_key);
let value = keyspace
.get(raw_key)
.map_err(|source| MemoryOpError::Fjall { op: "get", source })?;
return Ok(value
.map(|v| vec![(single_key.to_string(), v.to_vec())])
.unwrap_or_default());
}
let ns_prefix = memory_by_key_ns_prefix(scope, args.vis_byte, args.owner);
let mut items: Vec<(String, Vec<u8>)> = Vec::new();
for (scanned, guard) in keyspace.prefix(&ns_prefix).enumerate() {
if items.len() >= args.limit || scanned >= args.scan_cap {
break;
}
let (raw_key_bytes, raw_val) = guard
.into_inner()
.map_err(|source| MemoryOpError::Fjall { op: "iter", source })?;
let Some(parsed_key) = parse_memory_key_only(&raw_key_bytes) else {
continue;
};
items.push((parsed_key.to_string(), raw_val.to_vec()));
}
Ok(items)
}
#[cfg(all(feature = "comms", any(unix, windows)))]
pub(crate) fn audit_persist_core(
idx: &IndexDb,
scope: &str,
mutations: &[crate::comms::proposals_proto::AuditMutation],
) -> Result<(), MemoryOpError> {
use crate::index::keys::memory_by_key;
for mutation in mutations {
let raw_key = memory_by_key(scope, mutation.vis_byte, &mutation.owner, &mutation.key);
let bytes = rmp_serde::to_vec_named(&mutation.record).map_err(MemoryOpError::Serialize)?;
if mutation.archive {
idx.memory_archive
.insert(&raw_key, bytes)
.map_err(|source| MemoryOpError::Fjall { op: "insert", source })?;
idx.memory_by_key
.remove(&raw_key)
.map_err(|source| MemoryOpError::Fjall { op: "remove", source })?;
} else {
idx.memory_by_key
.insert(&raw_key, bytes)
.map_err(|source| MemoryOpError::Fjall { op: "insert", source })?;
}
}
Ok(())
}
#[cfg(all(feature = "comms", any(unix, windows)))]
pub(crate) fn run_governance_op(
idx: &IndexDb,
scope: &str,
op: &crate::comms::proposals_proto::GovernanceOp,
) -> Result<crate::comms::proposals_proto::GovernanceOutcome, MemoryOpError> {
use crate::comms::proposals_proto::{GovernanceOp, GovernanceOutcome};
match op {
GovernanceOp::ProposalsList {
kind_bytes,
limit,
scan_cap,
cursor,
} => {
let result = list_core(
idx,
scope,
kind_bytes,
*limit as usize,
*scan_cap as usize,
cursor.as_deref(),
)?;
Ok(GovernanceOutcome::ProposalsListed {
items: result.items,
truncated: result.truncated,
next_cursor: result.next_cursor,
})
}
GovernanceOp::ProposalReject { id } => {
reject_core(idx, scope, id)?;
Ok(GovernanceOutcome::Rejected)
}
GovernanceOp::ProposalsMineApply { candidates } => {
let count = apply_mine_core(idx, scope, candidates)?;
Ok(GovernanceOutcome::Mined { count })
}
GovernanceOp::ProposalGet { id } => Ok(GovernanceOutcome::Proposal(get_core(idx, scope, id)?)),
GovernanceOp::ProposalPromote {
proposal_id,
memory_key,
record,
} => {
promote_core(idx, scope, memory_key, record, proposal_id)?;
Ok(GovernanceOutcome::Promoted)
}
GovernanceOp::AuditScan {
vis_byte,
owner,
key,
from_archive,
limit,
scan_cap,
} => {
let args = AuditScanArgs {
vis_byte: *vis_byte,
owner,
key: key.as_deref(),
from_archive: *from_archive,
limit: *limit as usize,
scan_cap: *scan_cap as usize,
};
let items = audit_scan_core(idx, scope, &args)?;
Ok(GovernanceOutcome::AuditScanned { items })
}
GovernanceOp::AuditPersist { mutations } => {
audit_persist_core(idx, scope, mutations)?;
Ok(GovernanceOutcome::AuditPersisted)
}
}
}