use crate::combination_iterator::CombinationIterator;
use crate::digit_decomposition::group_by_ignoring_digits;
use crate::digit_trie::{DigitTrie, DigitTrieDump, DigitTrieIter};
use crate::multi_trie::{MultiTrie, MultiTrieDump, MultiTrieIterator};
use crate::utils::{get_value_callback, pre_pad_vec};
use crate::{DlcTrie, IndexedPath, LookupResult, OracleNumericInfo, RangeInfo, TrieIterInfo};
use ddk_dlc::{Error, RangePayout};
#[derive(Clone)]
pub struct MultiOracleTrie {
digit_trie: DigitTrie<Vec<RangeInfo>>,
threshold: usize,
oracle_numeric_infos: OracleNumericInfo,
extra_cover_trie: Option<MultiTrie<RangeInfo>>,
}
pub struct MultiOracleTrieDump {
pub digit_trie_dump: DigitTrieDump<Vec<RangeInfo>>,
pub threshold: usize,
pub oracle_numeric_infos: OracleNumericInfo,
pub extra_cover_trie_dump: Option<MultiTrieDump<RangeInfo>>,
}
impl MultiOracleTrie {
pub fn dump(&self) -> MultiOracleTrieDump {
MultiOracleTrieDump {
digit_trie_dump: self.digit_trie.dump(),
threshold: self.threshold,
oracle_numeric_infos: self.oracle_numeric_infos.clone(),
extra_cover_trie_dump: self.extra_cover_trie.as_ref().map(|trie| trie.dump()),
}
}
pub fn from_dump(dump: MultiOracleTrieDump) -> MultiOracleTrie {
let MultiOracleTrieDump {
digit_trie_dump,
threshold,
oracle_numeric_infos,
extra_cover_trie_dump,
} = dump;
MultiOracleTrie {
digit_trie: DigitTrie::from_dump(digit_trie_dump),
threshold,
oracle_numeric_infos,
extra_cover_trie: extra_cover_trie_dump.map(MultiTrie::from_dump),
}
}
fn get_agreeing_oracles(
&self,
paths: &[(usize, Vec<usize>)],
) -> Option<Vec<(Vec<usize>, Vec<usize>)>> {
let mut hash_set: std::collections::HashMap<Vec<usize>, Vec<usize>> =
std::collections::HashMap::new();
for path in paths {
let index = path.0;
let outcome_path = &path.1;
if let Some(index_set) = hash_set.get_mut(outcome_path) {
index_set.push(index);
} else {
let index_set = vec![index];
hash_set.insert(outcome_path.to_vec(), index_set);
}
}
if hash_set.is_empty() {
return None;
}
let mut values: Vec<_> = hash_set.into_iter().collect();
values.sort_by(|x, y| x.1.len().partial_cmp(&y.1.len()).unwrap());
let res = values
.into_iter()
.filter(|x| x.1.len() >= self.threshold)
.collect::<Vec<_>>();
if !res.is_empty() {
Some(res)
} else {
None
}
}
pub fn look_up(&self, paths: &[(usize, Vec<usize>)]) -> Option<(RangeInfo, Vec<IndexedPath>)> {
let min_nb_digits = self.oracle_numeric_infos.get_min_nb_digits();
let stripped_paths = paths
.iter()
.filter_map(|x| {
let extra_len = x.1.len().checked_sub(min_nb_digits)?;
if extra_len == 0 {
Some((x.0, x.1.clone()))
} else if x.1.iter().take(extra_len).all(|x| *x == 0) {
let mut cloned = x.1.clone();
cloned.drain(..extra_len);
Some((x.0, cloned))
} else {
None
}
})
.collect::<Vec<_>>();
let agreeing_combinations = self.get_agreeing_oracles(&stripped_paths);
if let Some(sufficient_combinations) = agreeing_combinations {
for (path, combination) in sufficient_combinations {
debug_assert_eq!(
path.len(),
min_nb_digits,
"Expected length {} got length {}",
min_nb_digits,
path.len()
);
if let Some(res) = self.digit_trie.look_up(&path) {
let sufficient_combination: Vec<_> =
combination.into_iter().take(self.threshold).collect();
if let Some(position) = CombinationIterator::new(
self.oracle_numeric_infos.nb_digits.len(),
self.threshold,
)
.get_index_for_combination(&sufficient_combination)
{
return Some((
res[0].value[position].clone(),
sufficient_combination
.iter()
.map(|x| {
let actual_len = res[0].path.len()
+ self.oracle_numeric_infos.nb_digits[*x]
- min_nb_digits;
(*x, pre_pad_vec(res[0].path.clone(), actual_len))
})
.collect::<Vec<_>>(),
));
}
}
}
}
if let Some(extra_cover_trie) = &self.extra_cover_trie {
if let Some(res) = extra_cover_trie.look_up(paths) {
return Some((res.0.clone(), res.1));
}
}
None
}
pub fn new(oracle_numeric_infos: &OracleNumericInfo, threshold: usize) -> Result<Self, Error> {
if oracle_numeric_infos.nb_digits.is_empty() {
return Err(Error::InvalidArgument(
"Oracle numeric infos is empty".to_string(),
));
}
let digit_trie = DigitTrie::new(oracle_numeric_infos.base);
let extra_cover_trie = if oracle_numeric_infos.has_diff_nb_digits() {
Some(MultiTrie::new(oracle_numeric_infos, threshold, 1, 2, true))
} else {
None
};
Ok(MultiOracleTrie {
digit_trie,
threshold,
oracle_numeric_infos: oracle_numeric_infos.clone(),
extra_cover_trie,
})
}
}
impl<'a> DlcTrie<'a, MultiOracleTrieIter<'a>> for MultiOracleTrie {
fn generate(
&mut self,
adaptor_index_start: usize,
outcomes: &[RangePayout],
) -> Result<Vec<TrieIterInfo>, Error> {
let threshold = self.threshold;
let nb_oracles = self.oracle_numeric_infos.nb_digits.len();
let min_nb_digits = self.oracle_numeric_infos.get_min_nb_digits();
let mut adaptor_index = adaptor_index_start;
let mut trie_infos = Vec::new();
let oracle_numeric_infos = &self.oracle_numeric_infos;
for (cet_index, outcome) in outcomes.iter().enumerate() {
if outcome.count == 0 {
return Err(Error::InvalidArgument("Outcome count is 0".to_string()));
}
let groups = group_by_ignoring_digits(
outcome.start,
outcome.start + outcome.count - 1,
self.digit_trie.base,
min_nb_digits,
);
for group in groups {
let mut get_value = |_: Option<Vec<RangeInfo>>| -> Result<Vec<RangeInfo>, Error> {
let combination_iterator = CombinationIterator::new(nb_oracles, threshold);
let mut range_infos: Vec<RangeInfo> = Vec::new();
for selector in combination_iterator {
let range_info = RangeInfo {
cet_index,
adaptor_index,
};
adaptor_index += 1;
let paths = oracle_numeric_infos
.nb_digits
.iter()
.enumerate()
.filter_map(|(i, nb_digits)| {
if !selector.contains(&i) {
return None;
}
let expected_len = group.len() + nb_digits - min_nb_digits;
Some(pre_pad_vec(group.clone(), expected_len))
})
.collect();
let trie_info = TrieIterInfo {
paths,
indexes: selector,
value: range_info.clone(),
};
trie_infos.push(trie_info);
range_infos.push(range_info);
}
Ok(range_infos)
};
self.digit_trie.insert(&group, &mut get_value)?;
}
}
if let Some(extra_cover_trie) = &mut self.extra_cover_trie {
let mut get_value =
|paths: &[Vec<usize>], oracle_indexes: &[usize]| -> Result<RangeInfo, Error> {
get_value_callback(
paths,
oracle_indexes,
outcomes.len() - 1,
&mut adaptor_index,
&mut trie_infos,
)
};
extra_cover_trie.insert_max_paths(&mut get_value)?;
}
Ok(trie_infos)
}
fn iter(&'a self) -> MultiOracleTrieIter<'a> {
let digit_trie_iterator = DigitTrieIter::new(&self.digit_trie);
let extra_cover_trie_iterator = self.extra_cover_trie.as_ref().map(MultiTrieIterator::new);
MultiOracleTrieIter {
digit_trie_iterator,
extra_cover_trie_iterator,
cur_res: None,
cur_index: 0,
combination_iter: CombinationIterator::new(
self.oracle_numeric_infos.nb_digits.len(),
self.threshold,
),
oracle_numeric_infos: self.oracle_numeric_infos.clone(),
}
}
}
pub struct MultiOracleTrieIter<'a> {
digit_trie_iterator: DigitTrieIter<'a, Vec<RangeInfo>>,
extra_cover_trie_iterator: Option<MultiTrieIterator<'a, RangeInfo>>,
cur_res: Option<LookupResult<'a, Vec<RangeInfo>, usize>>,
cur_index: usize,
combination_iter: CombinationIterator,
oracle_numeric_infos: OracleNumericInfo,
}
impl Iterator for MultiOracleTrieIter<'_> {
type Item = TrieIterInfo;
fn next(&mut self) -> Option<Self::Item> {
if self.cur_res.is_none() {
self.cur_res = self.digit_trie_iterator.next();
}
let res = match &self.cur_res {
None => {
if let Some(extra_cover_trie_iterator) = &mut self.extra_cover_trie_iterator {
let res = extra_cover_trie_iterator.next()?;
let (indexes, paths) = res.path.iter().fold(
(Vec::new(), Vec::new()),
|(mut indexes, mut paths), x| {
indexes.push(x.0);
paths.push(x.1.clone());
(indexes, paths)
},
);
return Some(TrieIterInfo {
indexes,
paths,
value: res.value.clone(),
});
} else {
return None;
}
}
Some(res) => res,
};
let indexes = match self.combination_iter.next() {
Some(selector) => selector,
None => {
self.cur_res = None;
self.cur_index = 0;
self.combination_iter = CombinationIterator::new(
self.combination_iter.nb_elements,
self.combination_iter.nb_selected,
);
return self.next();
}
};
let min_nb_digits = self.oracle_numeric_infos.get_min_nb_digits();
let paths = &std::iter::repeat_n(res.path.clone(), indexes.len())
.take(indexes.len())
.zip(indexes.iter())
.map(|(x, i)| {
let extra_len = self.oracle_numeric_infos.nb_digits[*i] - min_nb_digits;
if extra_len == 0 {
x
} else {
let expected_size = extra_len + x.len();
pre_pad_vec(x, expected_size)
}
})
.collect::<Vec<Vec<_>>>();
let value = res.value[self.cur_index].clone();
self.cur_index += 1;
Some(TrieIterInfo {
indexes,
paths: paths.clone(),
value,
})
}
}
#[cfg(test)]
mod tests {
use bitcoin::Amount;
use ddk_dlc::{Payout, RangePayout};
use crate::{test_utils::get_variable_oracle_numeric_infos, DlcTrie};
use super::MultiOracleTrie;
#[test]
fn test_longer_outcome_len() {
let range_payouts = vec![RangePayout {
start: 0,
count: 1023,
payout: Payout {
offer: Amount::from_sat(200000000),
accept: Amount::ZERO,
},
}];
let oracle_numeric_infos = get_variable_oracle_numeric_infos(&[10, 15, 15, 15, 12], 2);
let mut multi_oracle_trie = MultiOracleTrie::new(&oracle_numeric_infos, 2).unwrap();
multi_oracle_trie.generate(0, &range_payouts).unwrap();
multi_oracle_trie
.look_up(&[
(1, vec![0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0]),
(4, vec![0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0]),
])
.expect("Could not retrieve path with extra len.");
}
#[test]
fn test_over_bound_outcome() {
let range_payouts = vec![RangePayout {
start: 0,
count: 1023,
payout: Payout {
offer: Amount::from_sat(200000000),
accept: Amount::ZERO,
},
}];
let oracle_numeric_infos = get_variable_oracle_numeric_infos(&[10, 15, 15, 15, 12], 2);
let mut multi_oracle_trie = MultiOracleTrie::new(&oracle_numeric_infos, 2).unwrap();
multi_oracle_trie.generate(0, &range_payouts).unwrap();
multi_oracle_trie
.look_up(&[
(1, vec![1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0]),
(4, vec![0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0]),
])
.expect("Could not retrieve path with extra len.");
}
#[test]
fn test_invalid_range_payout() {
let range_payouts = vec![RangePayout {
start: 0,
count: 0,
payout: Payout {
offer: Amount::ZERO,
accept: Amount::from_sat(200000000),
},
}];
let oracle_numeric_infos = get_variable_oracle_numeric_infos(&[13, 12], 2);
let mut multi_oracle_trie = MultiOracleTrie::new(&oracle_numeric_infos, 2).unwrap();
multi_oracle_trie
.generate(0, &range_payouts)
.expect_err("Should fail when given a range payout with a count of 0");
}
}