Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: creates audits as pending to prevent duplicate audits #194

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 29 additions & 14 deletions entity/src/content_audit.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.7
use crate::content;
use crate::utils;
use anyhow::anyhow;
use anyhow::{bail, Result};
use chrono::DateTime;
use chrono::Utc;
use clap::ValueEnum;
use ethportal_api::OverlayContentKey;
use sea_orm::{entity::prelude::*, ActiveValue::NotSet, Set};

#[derive(Debug, Clone, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[derive(Copy, Debug, Clone, Eq, PartialEq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "i32", db_type = "Integer")]
pub enum AuditResult {
Failure = 0,
Success = 1,
Pending = 2,
Errored = 3,
}

#[derive(Debug, Clone, Eq, Hash, PartialEq, EnumIter, DeriveActiveEnum, ValueEnum)]
Expand Down Expand Up @@ -42,8 +45,10 @@ pub enum SelectionStrategy {
impl AuditResult {
pub fn as_text(&self) -> String {
match self {
AuditResult::Failure => "fail".to_string(),
AuditResult::Success => "success".to_string(),
AuditResult::Failure => "Fail".to_string(),
AuditResult::Success => "Success".to_string(),
AuditResult::Pending => "Pending".to_string(),
AuditResult::Errored => "Error".to_string(),
}
}
}
Expand Down Expand Up @@ -104,35 +109,42 @@ impl Related<super::client_info::Entity> for Entity {

impl ActiveModelBehavior for ActiveModel {}

/// Create a new, pending audit entry.
pub async fn create(
content_key_model_id: i32,
client_info_id: i32,
node_id: i32,
query_successful: bool,
strategy_used: SelectionStrategy,
trace_string: String,
conn: &DatabaseConnection,
) -> Result<Model> {
// If no record exists, create one and return it
let audit_result = if query_successful {
AuditResult::Success
} else {
AuditResult::Failure
};

let content_audit = ActiveModel {
id: NotSet,
content_key: Set(content_key_model_id),
client_info: Set(Some(client_info_id)),
node: Set(Some(node_id)),
created_at: Set(Utc::now()),
result: Set(audit_result),
result: Set(AuditResult::Pending),
strategy_used: Set(Some(strategy_used)),
trace: Set(trace_string),
trace: Set("".to_owned()),
};
Ok(content_audit.insert(conn).await?)
}

/// Retrieve the audit, update it from pending to success, failure, or errored.
pub async fn record_result(
audit_id: i32,
audit_result: AuditResult,
trace_string: String,
conn: &DatabaseConnection,
) -> Result<Model> {
let audit: Option<Model> = Entity::find_by_id(audit_id).one(conn).await?;
let audit = audit.ok_or(anyhow!("No audit found for id {}", audit_id))?;
let mut audit: ActiveModel = audit.into();
audit.result = Set(audit_result);
audit.trace = Set(trace_string);
Ok(audit.update(conn).await?)
}

pub async fn get_audits<T: OverlayContentKey>(
content_key: &T,
conn: &DatabaseConnection,
Expand Down Expand Up @@ -168,6 +180,9 @@ impl Model {
pub fn is_success(&self) -> bool {
self.result == AuditResult::Success
}
pub fn is_pending(&self) -> bool {
self.result == AuditResult::Pending
}
pub fn created_at_local_time(&self) -> String {
self.created_at.with_timezone(&chrono::Local).to_rfc2822()
}
Expand Down
101 changes: 62 additions & 39 deletions glados-audit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,46 +264,12 @@ async fn perform_single_audit(
client.url = client.api.client_url.clone(),
"auditing content",
);
let (content_response, trace) = if client.clone().supports_trace() {
match client.api.get_content_with_trace(&task.content_key).await {
Ok(c) => c,
Err(e) => {
error!(
content.key=hex_encode(task.content_key.to_bytes()),
err=?e,
"Problem requesting content with trace from Portal node."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
return;
}
}
} else {
match client.api.get_content(&task.content_key).await {
Ok(c) => (c, "".to_owned()),
Err(e) => {
error!(
content.key=hex_encode(task.content_key.to_bytes()),
err=?e,
"Problem requesting content from Portal node."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
return;
}
}
};

// If content was absent audit result is 'fail'.
let audit_result = match content_response {
Some(content_bytes) => content_is_valid(&task.content_key, &content_bytes.raw),
None => false,
};

let content_key_model = match content::get(&task.content_key, &conn).await {
Ok(Some(m)) => m,
Ok(None) => {
error!(
content.key=?task.content_key,
audit.pass=?audit_result,
"Content key not found in db."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
Expand All @@ -327,6 +293,7 @@ async fn perform_single_audit(
err=?error,
"Could not create/lookup client info in db."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
return;
}
};
Expand All @@ -338,19 +305,75 @@ async fn perform_single_audit(
err=?err,
"Failed to created node."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
return;
}
};
if let Err(e) = content_audit::create(

let pending_audit = match content_audit::create(
content_key_model.id,
client_info_id,
node_id,
audit_result,
task.strategy,
trace,
&conn,
)
.await
{
Ok(audit) => audit,
Err(e) => {
error!(
content.key=?task.content_key,
err=?e,
"Could not create audit entry in db."
);
active_threads.fetch_sub(1, Ordering::Relaxed);
return;
}
};
let (content_response, trace, errored) = if client.clone().supports_trace() {
match client.api.get_content_with_trace(&task.content_key).await {
Ok(c) => (c.0, c.1, false),
Err(e) => {
error!(
content.key=hex_encode(task.content_key.to_bytes()),
err=?e,
"Problem requesting content with trace from Portal node."
);
(None, "".to_owned(), true)
}
}
} else {
match client.api.get_content(&task.content_key).await {
Ok(c) => (c, "".to_owned(), false),
Err(e) => {
error!(
content.key=hex_encode(task.content_key.to_bytes()),
err=?e,
"Problem requesting content from Portal node."
);
(None, "".to_owned(), true)
}
}
};

let audit_result: content_audit::AuditResult = if errored {
content_audit::AuditResult::Errored
} else {
// If content is absent or invalid, audit is a failure.
match content_response {
Some(content) => {
if content_is_valid(&task.content_key, &content.raw) {
content_audit::AuditResult::Success
} else {
content_audit::AuditResult::Failure
}
}
None => content_audit::AuditResult::Failure,
}
};

// Update audit entry with result and trace.
if let Err(e) = content_audit::record_result(pending_audit.id, audit_result, trace, &conn).await
{
error!(
content.key=?task.content_key,
Expand All @@ -366,14 +389,14 @@ async fn perform_single_audit(
Ok(Some(b)) => {
info!(
content.key=hex_encode(task.content_key.to_bytes()),
audit.pass=?audit_result,
audit.result=?audit_result,
block = b.block_number,
);
}
Ok(None) => {
error!(
content.key=hex_encode(task.content_key.to_bytes()),
audit.pass=?audit_result,
audit.result=?audit_result,
"Block metadata absent for key."
);
}
Expand Down
5 changes: 3 additions & 2 deletions glados-web/templates/audit_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@
{{ audit.id }}{% endif %}
</td>
<td><span
class="badge text-bg-{% if audit.is_success() %}success{% else %}danger{% endif %}">{%
if audit.is_success() %}Success{% else %}Fail{% endif %}</span></td>
class="badge text-bg-{% if audit.is_success() %}success{% elseif audit.is_pending() %}warning{% else %}danger{% endif %}">
{{ audit.result.as_text() }}
</span></td>
<td>{{ content.protocol_id.as_text() }}</td>
<td>{{ audit.strategy_as_text() }}</td>
<td><a href="/content/key/{{content.key_as_hex()}}/">{{ content.key_as_hex_short()
Expand Down
20 changes: 12 additions & 8 deletions glados-web/templates/content_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ <h2> Recent content with audits</h2>
{{ audit.id }}{% endif %}
</td>
<td><span
class="badge text-bg-{% if audit.is_success() %}success{% else %}danger{% endif %}">{%
if audit.is_success() %}Success{% else %}Fail{% endif %}</span></td>
class="badge text-bg-{% if audit.is_success() %}success{% elseif audit.is_pending() %}warning{% else %}danger{% endif %}">
{{ audit.result.as_text() }}
</span></td>
<td>{{ content.protocol_id.as_text() }}</td>
<td>{{ audit.strategy_as_text() }}</td>
<td><a href="/content/key/{{content.key_as_hex()}}/">{{ content.key_as_hex_short()
Expand Down Expand Up @@ -161,8 +162,9 @@ <h2> Recent audits</h2>
{{ audit.id }}{% endif %}
</td>
<td><span
class="badge text-bg-{% if audit.is_success() %}success{% else %}danger{% endif %}">{%
if audit.is_success() %}Success{% else %}Fail{% endif %}</span></td>
class="badge text-bg-{% if audit.is_success() %}success{% elseif audit.is_pending() %}warning{% else %}danger{% endif %}">
{{ audit.result.as_text() }}
</span></td>
<td>{{ content.protocol_id.as_text() }}</td>
<td>{{ audit.strategy_as_text() }}</td>
<td><a href="/content/key/{{content.key_as_hex()}}/">{{ content.key_as_hex_short()
Expand Down Expand Up @@ -212,8 +214,9 @@ <h2> Recent audit successes</h2>
{{ audit.id }}{% endif %}
</td>
<td><span
class="badge text-bg-{% if audit.is_success() %}success{% else %}danger{% endif %}">{%
if audit.is_success() %}Success{% else %}Fail{% endif %}</span></td>
class="badge text-bg-{% if audit.is_success() %}success{% elseif audit.is_pending() %}warning{% else %}danger{% endif %}">
{{ audit.result.as_text() }}
</span></td>
<td>{{ content.protocol_id.as_text() }}</td>
<td>{{ audit.strategy_as_text() }}</td>
<td><a href="/content/key/{{content.key_as_hex()}}/">{{ content.key_as_hex_short()
Expand Down Expand Up @@ -263,8 +266,9 @@ <h2> Recent audit failures</h2>
{{ audit.id }}{% endif %}
</td>
<td><span
class="badge text-bg-{% if audit.is_success() %}success{% else %}danger{% endif %}">{%
if audit.is_success() %}Success{% else %}Fail{% endif %}</span></td>
class="badge text-bg-{% if audit.is_success() %}success{% elseif audit.is_pending() %}warning{% else %}danger{% endif %}">
{{ audit.result.as_text() }}
</span></td>
<td>{{ content.protocol_id.as_text() }}</td>
<td>{{ audit.strategy_as_text() }}</td>
<td><a href="/content/key/{{content.key_as_hex()}}/">{{ content.key_as_hex_short()
Expand Down