use super::*;
use lyquor_primitives::oracle::{
OracleConfig as OracleConfigWire, OracleConfigDelta as OracleConfigDeltaWire, OracleSigner,
};
use lyquor_primitives::{Address, Bytes, CallParams, Hash, HashBytes, InputABI};
fn is_delta_canonical(delta: &OracleConfigDeltaWire) -> bool {
if !delta.upsert.windows(2).all(|w| w[0].id < w[1].id) || !delta.remove.windows(2).all(|w| w[0] < w[1]) {
return false;
}
let mut i = 0usize;
let mut j = 0usize;
while i < delta.remove.len() && j < delta.upsert.len() {
let rid = delta.remove[i];
let uid = delta.upsert[j].id;
if rid == uid {
return false;
}
if rid < uid {
i += 1;
} else {
j += 1;
}
}
true
}
#[derive(Clone)]
struct OracleConfig {
committee: HashMap<SignerID, Vec<u8>>,
threshold: u16,
}
impl OracleConfig {
fn new() -> Self {
Self {
committee: new_hashmap(),
threshold: 0,
}
}
#[inline]
fn is_valid(&self) -> bool {
self.threshold != 0 &&
self.committee.len() <= u16::MAX as usize &&
self.committee.len() >= self.threshold as usize
}
fn to_wire(&self) -> OracleConfigWire {
let mut committee: Vec<_> = self
.committee
.iter()
.map(|(id, key)| OracleSigner {
id: *id,
key: key.clone().into(),
})
.collect();
committee.sort_by_key(|s| s.id);
OracleConfigWire {
committee,
threshold: self.threshold,
}
}
fn after_delta(&self, delta: &OracleConfigDeltaWire) -> Option<Self> {
if !is_delta_canonical(delta) {
return None;
}
let mut next = self.clone();
for id in delta.remove.iter() {
next.committee.remove(id);
}
for s in delta.upsert.iter() {
next.committee.insert(s.id, s.key.to_vec());
}
if let Some(threshold) = delta.threshold {
next.threshold = threshold;
}
if !next.is_valid() {
return None;
}
Some(next)
}
}
pub struct OracleDest {
config: OracleConfig,
config_hash: HashBytes,
change_count: u32,
used_nonce: HashSet<Hash>,
epoch: u32,
}
impl Default for OracleDest {
fn default() -> Self {
Self {
config: OracleConfig::new(),
config_hash: [0; 32].into(),
change_count: 0,
used_nonce: new_hashset(),
epoch: 0,
}
}
}
impl OracleDest {
const MAX_NONCE_PER_EPOCH: usize = 1_000_000;
const MIN_NONCE_NEXT_EPOCH: usize = Self::MAX_NONCE_PER_EPOCH * 9 / 10;
pub fn get_epoch(&self) -> u32 {
self.epoch
}
pub fn get_config_hash(&self) -> &HashBytes {
&self.config_hash
}
pub fn get_change_count(&self) -> u32 {
self.change_count
}
pub fn get_config(&self) -> lyquor_primitives::oracle::OracleConfig {
self.config.to_wire()
}
pub fn signer_node_id(&self, id: SignerID) -> Option<NodeID> {
let key = self.config.committee.get(&id)?;
let key: [u8; 32] = key.as_slice().try_into().ok()?;
Some(NodeID::from(key))
}
fn verify_lvm_binding(me: LyquidID, params: &CallParams, oc: &OracleCert) -> bool {
let backend = match lyquor_api::sequence_backend_id() {
Ok(id) => id,
Err(_) => return false,
};
if oc.header.target.seq_id != backend {
return false;
}
if params.abi != InputABI::Lyquor {
return false;
}
match &oc.header.target.target {
OracleServiceTarget::LVM(id) => {
if *id != me {
return false;
}
}
_ => return false,
}
true
}
fn update(&mut self, header: &OracleHeader, next_config: Option<OracleConfig>, change_count: u32) -> bool {
let update_config = next_config.is_some();
let nonce: Hash = header.nonce.clone().into();
let epoch_delta = match header.epoch.checked_sub(self.epoch) {
Some(delta) => delta,
None => return false,
};
match epoch_delta {
0 => {
if update_config ||
self.used_nonce.contains(&nonce) ||
self.used_nonce.len() >= Self::MAX_NONCE_PER_EPOCH
{
return false;
}
}
1 => {
if !update_config && self.used_nonce.len() < Self::MIN_NONCE_NEXT_EPOCH {
return false;
}
self.epoch = header.epoch;
self.used_nonce.clear();
self.change_count = change_count;
if let Some(config) = next_config {
self.config = config;
self.config_hash = header.config_hash.clone();
}
}
_ => return false,
}
self.used_nonce.insert(nonce)
}
pub fn verify(&mut self, me: LyquidID, params: lyquor_primitives::CallParams, oc: &OracleCert) -> bool {
if !Self::verify_lvm_binding(me, ¶ms, oc) {
return false;
}
if self.epoch == 0 {
return false;
}
if oc.header.epoch != self.epoch {
return false;
}
if oc.header.config_hash != self.config_hash {
return false;
}
if !super::verify_oracle_cert_signatures(oc, ¶ms, self.config.threshold, oc.header.target.cipher(), |id| {
self.config.committee.get(&id).map(|key| Bytes::copy_from_slice(key))
}) {
return false;
}
self.update(&oc.header, None, self.change_count)
}
pub fn verify_epoch_advance(
&mut self, me: LyquidID, caller: Address, topic: &str, config_delta: &OracleConfigDeltaWire, change_count: u32,
oc: &OracleCert,
) -> bool {
let params = CallParams {
origin: Address::ZERO,
caller,
group: "oracle::internal".to_string(),
method: ADVANCE_EPOCH_METHOD.into(),
input: encode_by_fields!(
topic: String = topic.to_string(),
config_delta: OracleConfigDeltaWire = config_delta.clone(),
change_count: u32 = change_count
)
.into(),
abi: InputABI::Lyquor,
};
if !Self::verify_lvm_binding(me, ¶ms, oc) {
return false;
}
if oc.header.epoch != self.epoch.wrapping_add(1) {
return false;
}
let next_config =
if config_delta.upsert.is_empty() && config_delta.remove.is_empty() && config_delta.threshold.is_none() {
None
} else {
match self.config.after_delta(config_delta) {
Some(config) => Some(config),
None => return false,
}
};
let (cert_config, config_hash) = match (self.epoch == 0, next_config.as_ref()) {
(true, Some(config)) => (config, config.to_wire().to_hash().into()),
(true, None) => return false,
(false, Some(config)) => (&self.config, config.to_wire().to_hash().into()),
(false, None) => (&self.config, self.config_hash.clone()),
};
if oc.header.config_hash != config_hash {
return false;
}
if !super::verify_oracle_cert_signatures(oc, ¶ms, cert_config.threshold, oc.header.target.cipher(), |id| {
cert_config.committee.get(&id).map(|key| Bytes::copy_from_slice(key))
}) {
return false;
}
self.update(&oc.header, next_config, change_count)
}
}