1use std::{collections::BTreeMap, ops::Range, time::Duration};
2
3use bitcoin::{block::Header, p2p::message_network::RejectReason, BlockHash, FeeRate, Wtxid};
4use tokio::sync::oneshot;
5
6use crate::IndexedFilter;
7use crate::{
8 chain::{checkpoints::HeaderCheckpoint, IndexedHeader},
9 IndexedBlock, TrustedPeer, TxBroadcast,
10};
11
12use super::error::{FetchBlockError, FetchHeaderError};
13
14#[derive(Debug, Clone)]
16pub enum Info {
17 SuccessfulHandshake,
19 ConnectionsMet,
21 Progress(Progress),
23 NewChainHeight(u32),
25 NewFork {
27 tip: IndexedHeader,
29 },
30 TxGossiped(Wtxid),
36 BlockReceived(BlockHash),
38}
39
40impl core::fmt::Display for Info {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match self {
43 Info::SuccessfulHandshake => write!(f, "Successful version handshake with a peer"),
44 Info::TxGossiped(txid) => write!(f, "Transaction gossiped: {txid}"),
45 Info::ConnectionsMet => write!(f, "Required connections met"),
46 Info::Progress(p) => {
47 let progress_percent = p.percentage_complete();
48 write!(f, "Percent complete: {progress_percent}")
49 }
50 Info::NewChainHeight(height) => write!(f, "New chain height: {height}"),
51 Info::NewFork { tip } => write!(f, "New fork {} -> {}", tip.height, tip.block_hash()),
52 Info::BlockReceived(hash) => write!(f, "Received block {hash}"),
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub enum Event {
60 Block(IndexedBlock),
64 FiltersSynced(SyncUpdate),
66 BlocksDisconnected {
68 accepted: Vec<IndexedHeader>,
70 disconnected: Vec<IndexedHeader>,
72 },
73 IndexedFilter(IndexedFilter),
75}
76
77#[derive(Debug, Clone)]
79pub struct SyncUpdate {
80 pub tip: HeaderCheckpoint,
82 pub recent_history: BTreeMap<u32, Header>,
84}
85
86impl SyncUpdate {
87 pub(crate) fn new(tip: HeaderCheckpoint, recent_history: BTreeMap<u32, Header>) -> Self {
88 Self {
89 tip,
90 recent_history,
91 }
92 }
93
94 pub fn tip(&self) -> HeaderCheckpoint {
96 self.tip
97 }
98
99 pub fn recent_history(&self) -> &BTreeMap<u32, Header> {
101 &self.recent_history
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
108pub struct Progress {
109 pub filter_headers: u32,
111 pub filters: u32,
113 pub total_to_check: u32,
115}
116
117impl Progress {
118 pub(crate) fn new(filter_headers: u32, filters: u32, total_to_check: u32) -> Self {
119 Self {
120 filter_headers,
121 filters,
122 total_to_check,
123 }
124 }
125
126 pub fn percentage_complete(&self) -> f32 {
128 self.fraction_complete() * 100.0
129 }
130
131 pub fn fraction_complete(&self) -> f32 {
133 let total = (2 * self.total_to_check) as f32;
134 (self.filter_headers + self.filters) as f32 / total
135 }
136}
137
138#[derive(Debug, Clone, Copy)]
140pub struct RejectPayload {
141 pub reason: Option<RejectReason>,
143 pub wtxid: Wtxid,
145}
146
147#[derive(Debug)]
149pub(crate) enum ClientMessage {
150 Shutdown,
152 Broadcast(TxBroadcast),
154 Rescan,
156 GetBlock(BlockRequest),
158 SetDuration(Duration),
160 AddPeer(TrustedPeer),
162 GetHeader(HeaderRequest),
164 GetHeaderBatch(BatchHeaderRequest),
166 GetBroadcastMinFeeRate(FeeRateSender),
168 NoOp,
170}
171
172type HeaderSender = tokio::sync::oneshot::Sender<Result<Header, FetchHeaderError>>;
173
174#[derive(Debug)]
175pub(crate) struct HeaderRequest {
176 pub(crate) oneshot: HeaderSender,
177 pub(crate) height: u32,
178}
179
180impl HeaderRequest {
181 pub(crate) fn new(oneshot: HeaderSender, height: u32) -> Self {
182 Self { oneshot, height }
183 }
184}
185
186type BatchHeaderSender =
187 tokio::sync::oneshot::Sender<Result<BTreeMap<u32, Header>, FetchHeaderError>>;
188
189#[derive(Debug)]
190pub(crate) struct BatchHeaderRequest {
191 pub(crate) oneshot: BatchHeaderSender,
192 pub(crate) range: Range<u32>,
193}
194
195impl BatchHeaderRequest {
196 pub(crate) fn new(oneshot: BatchHeaderSender, range: Range<u32>) -> Self {
197 Self { oneshot, range }
198 }
199}
200
201pub(crate) type FeeRateSender = tokio::sync::oneshot::Sender<FeeRate>;
202
203#[derive(Debug)]
204pub(crate) struct BlockRequest {
205 pub(crate) oneshot: oneshot::Sender<Result<IndexedBlock, FetchBlockError>>,
206 pub(crate) hash: BlockHash,
207}
208
209impl BlockRequest {
210 pub(crate) fn new(
211 oneshot: oneshot::Sender<Result<IndexedBlock, FetchBlockError>>,
212 hash: BlockHash,
213 ) -> Self {
214 Self { oneshot, hash }
215 }
216}
217
218#[derive(Debug, Clone)]
220pub enum Warning {
221 NeedConnections {
223 connected: usize,
225 required: usize,
227 },
228 PeerTimedOut,
230 CouldNotConnect,
232 NoCompactFilters,
234 PotentialStaleTip,
236 UnsolicitedMessage,
238 InvalidStartHeight,
241 CorruptedHeaders,
243 TransactionRejected {
245 payload: RejectPayload,
247 },
248 FailedPersistence {
250 warning: String,
252 },
253 EvaluatingFork,
255 EmptyPeerDatabase,
257 UnexpectedSyncError {
259 warning: String,
261 },
262 ChannelDropped,
264}
265
266impl core::fmt::Display for Warning {
267 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
268 match self {
269 Warning::NeedConnections {
270 connected,
271 required,
272 } => {
273 write!(
274 f,
275 "Looking for connections to peers. Connected: {connected}, Required: {required}"
276 )
277 }
278 Warning::InvalidStartHeight => write!(
279 f,
280 "The provided anchor is deeper than the database history."
281 ),
282 Warning::CouldNotConnect => {
283 write!(f, "An attempted connection failed or timed out.")
284 }
285 Warning::NoCompactFilters => {
286 write!(f, "A connected peer does not serve compact block filters.")
287 }
288 Warning::PotentialStaleTip => {
289 write!(
290 f,
291 "The node has been running for a long duration without receiving new blocks."
292 )
293 }
294 Warning::TransactionRejected { payload } => {
295 write!(f, "A transaction got rejected: WTXID {}", payload.wtxid)
296 }
297 Warning::FailedPersistence { warning } => {
298 write!(f, "A database failed to persist some data: {warning}")
299 }
300 Warning::EvaluatingFork => write!(f, "Peer sent us a potential fork."),
301 Warning::EmptyPeerDatabase => write!(f, "The peer database has no values."),
302 Warning::UnexpectedSyncError { warning } => {
303 write!(f, "Error handling a P2P message: {warning}")
304 }
305 Warning::CorruptedHeaders => {
306 write!(f, "The headers in the database do not link together.")
307 }
308 Warning::PeerTimedOut => {
309 write!(f, "A connection to a peer timed out.")
310 }
311 Warning::UnsolicitedMessage => {
312 write!(
313 f,
314 "A peer sent us a peer-to-peer message the node did not request."
315 )
316 }
317 Warning::ChannelDropped => {
318 write!(
319 f,
320 "A channel that was supposed to receive a message was dropped."
321 )
322 }
323 }
324 }
325}