use super::error;
use backtrace::Backtrace;
use rand::{rngs::SmallRng, FromEntropy, Rng};
use serde::Serialize;
use std::fmt::Debug;
#[derive(Serialize)]
pub enum SecurityEvent {
InvalidTransactionAC,
InvalidTransactionMP,
InvalidTransactionConsensus,
InvalidChunkExecutor,
InvalidNetworkEventMP,
DuplicateConsensusVote,
InvalidConsensusProposal,
InvalidConsensusVote,
InvalidConsensusRound,
InvalidBlock,
InvalidNetworkPeer,
#[cfg(test)]
TestError,
}
#[must_use = "must use `log()`"]
#[derive(Serialize)]
pub struct SecurityLog {
event: SecurityEvent,
error: Option<String>,
data: Vec<String>,
backtrace: Option<Backtrace>,
}
pub fn security_log(event: SecurityEvent) -> SecurityLog {
SecurityLog::new(event)
}
impl SecurityLog {
pub(crate) fn new(event: SecurityEvent) -> Self {
SecurityLog {
event,
error: None,
data: Vec::new(),
backtrace: None,
}
}
pub fn data<T: Debug>(mut self, data: T) -> Self {
let data = format!("{:?}", data);
if usize::checked_add(self.data.len(), 1).is_some() {
self.data.push(data);
}
self
}
pub fn error<T: Debug>(mut self, error: T) -> Self {
self.error = Some(format!("{:?}", error));
self
}
pub fn backtrace(mut self, sampling_rate: u8) -> Self {
let sampling_rate = std::cmp::min(sampling_rate, 100);
self.backtrace = {
let mut rng = SmallRng::from_entropy();
match rng.gen_range(0, 100) {
x if x < sampling_rate => Some(Backtrace::new()),
_ => None,
}
};
self
}
pub(crate) fn to_string(&self) -> String {
match serde_json::to_string(&self) {
Ok(s) => s,
Err(e) => e.to_string(),
}
}
pub fn log(self) {
error!("[security] {}", self.to_string());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct SampleData {
i: u8,
s: Vec<u8>,
}
#[derive(Debug)]
enum TestError {
Error,
}
#[test]
fn test_log() {
let s = security_log(SecurityEvent::TestError)
.error(&TestError::Error)
.data(&SampleData {
i: 0xff,
s: vec![0x90, 0xcd, 0x80],
})
.data("second_payload");
assert_eq!(
s.to_string(),
r#"{"event":"TestError","error":"Error","data":["SampleData { i: 255, s: [144, 205, 128] }","\"second_payload\""],"backtrace":null}"#,
);
}
}