use crate::{
types::{ConsumedOutput, CreatedOutput, OutputDiff, Receipt},
workers::{
consensus::worker::EXTRA_PRUNING_DEPTH,
pruning::{
error::Error,
metrics::{ConfirmedDataPruningMetrics, MilestoneDataPruningMetrics, UnconfirmedDataPruningMetrics},
},
storage::StorageBackend,
},
};
use bee_message::{
milestone::{Milestone, MilestoneIndex},
output::OutputId,
payload::{
indexation::{IndexationPayload, PaddedIndex},
transaction::Essence,
Payload,
},
Message, MessageId,
};
use bee_storage::access::{Batch, Fetch};
use bee_tangle::{
metadata::MessageMetadata, solid_entry_point::SolidEntryPoint, unreferenced_message::UnreferencedMessage, Tangle,
};
use hashbrown::{HashMap, HashSet};
use ref_cast::RefCast;
use std::collections::VecDeque;
pub type Messages = HashSet<MessageId>;
pub type ApproverCache = HashMap<MessageId, MilestoneIndex>;
pub type Seps = HashMap<SolidEntryPoint, MilestoneIndex>;
#[derive(Eq, PartialEq, Hash)]
pub struct Edge {
pub from_parent: MessageId,
pub to_child: MessageId,
}
pub async fn prune_confirmed_data<S: StorageBackend>(
tangle: &Tangle<S>,
storage: &S,
batch: &mut S::Batch,
prune_index: MilestoneIndex,
current_seps: &Seps,
) -> Result<(Seps, ConfirmedDataPruningMetrics), Error> {
let mut visited = Messages::with_capacity(512);
let mut approver_cache = ApproverCache::with_capacity(512);
let mut new_seps = Seps::with_capacity(512);
let mut metrics = ConfirmedDataPruningMetrics::default();
let mitigation_threshold = tangle.config().below_max_depth() + EXTRA_PRUNING_DEPTH;
let prune_id = *Fetch::<MilestoneIndex, Milestone>::fetch(storage, &prune_index)
.map_err(|e| Error::Storage(Box::new(e)))?
.ok_or(Error::MissingMilestone(prune_index))?
.message_id();
let mut to_visit: VecDeque<_> = vec![prune_id].into_iter().collect();
while let Some(message_id) = to_visit.pop_front() {
if visited.contains(&message_id) {
metrics.msg_already_visited += 1;
continue;
}
if current_seps.contains_key(SolidEntryPoint::ref_cast(&message_id)) {
metrics.references_sep += 1;
continue;
}
let msg = match Fetch::<MessageId, Message>::fetch(storage, &message_id)
.map_err(|e| Error::Storage(Box::new(e)))?
.ok_or(Error::MissingMessage(message_id))
{
Ok(msg) => msg,
Err(e) => {
log::error!(
"failed to fetch `Message` associated with message id {} during past-cone traversal of milestone {} ({})",
&message_id,
&prune_index,
&prune_id,
);
return Err(e);
}
};
let payload = msg.payload().as_ref();
if let Some(indexation) = unwrap_indexation(payload) {
let padded_index = indexation.padded_index();
prune_indexation_data(storage, batch, &(padded_index, message_id))?;
metrics.prunable_indexations += 1;
}
let parents = msg.parents();
for parent_id in parents.iter() {
prune_edge(storage, batch, &(*parent_id, message_id))?;
metrics.prunable_edges += 1;
}
to_visit.extend(msg.into_parents().iter());
visited.insert(message_id);
prune_message_and_metadata(storage, batch, &message_id)?;
let approvers = Fetch::<MessageId, Vec<MessageId>>::fetch(storage, &message_id)
.map_err(|e| Error::Storage(Box::new(e)))?
.ok_or(Error::MissingApprovers(message_id))?;
let mut unvisited_approvers = approvers.into_iter().filter(|id| !visited.contains(id)).peekable();
if unvisited_approvers.peek().is_none() {
metrics.all_approvers_visited += 1;
continue;
}
metrics.not_all_approvers_visited += 1;
let mut max_conf_index = *prune_index;
for unvisited_id in unvisited_approvers {
let approver_conf_index = if let Some(conf_index) = approver_cache.get(&unvisited_id) {
metrics.approver_cache_hit += 1;
**conf_index
} else {
metrics.approver_cache_miss += 1;
let unvisited_md = Fetch::<MessageId, MessageMetadata>::fetch(storage, &unvisited_id)
.map_err(|e| Error::Storage(Box::new(e)))?
.ok_or(Error::MissingMetadata(unvisited_id))?;
let conf_index = unvisited_md.milestone_index().unwrap_or_else(|| {
log::trace!(
"Bug mitigation: Using '{} + mitigation_threshold ({})' for approver '{}'",
prune_index,
mitigation_threshold,
&unvisited_id
);
prune_index + mitigation_threshold
});
approver_cache.insert(unvisited_id, conf_index);
*conf_index
};
max_conf_index = max_conf_index.max(approver_conf_index);
}
if max_conf_index > *prune_index {
new_seps.insert(message_id.into(), max_conf_index.into());
log::trace!("New SEP: {} until {}", message_id, max_conf_index);
metrics.found_seps += 1;
}
}
metrics.prunable_messages = visited.len();
metrics.new_seps = new_seps.len();
Ok((new_seps, metrics))
}
pub async fn prune_unconfirmed_data<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
prune_index: MilestoneIndex,
) -> Result<UnconfirmedDataPruningMetrics, Error> {
let mut metrics = UnconfirmedDataPruningMetrics::default();
let unconf_msgs = match Fetch::<MilestoneIndex, Vec<UnreferencedMessage>>::fetch(storage, &prune_index)
.map_err(|e| Error::Storage(Box::new(e)))?
{
Some(unconf_msgs) => {
if unconf_msgs.is_empty() {
metrics.none_received = true;
Vec::new()
} else {
unconf_msgs
}
}
None => {
metrics.none_received = true;
Vec::new()
}
};
'outer_loop: for unconf_msg_id in unconf_msgs.iter().map(|unconf_msg| unconf_msg.message_id()) {
match Fetch::<MessageId, MessageMetadata>::fetch(storage, unconf_msg_id)
.map_err(|e| Error::Storage(Box::new(e)))?
{
Some(msg_meta) => {
if msg_meta.flags().is_referenced() {
metrics.were_confirmed += 1;
continue;
} else {
log::trace!("'referenced' flag not set for {}", unconf_msg_id);
let unconf_approvers = Fetch::<MessageId, Vec<MessageId>>::fetch(storage, unconf_msg_id)
.map_err(|e| Error::Storage(Box::new(e)))?
.ok_or(Error::MissingApprovers(*unconf_msg_id))?;
for unconf_approver_id in unconf_approvers {
if let Some(unconf_approver_md) =
Fetch::<MessageId, MessageMetadata>::fetch(storage, &unconf_approver_id)
.map_err(|e| Error::Storage(Box::new(e)))?
{
if unconf_approver_md.flags().is_referenced() {
continue 'outer_loop;
}
}
}
log::trace!("all of '{}'s approvers are flagged 'unreferenced'", unconf_msg_id);
}
}
None => {
metrics.already_pruned += 1;
continue;
}
}
match Fetch::<MessageId, Message>::fetch(storage, unconf_msg_id).map_err(|e| Error::Storage(Box::new(e)))? {
Some(msg) => {
let payload = msg.payload().as_ref();
let parents = msg.parents();
prune_message_and_metadata(storage, batch, unconf_msg_id)?;
log::trace!("Pruned unconfirmed msg {} at {}.", unconf_msg_id, prune_index);
if let Some(indexation) = unwrap_indexation(payload) {
let padded_index = indexation.padded_index();
let message_id = *unconf_msg_id;
prune_indexation_data(storage, batch, &(padded_index, message_id))?;
metrics.prunable_indexations += 1;
}
for parent in parents.iter() {
prune_edge(storage, batch, &(*parent, *unconf_msg_id))?;
metrics.prunable_edges += 1;
}
}
None => {
metrics.already_pruned += 1;
continue;
}
}
Batch::<(MilestoneIndex, UnreferencedMessage), ()>::batch_delete(
storage,
batch,
&(prune_index, (*unconf_msg_id).into()),
)
.map_err(|e| Error::Storage(Box::new(e)))?;
metrics.prunable_messages += 1;
}
Ok(metrics)
}
pub async fn prune_milestone_data<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
prune_index: MilestoneIndex,
should_prune_receipts: bool,
) -> Result<MilestoneDataPruningMetrics, Error> {
let mut metrics = MilestoneDataPruningMetrics::default();
prune_milestone(storage, batch, prune_index).await?;
prune_output_diff(storage, batch, prune_index).await?;
if should_prune_receipts {
metrics.receipts = prune_receipts(storage, batch, prune_index).await?;
}
Ok(metrics)
}
fn prune_message_and_metadata<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
message_id: &MessageId,
) -> Result<(), Error> {
Batch::<MessageId, Message>::batch_delete(storage, batch, message_id).map_err(|e| Error::Storage(Box::new(e)))?;
Batch::<MessageId, MessageMetadata>::batch_delete(storage, batch, message_id)
.map_err(|e| Error::Storage(Box::new(e)))?;
Ok(())
}
fn prune_edge<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
edge: &(MessageId, MessageId),
) -> Result<(), Error> {
Batch::<(MessageId, MessageId), ()>::batch_delete(storage, batch, edge).map_err(|e| Error::Storage(Box::new(e)))?;
Ok(())
}
fn prune_indexation_data<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
index_message_id: &(PaddedIndex, MessageId),
) -> Result<(), Error> {
Batch::<(PaddedIndex, MessageId), ()>::batch_delete(storage, batch, index_message_id)
.map_err(|e| Error::Storage(Box::new(e)))?;
Ok(())
}
async fn prune_milestone<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
index: MilestoneIndex,
) -> Result<(), Error> {
Batch::<MilestoneIndex, Milestone>::batch_delete(storage, batch, &index)
.map_err(|e| Error::Storage(Box::new(e)))?;
Ok(())
}
async fn prune_output_diff<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
index: MilestoneIndex,
) -> Result<(), Error> {
if let Some(output_diff) =
Fetch::<MilestoneIndex, OutputDiff>::fetch(storage, &index).map_err(|e| Error::Storage(Box::new(e)))?
{
for consumed_output in output_diff.consumed_outputs() {
Batch::<OutputId, ConsumedOutput>::batch_delete(storage, batch, consumed_output)
.map_err(|e| Error::Storage(Box::new(e)))?;
Batch::<OutputId, CreatedOutput>::batch_delete(storage, batch, consumed_output)
.map_err(|e| Error::Storage(Box::new(e)))?;
}
if let Some(_treasury_diff) = output_diff.treasury_diff() {
}
}
Batch::<MilestoneIndex, OutputDiff>::batch_delete(storage, batch, &index)
.map_err(|e| Error::Storage(Box::new(e)))?;
Ok(())
}
async fn prune_receipts<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
index: MilestoneIndex,
) -> Result<usize, Error> {
let receipts = Fetch::<MilestoneIndex, Vec<Receipt>>::fetch(storage, &index)
.map_err(|e| Error::Storage(Box::new(e)))?
.unwrap();
let mut num = 0;
for receipt in receipts.into_iter() {
Batch::<(MilestoneIndex, Receipt), ()>::batch_delete(storage, batch, &(index, receipt))
.map_err(|e| Error::Storage(Box::new(e)))?;
num += 1;
}
Ok(num)
}
fn unwrap_indexation(payload: Option<&Payload>) -> Option<&IndexationPayload> {
match payload {
Some(Payload::Indexation(indexation)) => Some(indexation),
Some(Payload::Transaction(transaction)) =>
{
#[allow(irrefutable_let_patterns)]
if let Essence::Regular(essence) = transaction.essence() {
if let Some(Payload::Indexation(indexation)) = essence.payload() {
Some(indexation)
} else {
None
}
} else {
None
}
}
_ => None,
}
}
#[allow(dead_code)]
async fn prune_seps<S: StorageBackend>(
storage: &S,
batch: &mut S::Batch,
seps: &[SolidEntryPoint],
) -> Result<usize, Error> {
let mut num = 0;
for sep in seps {
Batch::<SolidEntryPoint, MilestoneIndex>::batch_delete(storage, batch, sep)
.map_err(|e| Error::Storage(Box::new(e)))?;
num += 1;
}
Ok(num)
}