From ba3a6f9e91c1ef7b7b744e5b0531ec390426dd25 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 10 Sep 2024 07:07:09 -0400 Subject: [PATCH] Bitcoin ScannerFeed --- Cargo.lock | 1 + processor/bitcoin/Cargo.toml | 1 + processor/bitcoin/src/block.rs | 6 +- processor/bitcoin/src/lib.rs | 9 ++- processor/bitcoin/src/output.rs | 2 +- processor/bitcoin/src/{scanner.rs => scan.rs} | 0 processor/bitcoin/src/scanner_feed.rs | 62 +++++++++++++++++++ processor/scanner/src/lib.rs | 10 ++- 8 files changed, 84 insertions(+), 7 deletions(-) rename processor/bitcoin/src/{scanner.rs => scan.rs} (100%) create mode 100644 processor/bitcoin/src/scanner_feed.rs diff --git a/Cargo.lock b/Cargo.lock index ee8c8a99..b35cda50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8141,6 +8141,7 @@ dependencies = [ "serai-message-queue", "serai-processor-messages", "serai-processor-primitives", + "serai-processor-scanner", "serai-processor-scheduler-primitives", "tokio", "zalloc", diff --git a/processor/bitcoin/Cargo.toml b/processor/bitcoin/Cargo.toml index 656c7c40..ff14890e 100644 --- a/processor/bitcoin/Cargo.toml +++ b/processor/bitcoin/Cargo.toml @@ -44,6 +44,7 @@ messages = { package = "serai-processor-messages", path = "../messages" } primitives = { package = "serai-processor-primitives", path = "../primitives" } scheduler = { package = "serai-processor-scheduler-primitives", path = "../scheduler/primitives" } +scanner = { package = "serai-processor-scanner", path = "../scanner" } message-queue = { package = "serai-message-queue", path = "../../message-queue" } diff --git a/processor/bitcoin/src/block.rs b/processor/bitcoin/src/block.rs index 304f19e3..24cccec9 100644 --- a/processor/bitcoin/src/block.rs +++ b/processor/bitcoin/src/block.rs @@ -8,10 +8,10 @@ use serai_client::networks::bitcoin::Address; use primitives::{ReceivedOutput, EventualityTracker}; -use crate::{hash_bytes, scanner::scanner, output::Output, transaction::Eventuality}; +use crate::{hash_bytes, scan::scanner, output::Output, transaction::Eventuality}; #[derive(Clone, Debug)] -pub(crate) struct BlockHeader(Header); +pub(crate) struct BlockHeader(pub(crate) Header); impl primitives::BlockHeader for BlockHeader { fn id(&self) -> [u8; 32] { hash_bytes(self.0.block_hash().to_raw_hash()) @@ -22,7 +22,7 @@ impl primitives::BlockHeader for BlockHeader { } #[derive(Clone, Debug)] -pub(crate) struct Block(BBlock); +pub(crate) struct Block(pub(crate) BBlock); #[async_trait::async_trait] impl primitives::Block for Block { diff --git a/processor/bitcoin/src/lib.rs b/processor/bitcoin/src/lib.rs index 03c9e903..bba8629e 100644 --- a/processor/bitcoin/src/lib.rs +++ b/processor/bitcoin/src/lib.rs @@ -6,12 +6,19 @@ static ALLOCATOR: zalloc::ZeroizingAlloc = zalloc::ZeroizingAlloc(std::alloc::System); -mod scanner; +// Internal utilities for scanning transactions +mod scan; +// Output trait satisfaction mod output; +// Transaction/SignableTransaction/Eventuality trait satisfaction mod transaction; +// Block trait satisfaction mod block; +// ScannerFeed trait satisfaction +mod scanner_feed; + pub(crate) fn hash_bytes(hash: bitcoin_serai::bitcoin::hashes::sha256d::Hash) -> [u8; 32] { use bitcoin_serai::bitcoin::hashes::Hash; diff --git a/processor/bitcoin/src/output.rs b/processor/bitcoin/src/output.rs index c7ed060f..a783792d 100644 --- a/processor/bitcoin/src/output.rs +++ b/processor/bitcoin/src/output.rs @@ -23,7 +23,7 @@ use serai_client::{ use primitives::{OutputType, ReceivedOutput}; -use crate::scanner::{offsets_for_key, presumed_origin, extract_serai_data}; +use crate::scan::{offsets_for_key, presumed_origin, extract_serai_data}; #[derive(Clone, PartialEq, Eq, Hash, Debug, Encode, Decode, BorshSerialize, BorshDeserialize)] pub(crate) struct OutputId([u8; 36]); diff --git a/processor/bitcoin/src/scanner.rs b/processor/bitcoin/src/scan.rs similarity index 100% rename from processor/bitcoin/src/scanner.rs rename to processor/bitcoin/src/scan.rs diff --git a/processor/bitcoin/src/scanner_feed.rs b/processor/bitcoin/src/scanner_feed.rs new file mode 100644 index 00000000..73265bfe --- /dev/null +++ b/processor/bitcoin/src/scanner_feed.rs @@ -0,0 +1,62 @@ +use bitcoin_serai::rpc::{RpcError, Rpc as BRpc}; + +use serai_client::primitives::{NetworkId, Coin, Amount}; + +use scanner::ScannerFeed; + +use crate::block::{BlockHeader, Block}; + +#[derive(Clone)] +pub(crate) struct Rpc(BRpc); + +#[async_trait::async_trait] +impl ScannerFeed for Rpc { + const NETWORK: NetworkId = NetworkId::Bitcoin; + const CONFIRMATIONS: u64 = 6; + const WINDOW_LENGTH: u64 = 6; + + const TEN_MINUTES: u64 = 1; + + type Block = Block; + + type EphemeralError = RpcError; + + async fn latest_finalized_block_number(&self) -> Result { + u64::try_from(self.0.get_latest_block_number().await?) + .unwrap() + .checked_sub(Self::CONFIRMATIONS) + .ok_or(RpcError::ConnectionError) + } + + async fn unchecked_block_header_by_number( + &self, + number: u64, + ) -> Result<::Header, Self::EphemeralError> { + Ok(BlockHeader( + self.0.get_block(&self.0.get_block_hash(number.try_into().unwrap()).await?).await?.header, + )) + } + + async fn unchecked_block_by_number( + &self, + number: u64, + ) -> Result { + Ok(Block(self.0.get_block(&self.0.get_block_hash(number.try_into().unwrap()).await?).await?)) + } + + fn dust(coin: Coin) -> Amount { + assert_eq!(coin, Coin::Bitcoin); + // 10,000 satoshis, or $5 if 1 BTC = 50,000 USD + Amount(10_000) + } + + async fn cost_to_aggregate( + &self, + coin: Coin, + _reference_block: &Self::Block, + ) -> Result { + assert_eq!(coin, Coin::Bitcoin); + // TODO + Ok(Amount(0)) + } +} diff --git a/processor/scanner/src/lib.rs b/processor/scanner/src/lib.rs index 2c56db35..4f30f5e7 100644 --- a/processor/scanner/src/lib.rs +++ b/processor/scanner/src/lib.rs @@ -71,8 +71,14 @@ pub trait ScannerFeed: 'static + Send + Sync + Clone { /// The amount of blocks to process in parallel. /// - /// This must be at least `1`. This value should be the worst-case latency to handle a block - /// divided by the expected block time. + /// This must be at least `1`. This value MUST be at least the worst-case latency to publish a + /// Batch for a block divided by the expected block time. Setting this value too low will risk a + /// backlog forming. Setting this value too high will only delay key rotation and forwarded + /// outputs. + // The latency to publish a Batch for a block is the latency of a provided transaction + // (1 minute), the latency of a signing protocol (1 minute), the latency of Serai to finalize a + // block (1 minute), and the latency to cosign such a block (5 minutes for the cosign distance + // plus 1 minute). Accordingly, this should be at least ~30 minutes, ideally 60 minutes. const WINDOW_LENGTH: u64; /// The amount of blocks which will occur in 10 minutes (approximate).