use std::collections::BTreeMap;
use std::ops::Div;
use bitcoin::p2p::address::AddrV2;
use bitcoin::p2p::ServiceFlags;
use bitcoin::{block::Header, p2p::message_network::RejectReason, BlockHash, FeeRate, Wtxid};
use crate::chain::{BlockHeaderChanges, IndexedHeader};
use crate::{chain::checkpoints::HeaderCheckpoint, IndexedBlock, TrustedPeer};
use crate::{IndexedFilter, Package};
use super::error::FetchBlockError;
#[derive(Debug, Clone)]
pub enum Info {
SuccessfulHandshake,
ConnectionsMet,
Progress(Progress),
BlockReceived(BlockHash),
}
impl core::fmt::Display for Info {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Info::SuccessfulHandshake => write!(f, "Successful version handshake with a peer"),
Info::ConnectionsMet => write!(f, "Required connections met"),
Info::Progress(p) => {
let progress_percent = p.percentage_complete();
write!(f, "Percent complete: {progress_percent}")
}
Info::BlockReceived(hash) => write!(f, "Received block {hash}"),
}
}
}
#[derive(Debug, Clone)]
pub enum Event {
ChainUpdate(BlockHeaderChanges),
FiltersSynced(SyncUpdate),
IndexedFilter(IndexedFilter),
}
#[derive(Debug, Clone)]
pub struct SyncUpdate {
pub tip: HeaderCheckpoint,
pub recent_history: BTreeMap<u32, Header>,
}
impl SyncUpdate {
pub(crate) fn new(tip: HeaderCheckpoint, recent_history: BTreeMap<u32, Header>) -> Self {
Self {
tip,
recent_history,
}
}
pub fn tip(&self) -> HeaderCheckpoint {
self.tip
}
pub fn recent_history(&self) -> &BTreeMap<u32, Header> {
&self.recent_history
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Progress {
filter_headers: u32,
filters: u32,
total_to_check: u32,
chain_height: u32,
}
impl Progress {
pub(crate) fn new(
filter_headers: u32,
filters: u32,
total_to_check: u32,
chain_height: u32,
) -> Self {
Self {
filter_headers,
filters,
total_to_check,
chain_height,
}
}
pub fn chain_height(&self) -> u32 {
self.chain_height
}
pub fn percentage_complete(&self) -> f32 {
self.fraction_complete() * 100.0
}
pub fn fraction_complete(&self) -> f32 {
let total = (4 * self.total_to_check) as f32;
((self.filter_headers + 3 * self.filters) as f32).div(total)
}
}
#[derive(Debug, Clone, Copy)]
pub struct RejectPayload {
pub reason: Option<RejectReason>,
pub wtxid: Wtxid,
}
#[derive(Debug)]
pub(crate) enum ClientMessage {
Shutdown,
Broadcast(ClientRequest<Package, Wtxid>),
Rescan(Option<u32>),
GetBlock(ClientRequest<BlockHash, Result<IndexedBlock, FetchBlockError>>),
BestBlock(ClientRequest<(), HeaderCheckpoint>),
AddPeer(TrustedPeer),
GetBroadcastMinFeeRate(ClientRequest<(), FeeRate>),
GetPeerInfo(ClientRequest<(), Vec<(AddrV2, ServiceFlags)>>),
GetHeader(ClientRequest<u32, Option<IndexedHeader>>),
HeightOfHash(ClientRequest<BlockHash, Option<u32>>),
NoOp,
}
#[derive(Debug)]
pub(crate) struct ClientRequest<D: Clone, U> {
data: D,
response: tokio::sync::oneshot::Sender<U>,
}
impl<D: Clone, U> ClientRequest<D, U> {
pub(crate) fn new(data: D, response: tokio::sync::oneshot::Sender<U>) -> Self {
Self { data, response }
}
pub(crate) fn data(&self) -> D {
self.data.clone()
}
pub(crate) fn into_values(self) -> (D, tokio::sync::oneshot::Sender<U>) {
(self.data, self.response)
}
}
#[derive(Debug, Clone)]
pub enum Warning {
NeedConnections {
connected: usize,
required: usize,
},
PeerTimedOut,
CouldNotConnect,
NoCompactFilters,
PotentialStaleTip,
UnsolicitedMessage,
TransactionRejected {
payload: RejectPayload,
},
EvaluatingFork,
UnexpectedSyncError {
warning: String,
},
ChannelDropped,
}
impl core::fmt::Display for Warning {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Warning::NeedConnections {
connected,
required,
} => {
write!(
f,
"Looking for connections to peers. Connected: {connected}, Required: {required}"
)
}
Warning::CouldNotConnect => {
write!(f, "An attempted connection failed or timed out.")
}
Warning::NoCompactFilters => {
write!(f, "A connected peer does not serve compact block filters.")
}
Warning::PotentialStaleTip => {
write!(
f,
"The node has been running for a long duration without receiving new blocks."
)
}
Warning::TransactionRejected { payload } => {
write!(f, "A transaction got rejected: WTXID {}", payload.wtxid)
}
Warning::EvaluatingFork => write!(f, "Peer sent us a potential fork."),
Warning::UnexpectedSyncError { warning } => {
write!(f, "Error handling a P2P message: {warning}")
}
Warning::PeerTimedOut => {
write!(f, "A connection to a peer timed out.")
}
Warning::UnsolicitedMessage => {
write!(
f,
"A peer sent us a peer-to-peer message the node did not request."
)
}
Warning::ChannelDropped => {
write!(
f,
"A channel that was supposed to receive a message was dropped."
)
}
}
}
}