use {
sha2::{Digest, Sha256},
std::collections::HashSet,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum IdlHistorySource {
Legacy,
Pmp,
}
impl IdlHistorySource {
pub fn as_suffix(self) -> &'static str {
match self {
Self::Legacy => "legacy",
Self::Pmp => "pmp",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HistoricalIdlVersion {
pub slot: u64,
pub signature: String,
pub source: IdlHistorySource,
pub idl_data: Vec<u8>,
}
pub fn merge_historical_idls(
legacy: Vec<HistoricalIdlVersion>,
pmp: Vec<HistoricalIdlVersion>,
) -> Vec<HistoricalIdlVersion> {
let mut candidates: Vec<_> = legacy.into_iter().chain(pmp).collect();
candidates.sort_by(|a, b| {
b.slot
.cmp(&a.slot)
.then_with(|| source_rank(b.source).cmp(&source_rank(a.source)))
.then_with(|| a.signature.cmp(&b.signature))
});
let mut merged = Vec::new();
let mut seen: HashSet<(u64, [u8; 32])> = HashSet::new();
for item in candidates {
let key = (item.slot, payload_hash(&item.idl_data));
if seen.contains(&key) {
continue;
}
seen.insert(key);
merged.push(item);
}
merged
}
fn source_rank(source: IdlHistorySource) -> u8 {
match source {
IdlHistorySource::Legacy => 0,
IdlHistorySource::Pmp => 1,
}
}
fn payload_hash(bytes: &[u8]) -> [u8; 32] {
Sha256::digest(bytes).into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_prefers_pmp_for_duplicate_slot_and_payload() {
let legacy = HistoricalIdlVersion {
slot: 10,
signature: "legacy".into(),
source: IdlHistorySource::Legacy,
idl_data: b"{}".to_vec(),
};
let pmp = HistoricalIdlVersion {
slot: 10,
signature: "pmp".into(),
source: IdlHistorySource::Pmp,
idl_data: b"{}".to_vec(),
};
let merged = merge_historical_idls(vec![legacy], vec![pmp.clone()]);
assert_eq!(merged, vec![pmp]);
}
}